Selaa lähdekoodia

增加proFrom组件

tongshangming 3 vuotta sitten
vanhempi
commit
75fa65b87b

+ 0 - 1
src/auto-import.d.ts

@@ -2,7 +2,6 @@
 export {}
 declare global {
   const EffectScope: typeof import('vue')['EffectScope']
-  const ElEditor: typeof import('element-plus/es')['ElEditor']
   const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
   const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
   const asyncComputed: typeof import('@vueuse/core')['asyncComputed']

+ 2 - 0
src/components.d.ts

@@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
+    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
@@ -47,6 +48,7 @@ declare module '@vue/runtime-core' {
     IEpRefresh: typeof import('~icons/ep/refresh')['default']
     IEpSearch: typeof import('~icons/ep/search')['default']
     IEpUser: typeof import('~icons/ep/user')['default']
+    ProForm: typeof import('./components/ProForm.vue')['default']
     ProTable: typeof import('./components/ProTable.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 8 - 3
src/components/ElEditor.vue

@@ -15,7 +15,7 @@ const props = defineProps({
   },
   height: {
     type: String,
-    default: '500px'
+    default: '400px'
   }
 })
 const emits = defineEmits(['update:modelValue', 'change'])
@@ -66,8 +66,13 @@ const handleChange = (editor: any) => {
 </script>
 
 <template>
-  <div style="border: 1px solid #ccc">
-    <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
+  <div style="border: 1px solid var(--el-border-color)" class="w-full">
+    <Toolbar
+      style="border-bottom: 1px solid var(--el-border-color)"
+      :editor="editorRef"
+      :defaultConfig="toolbarConfig"
+      :mode="mode"
+    />
     <Editor
       style="overflow-y: hidden"
       :style="{ height: height }"

+ 146 - 0
src/components/ProForm.vue

@@ -0,0 +1,146 @@
+<script setup lang="ts">
+import type { PropType } from 'vue'
+import type { BasicForm, BasicFormItem, AdvancedForm, AdvancedFormItem } from '@/types/form'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  form: {
+    required: true,
+    type: Object as PropType<BasicForm | AdvancedForm>
+  },
+  formData: {
+    required: true,
+    type: Object
+  },
+  create: {
+    required: true,
+    type: Function
+  },
+  update: {
+    required: true,
+    type: Function
+  },
+  advanced: Boolean
+})
+
+const formProps: any = ref({
+  labelWidth: '100px',
+  labelPosition: 'top',
+  size: 'large',
+  ...props.form?.props
+})
+
+const placeholder = (item: BasicFormItem) => {
+  if (['select'].includes(item.type)) {
+    return '请选择' + item.label
+  } else {
+    return '请输入' + item.label
+  }
+}
+
+const formRef = ref()
+
+const submit = () => {
+  let message
+  formRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      try {
+        if (props.formData.id) {
+          await props.update(props.formData)
+          message = '新增成功'
+        } else {
+          await props.create(props.formData)
+          message = '修改成功'
+        }
+
+        ElMessage({
+          type: 'success',
+          message
+        })
+        return Promise.resolve()
+      } catch (error) {}
+    }
+  })
+}
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <el-form :model="formData" ref="formRef" v-bind="formProps" class="form">
+    <template v-if="advanced">
+      <el-card v-for="item in (form.formItems as AdvancedFormItem[])" :title="item.label">
+        <el-row :gutter="20">
+          <el-col :span="sub.span || 12" v-for="sub in item.group">
+            <el-form-item :label="sub.label" :rules="sub.rules" :prop="sub.name">
+              <component
+                :is="'el-' + sub.type"
+                v-model="formData[sub.name]"
+                v-bind="sub.props"
+                :placeholder="sub.placeholder || placeholder(sub)"
+              >
+                <template v-if="sub.type === 'radio-group'">
+                  <el-radio :label="option.label" v-for="option in sub.options" v-bind="option.props">
+                    {{ option.value }}
+                  </el-radio>
+                </template>
+                <template v-if="sub.type === 'checkbox-group'">
+                  <el-checkbox :label="option.label" v-for="option in sub.options" v-bind="option.props">
+                    {{ option.value }}
+                  </el-checkbox>
+                </template>
+                <template v-else-if="sub.type === 'select'">
+                  <el-option
+                    :label="option.label"
+                    :value="option.value"
+                    v-for="option in sub.options"
+                    v-bind="option.props"
+                  ></el-option>
+                </template>
+              </component>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-card>
+    </template>
+    <el-row :gutter="20" v-else>
+      <el-col :span="item.span || 12" v-for="item in (form.formItems as BasicFormItem[])">
+        <el-form-item :label="item.label" :rules="item.rules" :prop="item.name">
+          <component
+            :is="'el-' + item.type"
+            v-model="formData[item.name]"
+            v-bind="item.props"
+            :placeholder="item.placeholder || placeholder(item)"
+          >
+            <template v-if="item.type === 'radio-group'">
+              <el-radio :label="option.label" v-for="option in item.options" v-bind="option.props">
+                {{ option.value }}
+              </el-radio>
+            </template>
+            <template v-if="item.type === 'checkbox-group'">
+              <el-checkbox :label="option.label" v-for="option in item.options" v-bind="option.props">
+                {{ option.value }}
+              </el-checkbox>
+            </template>
+            <template v-else-if="item.type === 'select'">
+              <el-option
+                :label="option.label"
+                :value="option.value"
+                v-for="option in item.options"
+                v-bind="option.props"
+              ></el-option>
+            </template>
+          </component>
+        </el-form-item>
+      </el-col>
+    </el-row>
+  </el-form>
+</template>
+
+<style lang="scss" scoped>
+.form .el-select {
+  width: 100%;
+}
+</style>

+ 47 - 96
src/components/ProTable.vue

@@ -1,20 +1,10 @@
 <script setup lang="ts">
 import { ElMessage, ElMessageBox, type FormProps } from 'element-plus'
 import type { PropType } from 'vue'
+import type { BasicForm, BasicFormItem } from '@/types/form'
+import router from '@/router'
+import type { RouteLocationNormalized } from 'vue-router'
 
-interface column {
-  label?: string
-  value: string
-  name: string
-  formType: string
-  placeholder: string
-  options: Array<any>
-  rules: any
-  search: boolean
-  width: string | number
-  type: string
-  span: number
-}
 interface CRUD {
   create: Function
   update: Function
@@ -23,27 +13,27 @@ interface CRUD {
   getRecord: Function
 }
 const props = defineProps({
-  crud: Object as PropType<CRUD>,
+  crud: {
+    required: true,
+    type: Object as PropType<CRUD>
+  },
   pageSize: {
     type: Number,
     default: 10
   },
-  columns: {
-    type: Array as PropType<column[]>,
-    default: () => []
-  },
   selection: {
     type: Boolean,
     default: true
   },
-  form: Object as PropType<FormProps>,
-  formItem: Object,
-  formComp: Object
+  form: {
+    type: Object as PropType<BasicForm>,
+    required: true
+  }
 })
 
 // ============== 查询部分开始 ===============
 const query = ref<any>({})
-const searchList = computed(() => props.columns.filter(item => item.search))
+const searchList = computed(() => props?.form.formItems.filter(item => item.search))
 const handleQuery = () => {
   curPage.value = 1
   getTableData()
@@ -83,19 +73,28 @@ const handleSelectionChange = (columns: any[]) => {
 // ============== 表格部分结束 ===============
 
 // ============== crud部分开始 ===============
+const formRoute = props.form.route
 const handleCreate = () => {
-  dialogTitle.value = '新增'
-  dialogVisible.value = true
+  if (formRoute) {
+    router.push(formRoute)
+  } else {
+    dialogTitle.value = '新增'
+    dialogVisible.value = true
+  }
 }
 const handleUpdate = (row: any) => {
-  dialogTitle.value = '编辑'
-  dialogVisible.value = true
-  if (props.crud?.getRecord) {
-    props.crud.getRecord({ id: row.id }).then((res: any) => {
-      formData.value = res.data
-    })
+  if (formRoute) {
+    router.push(formRoute)
   } else {
-    formData.value = row
+    dialogTitle.value = '编辑'
+    dialogVisible.value = true
+    if (props.crud?.getRecord) {
+      props.crud.getRecord({ id: row.id }).then((res: any) => {
+        formData.value = res.data
+      })
+    } else {
+      formData.value = row
+    }
   }
 }
 const handleDelete = (id: string | number) => {
@@ -115,57 +114,32 @@ const handlePatchDelete = () => {}
 // ============== crud部分结束 ===============
 
 // ============== 表单部分开始 ===============
-const formConfig: any = ref({
-  labelWidth: '100px',
-  labelPosition: 'top',
-  size: 'large',
-  ...props.form
-})
+const formData = ref<any>({})
+const formRef = ref()
 const dialogTitle = ref('新增')
 const dialogVisible = ref(false)
 
-const closeDialog = () => {
-  dialogVisible.value = false
-  formData.value = {}
-}
+props.form?.formItems.forEach(item => {
+  formData.value[item.name] = item.value
+})
 
-const formData = ref<any>({})
-const formRef = ref()
-const formList = computed(() => props.columns.filter(item => item.formType))
-const placeholder = (item: column) => {
-  if (['select'].includes(item.formType)) {
+const placeholder = (item: BasicFormItem) => {
+  if (['select'].includes(item.type)) {
     return '请选择' + item.label
   } else {
     return '请输入' + item.label
   }
 }
 
-formList.value.forEach(item => {
-  formData.value[item.name] = item.value
-})
+const closeDialog = () => {
+  dialogVisible.value = false
+  formData.value = {}
+}
 
 const submit = () => {
-  let res, message
-  formRef.value.validate(async (valid: boolean) => {
-    if (valid) {
-      if (formData.value.id) {
-        res = props.crud?.update(formData.value)
-        message = '新增成功'
-      } else {
-        res = props.crud?.create(formData.value)
-        message = '修改成功'
-      }
-
-      if (res.code === 0) {
-        ElMessage({
-          type: 'success',
-          message
-        })
-        closeDialog()
-      }
-    }
-  })
+  formRef.value.submit().then(() => closeDialog())
 }
+
 // ============== 表单部分结束 ===============
 </script>
 
@@ -175,11 +149,11 @@ const submit = () => {
       <el-form :inline="true">
         <el-form-item :label="item.label" v-for="item in searchList">
           <component
-            :is="'el-' + item.formType"
+            :is="'el-' + item.type"
             v-model="query[item.name]"
             :placeholder="item.placeholder || placeholder(item)"
           >
-            <template v-if="item.formType === 'radio-group'">
+            <template v-if="item.type === 'radio-group'">
               <el-radio :label="option.label" v-for="option in item.options">{{ option.value }}</el-radio>
             </template>
           </component>
@@ -228,31 +202,11 @@ const submit = () => {
       draggable
       :title="dialogTitle"
       v-model="dialogVisible"
-      width="1000px"
+      width="1200px"
       @close="closeDialog"
       :close-on-click-modal="false"
     >
-      <el-form :model="formData" ref="formRef" v-bind="formConfig" class="form">
-        <el-row :gutter="20">
-          <el-col :span="item.span || 12" v-for="item in formList">
-            <el-form-item :label="item.label" :rules="item.rules" :prop="item.name">
-              <component
-                :is="'el-' + item.formType"
-                v-model="formData[item.name]"
-                v-bind="formComp"
-                :placeholder="item.placeholder || placeholder(item)"
-              >
-                <template v-if="item.formType === 'radio-group'">
-                  <el-radio :label="option.label" v-for="option in item.options">{{ option.value }}</el-radio>
-                </template>
-                <template v-else-if="item.formType === 'select'">
-                  <el-option :label="option.label" :value="option.value" v-for="option in item.options"></el-option>
-                </template>
-              </component>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
+      <pro-form :form="form" :formData="formData" ref="formRef" :create="crud.create" :update="crud?.update"></pro-form>
 
       <template #footer>
         <el-button @click="dialogVisible = false">取消</el-button>
@@ -267,7 +221,4 @@ const submit = () => {
   background-color: #f5f7fa;
   color: rgb(31, 34, 37);
 }
-.form .el-select {
-  width: 100%;
-}
 </style>

+ 1 - 1
src/router/guard.ts

@@ -27,7 +27,7 @@ const createRouterGuard = (router: Router) => {
         if (!userStore.flag) {
           await userStore.getUserInfo()
 
-          const accessedRouters = await routerStore.generatorRouter('')
+          const accessedRouters = await routerStore.generatorRouter()
           accessedRouters.forEach(route => {
             router.addRoute('layout', route)
           })

+ 21 - 10
src/stores/router.ts

@@ -1,11 +1,8 @@
 import type { RouteRecordRaw } from 'vue-router'
-import {constantRouter, asyncRouter} from '@/router'
+import { constantRouter, asyncRouter } from '@/router'
 
-function hasPermission (permission:any[], route:RouteRecordRaw) {
+function hasPermission(route: RouteRecordRaw, permission: any[]) {
   if (route.meta && route.meta.permission) {
-    if (permission === undefined) {
-      return false
-    }
     let flag = false
     for (let i = 0, len = permission.length; i < len; i++) {
       flag = route.meta.permission.includes(permission[i])
@@ -18,9 +15,23 @@ function hasPermission (permission:any[], route:RouteRecordRaw) {
   return true
 }
 
-function filterAsyncRouter (routerMap:RouteRecordRaw[], role:any) {
+function hasRole(route: RouteRecordRaw, role: any[]) {
+  if (route.meta && route.meta.role) {
+    let flag = false
+    for (let i = 0, len = role.length; i < len; i++) {
+      flag = route.meta.role.includes(role[i])
+      if (flag) {
+        return true
+      }
+    }
+    return false
+  }
+  return true
+}
+
+function filterAsyncRouter(routerMap: RouteRecordRaw[], role: any[]) {
   const accessedRouters = routerMap.filter(route => {
-    if (hasPermission(role.permissionList, route)) {
+    if (hasRole(route, role)) {
       if (route.children && route.children.length) {
         route.children = filterAsyncRouter(route.children, role)
       }
@@ -37,11 +48,11 @@ export const useRouterStore = defineStore({
     menuRouter: <any>[]
   }),
   actions: {
-    generatorRouter(role: any):Promise<RouteRecordRaw[]> {
+    generatorRouter(role?: any[]): Promise<RouteRecordRaw[]> {
       return new Promise(resolve => {
-        this.menuRouter = filterAsyncRouter(asyncRouter, role)
+        this.menuRouter = role ? filterAsyncRouter(asyncRouter, role) : asyncRouter
         resolve(this.menuRouter)
       })
     }
   }
-})
+})

+ 0 - 2
src/stores/user.ts

@@ -19,8 +19,6 @@ export const useUserStore = defineStore({
       this.token = res.accessToken
       localStorage.setItem('token', this.token)
 
-      await this.getUserInfo()
-
       router.replace({ path: (router.currentRoute.value.query.redirect as string) || '/home' })
     },
     logout() {

+ 32 - 0
src/types/form.ts

@@ -0,0 +1,32 @@
+import type { FormProps, FormItemRule } from 'element-plus'
+import type { RouteLocationNormalized } from 'vue-router'
+
+export type BasicFormItem = {
+  label: string
+  value: string | number | boolean
+  name: string
+  type: string
+  placeholder?: string
+  children?: Array<BasicFormItem>
+  options?: Array<any>
+  search?: boolean
+  required?: boolean
+  rules?: FormItemRule | FormItemRule[]
+  span?: number
+  props?: object
+}
+export type BasicForm = {
+  props?: FormProps
+  route?: RouteLocationNormalized
+  formItems: BasicFormItem[]
+}
+
+export type AdvancedFormItem = {
+  label: string
+  group: BasicFormItem[]
+}
+export type AdvancedForm = {
+  props?: FormProps
+  route?: RouteLocationNormalized
+  formItems: AdvancedFormItem[]
+}

+ 52 - 56
src/views/system/user.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
-import ElEditor from '@/components/ElEditor.vue'
+import type { BasicForm } from '@/types/form'
+
 const testRequest = () => {
   return new Promise(resolve => {
     return resolve({
@@ -17,7 +18,7 @@ const testRequest = () => {
 
 const testGetRole = () => {
   Promise.resolve().then(() => {
-    columns.value[4].options = [
+    form.formItems[3].options = [
       {
         label: '测试角色',
         value: '1'
@@ -27,63 +28,58 @@ const testGetRole = () => {
 }
 testGetRole()
 
-const columns = ref([
-  {
-    label: '用户名',
-    value: '',
-    name: 'name',
-    formType: 'input',
-    rules: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-    search: true
-  },
-  {
-    label: '手机号',
-    value: '',
-    name: 'phone',
-    formType: 'input',
-    search: true
-  },
-  {
-    label: '性别',
-    value: '1',
-    name: 'gender',
-    formType: 'radio-group',
-    options: [
-      {
-        label: '1',
-        value: '男'
-      },
-      {
-        label: '2',
-        value: '女'
-      }
-    ]
-  },
-  {
-    label: '角色',
-    value: '',
-    name: 'role',
-    formType: 'select',
-    options: []
-  },
-  {
-    label: '状态',
-    value: true,
-    name: 'state',
-    formType: 'switch'
-  },
-  {
-    label: '详情',
-    value: '<div>ff</div>',
-    name: 'detail',
-    formType: 'editor',
-    span: 24
-  }
-])
+const form = reactive<BasicForm>({
+  formItems: [
+    {
+      label: '用户名',
+      value: '',
+      name: 'name',
+      type: 'input',
+      rules: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+      search: true
+    },
+    {
+      label: '手机号',
+      value: '',
+      name: 'phone',
+      type: 'input',
+      search: true
+    },
+    {
+      label: '性别',
+      value: '1',
+      name: 'gender',
+      type: 'radio-group',
+      options: [
+        {
+          label: '1',
+          value: '男'
+        },
+        {
+          label: '2',
+          value: '女'
+        }
+      ]
+    },
+    {
+      label: '角色',
+      value: '',
+      name: 'role',
+      type: 'select',
+      options: []
+    },
+    {
+      label: '状态',
+      value: true,
+      name: 'state',
+      type: 'switch'
+    }
+  ]
+})
 </script>
 
 <template>
-  <pro-table :crud="{ getList: testRequest }" :columns="columns">
+  <pro-table :crud="{ getList: testRequest }" :form="form">
     <el-table-column prop="name" label="用户名"></el-table-column>
     <el-table-column prop="phone" label="手机号"></el-table-column>
     <el-table-column prop="gender" label="性别"></el-table-column>