Browse Source

!331 升级vite4 修复vue3部分BUG
Merge pull request !331 from xingyu/dev

芋道源码 2 years ago
parent
commit
a8ae99e6b4

+ 4 - 4
README.md

@@ -223,12 +223,12 @@ ps:核心功能已经实现,正在对接微信小程序中...
 | 框架                                                                  |     说明      |   版本   |
 |----------------------------------------------------------------------|:------------:|:------:|
 | [Vue](https://staging-cn.vuejs.org/)                                 |   Vue 框架    | 3.2.45 |
-| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 3.2.3  |
-| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.25 |
+| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具  | 4.0.1  |
+| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus | 2.2.26 |
 | [TypeScript](https://www.typescriptlang.org/docs/)                   |  TypeScript  | 4.9.4  |
-| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.27 |
+| [pinia](https://pinia.vuejs.org/)                                    |    vuex5     | 2.0.28 |
 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) |    国际化     | 9.2.2  |
-| [vxe-table](https://vxetable.cn/)                                    |  vue最强表单  | 4.5.6  |
+| [vxe-table](https://vxetable.cn/)                                    |  vue最强表单  | 4.5.7  |
 
 ### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
 

+ 6 - 6
yudao-ui-admin-vue3/README.md

@@ -2,7 +2,7 @@
 
 <p align="center">
     <img src="https://img.shields.io/badge/-Vue3.2-34495e?logo=vue.j" />
-    <img src="https://img.shields.io/badge/-Vite3-646cff?logo=vite&logoColor=white" />
+    <img src="https://img.shields.io/badge/-Vite4-646cff?logo=vite&logoColor=white" />
     <img src="https://img.shields.io/badge/-TypeScript4.9-blue?logo=typescript&logoColor=white" />
     <img src="https://img.shields.io/badge/-Pinia2-yellow?logo=picpay&logoColor=white" />
     <img src="https://img.shields.io/badge/-ESLint-4b32c3?logo=eslint&logoColor=white" />
@@ -15,7 +15,7 @@
 
 ## 介绍
 
-- 基于 vue3.2+ ,TypeScript ,Element Plus 2.2.0+ ,Vite3 ,Pinia ,Vxe-table , Windicss 等开发的后台管理系统
+- 基于 vue3.2+ ,TypeScript ,Element Plus 2.2.0+ ,Vite4 ,Pinia ,Vxe-table , Windicss 等开发的后台管理系统
 
 ## 注意事项
 
@@ -30,12 +30,12 @@
 | 框架 | 说明 | 版本 |
 | --- | --- | --- |
 | [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.45 |
-| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.2.3 |
-| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.23 |
+| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.0.1 |
+| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.26 |
 | [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.9.4 |
-| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.26 |
+| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.28 |
 | [vueuse](https://vueuse.org/) | 常用工具集 | 9.6.0 |
-| [vxe-table](https://vxetable.cn/) | vue 最强表单 | 4.3.6 |
+| [vxe-table](https://vxetable.cn/) | vue 最强表单 | 4.3.7 |
 | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
 | [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.6 |
 | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |

+ 19 - 19
yudao-ui-admin-vue3/package.json

@@ -1,7 +1,7 @@
 {
   "name": "yudao-ui-admin-vue3",
-  "version": "1.6.5.1874",
-  "description": "基于vue3、vite3、element-plus、typesScript",
+  "version": "1.6.5.1875",
+  "description": "基于vue3、vite4、element-plus、typesScript",
   "author": "xingyu",
   "private": false,
   "scripts": {
@@ -33,7 +33,7 @@
     "axios": "^1.2.1",
     "crypto-js": "^4.1.1",
     "dayjs": "^1.11.7",
-    "echarts": "^5.4.0",
+    "echarts": "^5.4.1",
     "echarts-wordcloud": "^2.1.0",
     "element-plus": "2.2.26",
     "intro.js": "^6.0.0",
@@ -41,7 +41,7 @@
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.0",
     "nprogress": "^0.2.0",
-    "pinia": "^2.0.27",
+    "pinia": "^2.0.28",
     "qrcode": "^1.5.1",
     "qs": "^6.11.0",
     "url": "^0.11.0",
@@ -50,27 +50,27 @@
     "vue-i18n": "9.2.2",
     "vue-router": "^4.1.6",
     "vue-types": "^5.0.1",
-    "vxe-table": "^4.3.6",
+    "vxe-table": "^4.3.7",
     "web-storage-cache": "^1.1.1",
     "xe-utils": "^3.5.7"
   },
   "devDependencies": {
     "@commitlint/cli": "^17.3.0",
     "@commitlint/config-conventional": "^17.3.0",
-    "@iconify/json": "^2.1.149",
+    "@iconify/json": "^2.1.151",
     "@intlify/vite-plugin-vue-i18n": "^6.0.3",
     "@purge-icons/generated": "^0.9.0",
     "@types/intro.js": "^5.1.0",
     "@types/lodash-es": "^4.17.6",
-    "@types/node": "^18.11.11",
+    "@types/node": "^18.11.15",
     "@types/nprogress": "^0.2.0",
     "@types/qrcode": "^1.5.0",
     "@types/qs": "^6.9.7",
-    "@typescript-eslint/eslint-plugin": "^5.46.0",
-    "@typescript-eslint/parser": "^5.46.0",
-    "@vitejs/plugin-legacy": "^2.3.1",
-    "@vitejs/plugin-vue": "^3.2.0",
-    "@vitejs/plugin-vue-jsx": "^2.1.1",
+    "@typescript-eslint/eslint-plugin": "^5.46.1",
+    "@typescript-eslint/parser": "^5.46.1",
+    "@vitejs/plugin-legacy": "^3.0.1",
+    "@vitejs/plugin-vue": "^4.0.0",
+    "@vitejs/plugin-vue-jsx": "^3.0.0",
     "autoprefixer": "^10.4.13",
     "consola": "^2.15.3",
     "eslint": "^8.29.0",
@@ -79,13 +79,13 @@
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^9.8.0",
     "lint-staged": "^13.1.0",
-    "postcss": "^8.4.19",
+    "postcss": "^8.4.20",
     "postcss-html": "^1.5.0",
     "postcss-scss": "^4.0.6",
     "prettier": "^2.8.1",
     "rimraf": "^3.0.2",
-    "rollup": "^3.7.0",
-    "sass": "^1.56.1",
+    "rollup": "^3.7.4",
+    "sass": "^1.56.2",
     "stylelint": "^14.16.0",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-prettier": "^9.0.4",
@@ -94,17 +94,17 @@
     "stylelint-order": "^5.0.0",
     "terser": "^5.16.1",
     "typescript": "4.9.4",
-    "vite": "3.2.5",
+    "vite": "4.0.1",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-ejs": "^1.6.4",
     "vite-plugin-eslint": "^1.8.1",
     "vite-plugin-progress": "^0.0.6",
-    "vite-plugin-purge-icons": "^0.9.1",
+    "vite-plugin-purge-icons": "^0.9.2",
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-vue-setup-extend": "^0.4.0",
-    "vite-plugin-windicss": "^1.8.8",
-    "vue-tsc": "^1.0.11",
+    "vite-plugin-windicss": "^1.8.10",
+    "vue-tsc": "^1.0.13",
     "windicss": "^3.5.6"
   },
   "engines": {

File diff suppressed because it is too large
+ 2770 - 1692
yudao-ui-admin-vue3/pnpm-lock.yaml


+ 2 - 1
yudao-ui-admin-vue3/src/components/Editor/src/Editor.vue

@@ -23,6 +23,7 @@ const props = defineProps({
     type: Object as PropType<IEditorConfig>,
     default: () => undefined
   },
+  readonly: propTypes.bool.def(false),
   modelValue: propTypes.string.def('')
 })
 
@@ -61,7 +62,7 @@ const editorConfig = computed((): IEditorConfig => {
   return Object.assign(
     {
       placeholder: '请输入内容...',
-      readOnly: false,
+      readOnly: props.readonly,
       customAlert: (s: string, t: string) => {
         switch (t) {
           case 'success':

+ 1 - 1
yudao-ui-admin-vue3/src/components/Qrcode/src/Qrcode.vue

@@ -64,7 +64,7 @@ const initQrcode = async () => {
       options.errorCorrectionLevel || getErrorCorrectionLevel(unref(renderText))
     const _width: number = await getOriginWidth(unref(renderText), options)
     options.scale = props.width === 0 ? undefined : (props.width / _width) * 4
-    const canvasRef: HTMLCanvasElement = await toCanvas(
+    const canvasRef: HTMLCanvasElement | any = await toCanvas(
       unref(wrapRef) as HTMLCanvasElement,
       unref(renderText),
       options

+ 69 - 4
yudao-ui-admin-vue3/src/hooks/web/useCrudSchemas.ts

@@ -1,6 +1,9 @@
 import { reactive } from 'vue'
+import { AxiosPromise } from 'axios'
+import { findIndex } from '@/utils'
 import { eachTree, treeMap, filter } from '@/utils/tree'
 import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
+import { useI18n } from '@/hooks/web/useI18n'
 import { FormSchema } from '@/types/form'
 import { TableColumn } from '@/types/table'
 import { DescriptionsSchema } from '@/types/descriptions'
@@ -23,6 +26,8 @@ export type CrudSchema = Omit<TableColumn, 'children'> & {
 type CrudSearchParams = {
   // 是否显示在查询项
   show?: boolean
+  // 接口
+  api?: () => Promise<any>
 } & Omit<FormSchema, 'field'>
 
 type CrudTableParams = {
@@ -33,6 +38,8 @@ type CrudTableParams = {
 type CrudFormParams = {
   // 是否显示表单项
   show?: boolean
+  // 接口
+  api?: () => Promise<any>
 } & Omit<FormSchema, 'field'>
 
 type CrudDescriptionsParams = {
@@ -47,6 +54,8 @@ interface AllSchemas {
   detailSchema: DescriptionsSchema[]
 }
 
+const { t } = useI18n()
+
 // 过滤所有结构
 export const useCrudSchemas = (
   crudSchema: CrudSchema[]
@@ -61,13 +70,13 @@ export const useCrudSchemas = (
     detailSchema: []
   })
 
-  const searchSchema = filterSearchSchema(crudSchema)
+  const searchSchema = filterSearchSchema(crudSchema, allSchemas)
   allSchemas.searchSchema = searchSchema || []
 
   const tableColumns = filterTableSchema(crudSchema)
   allSchemas.tableColumns = tableColumns || []
 
-  const formSchema = filterFormSchema(crudSchema)
+  const formSchema = filterFormSchema(crudSchema, allSchemas)
   allSchemas.formSchema = formSchema
 
   const detailSchema = filterDescriptionsSchema(crudSchema)
@@ -79,9 +88,11 @@ export const useCrudSchemas = (
 }
 
 // 过滤 Search 结构
-const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
+const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
   const searchSchema: FormSchema[] = []
 
+  // 获取字典列表队列
+  const searchRequestTask: Array<() => Promise<void>> = []
   eachTree(crudSchema, (schemaItem: CrudSchema) => {
     // 判断是否显示
     if (schemaItem?.isSearch || schemaItem.search?.show) {
@@ -107,12 +118,31 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
         field: schemaItem.field,
         label: schemaItem.search?.label || schemaItem.label
       }
+      if (searchSchemaItem.api) {
+        searchRequestTask.push(async () => {
+          const res = await (searchSchemaItem.api as () => AxiosPromise)()
+          if (res) {
+            const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => {
+              return v.field === searchSchemaItem.field
+            })
+            if (index !== -1) {
+              allSchemas.searchSchema[index]!.componentProps!.options = filterOptions(
+                res,
+                searchSchemaItem.componentProps.optionsAlias?.labelField
+              )
+            }
+          }
+        })
+      }
       // 删除不必要的字段
       delete searchSchemaItem.show
 
       searchSchema.push(searchSchemaItem)
     }
   })
+  for (const task of searchRequestTask) {
+    task()
+  }
   return searchSchema
 }
 
@@ -139,9 +169,12 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
 }
 
 // 过滤 form 结构
-const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
+const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {
   const formSchema: FormSchema[] = []
 
+  // 获取字典列表队列
+  const formRequestTask: Array<() => Promise<void>> = []
+
   eachTree(crudSchema, (schemaItem: CrudSchema) => {
     // 判断是否显示
     if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) {
@@ -185,6 +218,23 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
         label: schemaItem.form?.label || schemaItem.label
       }
 
+      if (formSchemaItem.api) {
+        formRequestTask.push(async () => {
+          const res = await (formSchemaItem.api as () => AxiosPromise)()
+          if (res) {
+            const index = findIndex(allSchemas.formSchema, (v: FormSchema) => {
+              return v.field === formSchemaItem.field
+            })
+            if (index !== -1) {
+              allSchemas.formSchema[index]!.componentProps!.options = filterOptions(
+                res,
+                formSchemaItem.componentProps.optionsAlias?.labelField
+              )
+            }
+          }
+        })
+      }
+
       // 删除不必要的字段
       delete formSchemaItem.show
 
@@ -192,6 +242,9 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
     }
   })
 
+  for (const task of formRequestTask) {
+    task()
+  }
   return formSchema
 }
 
@@ -225,3 +278,15 @@ const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[
 
   return descriptionsSchema
 }
+
+// 给options添加国际化
+const filterOptions = (options: Recordable, labelField?: string) => {
+  return options.map((v: Recordable) => {
+    if (labelField) {
+      v['labelField'] = t(v.labelField)
+    } else {
+      v['label'] = t(v.label)
+    }
+    return v
+  })
+}

+ 1 - 1
yudao-ui-admin-vue3/src/hooks/web/useVxeCrudSchemas.ts

@@ -63,7 +63,7 @@ type CrudDescriptionsParams = {
 } & Omit<DescriptionsSchema, 'field'>
 
 type CrudPrintParams = {
-  // 是否显示表单
+  // 是否显示打印
   show?: boolean
 } & Omit<VxeTableDefines.ColumnInfo[], 'field'>
 

+ 1 - 1
yudao-ui-admin-vue3/src/plugins/vxeTable/index.ts

@@ -56,7 +56,7 @@ watch(
       import('./theme/light.scss')
     }
   },
-  { immediate: true }
+  { deep: true }
 )
 // 全局默认参数
 VXETable.setup({

+ 1 - 1
yudao-ui-admin-vue3/src/router/index.ts

@@ -14,7 +14,7 @@ import { usePermissionStoreWithOut } from '@/store/modules/permission'
 import { getInfoApi } from '@/api/login'
 import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
 
-const { wsCache } = useCache('sessionStorage')
+const { wsCache } = useCache()
 
 const { start, done } = useNProgress()
 

+ 25 - 3
yudao-ui-admin-vue3/src/views/infra/fileConfig/index.vue

@@ -64,8 +64,8 @@
       <el-form-item label="存储器" prop="storage">
         <el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id !== 0">
           <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
-            :key="dict.value"
+            v-for="(dict, index) in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
+            :key="index"
             :label="dict.label"
             :value="dict.value"
           />
@@ -197,7 +197,7 @@ const dialogVisible = ref(false) // 是否显示弹出层
 const dialogTitle = ref('edit') // 弹出层标题
 const formRef = ref<FormInstance>() // 表单 Ref
 const detailData = ref() // 详情 Ref
-let form = ref<FileConfigApi.FileConfigVO>({
+const form = ref<FileConfigApi.FileConfigVO>({
   id: 0,
   name: '',
   storage: 0,
@@ -230,6 +230,28 @@ const setDialogTile = (type: string) => {
 const handleCreate = (formEl: FormInstance | undefined) => {
   setDialogTile('create')
   formEl?.resetFields()
+  form.value = {
+    id: 0,
+    name: '',
+    storage: 0,
+    master: false,
+    visible: false,
+    config: {
+      basePath: '',
+      host: '',
+      port: 0,
+      username: '',
+      password: '',
+      mode: '',
+      endpoint: '',
+      bucket: '',
+      accessKey: '',
+      accessSecret: '',
+      domain: ''
+    },
+    remark: '',
+    createTime: new Date()
+  }
 }
 
 // 修改操作

+ 1 - 2
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts

@@ -12,8 +12,7 @@ export const rules = reactive({
   email: [required],
   phone: [
     {
-      min: 11,
-      max: 11,
+      len: 11,
       trigger: 'blur',
       message: '请输入正确的手机号码'
     }

+ 2 - 10
yudao-ui-admin-vue3/src/views/system/dept/index.vue

@@ -75,16 +75,15 @@
   </XModal>
 </template>
 <script setup lang="ts" name="Dept">
-import { nextTick, onMounted, reactive, ref, unref } from 'vue'
+import { nextTick, onMounted, ref, unref } from 'vue'
 import { ElSelect, ElTreeSelect, ElOption } from 'element-plus'
 import { VxeGridInstance } from 'vxe-table'
 import { handleTree, defaultProps } from '@/utils/tree'
-import { required } from '@/utils/formRules.js'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useMessage } from '@/hooks/web/useMessage'
 import { useVxeGrid } from '@/hooks/web/useVxeGrid'
 import { FormExpose } from '@/components/Form'
-import { allSchemas } from './dept.data'
+import { allSchemas, rules } from './dept.data'
 import * as DeptApi from '@/api/system/dept'
 import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
 
@@ -107,13 +106,6 @@ const actionLoading = ref(false) // 遮罩层
 const formRef = ref<FormExpose>() // 表单 Ref
 const deptOptions = ref() // 树形结构
 const userOption = ref<UserVO[]>([])
-// 新增和修改的表单校验
-const rules = reactive({
-  name: [required],
-  sort: [required],
-  path: [required],
-  status: [required]
-})
 
 const getUserList = async () => {
   const res = await getListSimpleUsersApi()

+ 49 - 139
yudao-ui-admin-vue3/src/views/system/menu/index.vue

@@ -1,34 +1,8 @@
 <template>
   <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form :model="queryParams" ref="queryForm" :inline="true">
-      <el-form-item label="菜单名称" prop="name">
-        <el-input v-model="queryParams.name" placeholder="请输入菜单名称" />
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择菜单状态">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <!-- 操作:搜索 -->
-        <XButton
-          type="primary"
-          preIcon="ep:search"
-          :title="t('common.query')"
-          @click="handleQuery()"
-        />
-        <!-- 操作:重置 -->
-        <XButton preIcon="ep:refresh-right" :title="t('common.reset')" @click="resetQuery()" />
-      </el-form-item>
-    </el-form>
-    <vxe-toolbar>
-      <template #buttons>
+    <!-- 列表 -->
+    <vxe-grid ref="xGrid" v-bind="gridOptions" show-overflow class="xtable-scrollbar">
+      <template #toolbar_buttons>
         <!-- 操作:新增 -->
         <XButton
           type="primary"
@@ -37,63 +11,30 @@
           v-hasPermi="['system:menu:create']"
           @click="handleCreate()"
         />
-        <XButton title="展开所有" @click="xTable?.setAllTreeExpand(true)" />
-        <XButton title="关闭所有" @click="xTable?.clearTreeExpand()" />
+        <XButton title="展开所有" @click="xGrid?.setAllTreeExpand(true)" />
+        <XButton title="关闭所有" @click="xGrid?.clearTreeExpand()" />
       </template>
-    </vxe-toolbar>
-    <!-- 列表 -->
-    <vxe-table
-      show-overflow
-      keep-source
-      ref="xTable"
-      :loading="tableLoading"
-      :row-config="{ keyField: 'id' }"
-      :column-config="{ resizable: true }"
-      :tree-config="{ transform: true, rowField: 'id', parentField: 'parentId' }"
-      :print-config="{}"
-      :export-config="{}"
-      :data="tableData"
-    >
-      <vxe-column title="菜单名称" field="name" width="200" tree-node>
-        <template #default="{ row }">
-          <Icon :icon="row.icon" />
-          <span class="ml-3">{{ row.name }}</span>
-        </template>
-      </vxe-column>
-      <vxe-column title="菜单类型" field="type">
-        <template #default="{ row }">
-          <DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="row.type" />
-        </template>
-      </vxe-column>
-      <vxe-column title="路由地址" field="path" />
-      <vxe-column title="组件路径" field="component" />
-      <vxe-column title="权限标识" field="permission" />
-      <vxe-column title="排序" field="sort" />
-      <vxe-column title="状态" field="status">
-        <template #default="{ row }">
-          <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
-        </template>
-      </vxe-column>
-      <vxe-column title="创建时间" field="createTime" formatter="formatDate" />
-      <vxe-column title="操作" width="200">
-        <template #default="{ row }">
-          <!-- 操作:修改 -->
-          <XTextButton
-            preIcon="ep:edit"
-            :title="t('action.edit')"
-            v-hasPermi="['system:menu:update']"
-            @click="handleUpdate(row.id)"
-          />
-          <!-- 操作:删除 -->
-          <XTextButton
-            preIcon="ep:delete"
-            :title="t('action.del')"
-            v-hasPermi="['system:menu:delete']"
-            @click="handleDelete(row.id)"
-          />
-        </template>
-      </vxe-column>
-    </vxe-table>
+      <template #name_default="{ row }">
+        <Icon :icon="row.icon" />
+        <span class="ml-3">{{ row.name }}</span>
+      </template>
+      <template #actionbtns_default="{ row }">
+        <!-- 操作:修改 -->
+        <XTextButton
+          preIcon="ep:edit"
+          :title="t('action.edit')"
+          v-hasPermi="['system:menu:update']"
+          @click="handleUpdate(row.id)"
+        />
+        <!-- 操作:删除 -->
+        <XTextButton
+          preIcon="ep:delete"
+          :title="t('action.del')"
+          v-hasPermi="['system:menu:delete']"
+          @click="handleDelete(row.id)"
+        />
+      </template>
+    </vxe-grid>
   </ContentWrap>
   <!-- 添加或修改菜单对话框 -->
   <XModal id="menuModel" v-model="dialogVisible" :title="dialogTitle">
@@ -124,7 +65,7 @@
         <el-radio-group v-model="menuForm.type">
           <el-radio-button
             v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
-            :key="dict.value"
+            :key="dict.label"
             :label="dict.value"
           >
             {{ dict.label }}
@@ -178,7 +119,7 @@
             <el-radio
               border
               v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-              :key="dict.value"
+              :key="dict.label"
               :label="dict.value"
             >
               {{ dict.label }}
@@ -235,7 +176,7 @@
 </template>
 <script setup lang="ts" name="Menu">
 // 全局相关的 import
-import { onMounted, reactive, ref } from 'vue'
+import { ref } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import { useMessage } from '@/hooks/web/useMessage'
@@ -245,9 +186,7 @@ import {
   ElFormItem,
   ElInput,
   ElInputNumber,
-  ElSelect,
   ElTreeSelect,
-  ElOption,
   ElRadio,
   ElRadioGroup,
   ElRadioButton,
@@ -255,21 +194,33 @@ import {
 } from 'element-plus'
 import { Tooltip } from '@/components/Tooltip'
 import { IconSelect } from '@/components/Icon'
-import { VxeTableInstance } from 'vxe-table'
+import { VxeGridInstance } from 'vxe-table'
 // 业务相关的 import
-import * as MenuApi from '@/api/system/menu'
-import { required } from '@/utils/formRules.js'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
 import { handleTree, defaultProps } from '@/utils/tree'
+import * as MenuApi from '@/api/system/menu'
+import { allSchemas, rules } from './menu.data'
+import { useVxeGrid } from '@/hooks/web/useVxeGrid'
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 const { wsCache } = useCache()
 // 列表相关的变量
-const xTable = ref<VxeTableInstance>()
-const tableLoading = ref(false)
-const tableData = ref()
+// 列表相关的变量
+const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
+const treeConfig = {
+  transform: true,
+  rowField: 'id',
+  parentField: 'parentId',
+  expandAll: false
+}
+const { gridOptions, getList, deleteData } = useVxeGrid<MenuApi.MenuVO>({
+  allSchemas: allSchemas,
+  treeConfig: treeConfig,
+  getListApi: MenuApi.getMenuListApi,
+  deleteApi: MenuApi.deleteMenuApi
+})
 // 弹窗相关的变量
 const dialogVisible = ref(false) // 是否显示弹出层
 const dialogTitle = ref('edit') // 弹出层标题
@@ -292,13 +243,6 @@ const menuForm = ref<MenuApi.MenuVO>({
   keepAlive: true,
   createTime: new Date()
 })
-// 新增和修改的表单校验
-const rules = reactive({
-  name: [required],
-  sort: [required],
-  path: [required],
-  status: [required]
-})
 
 // ========== 下拉框[上级菜单] ==========
 const menuOptions = ref<any[]>([]) // 树形结构
@@ -311,31 +255,6 @@ const getTree = async () => {
   menuOptions.value.push(menu)
 }
 
-// ========== 查询 ==========
-const queryParams = reactive<MenuApi.MenuPageReqVO>({
-  name: undefined,
-  status: undefined
-})
-// 执行查询
-const getList = async () => {
-  tableLoading.value = true
-  const res = await MenuApi.getMenuListApi(queryParams)
-  tableData.value = res
-  tableLoading.value = false
-}
-
-// 查询操作
-const handleQuery = async () => {
-  await getList()
-}
-
-// 重置操作
-const resetQuery = async () => {
-  queryParams.name = undefined
-  queryParams.status = undefined
-  await getList()
-}
-
 // ========== 新增/修改 ==========
 
 // 设置标题
@@ -407,7 +326,7 @@ const submitForm = async () => {
     actionLoading.value = false
     wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
     // 操作成功,重新加载列表
-    await getList()
+    await getList(xGrid)
   }
 }
 
@@ -419,15 +338,6 @@ const isExternal = (path: string) => {
 // ========== 删除 ==========
 // 删除操作
 const handleDelete = async (rowId: number) => {
-  message.delConfirm().then(async () => {
-    await MenuApi.deleteMenuApi(rowId)
-    message.success(t('common.delSuccess'))
-    await getList()
-  })
+  await deleteData(xGrid, rowId)
 }
-
-// ========== 初始化 ==========
-onMounted(async () => {
-  await getList()
-})
 </script>

+ 75 - 0
yudao-ui-admin-vue3/src/views/system/menu/menu.data.ts

@@ -0,0 +1,75 @@
+import { reactive } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { DICT_TYPE } from '@/utils/dict'
+import { required } from '@/utils/formRules'
+import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas'
+const { t } = useI18n() // 国际化
+
+// 新增和修改的表单校验
+export const rules = reactive({
+  name: [required],
+  sort: [required],
+  path: [required],
+  status: [required]
+})
+
+// CrudSchema
+const crudSchemas = reactive<VxeCrudSchema>({
+  primaryKey: 'id',
+  primaryType: null,
+  action: true,
+  columns: [
+    {
+      title: '上级菜单',
+      field: 'parentId',
+      isTable: false
+    },
+    {
+      title: '菜单名称',
+      field: 'name',
+      isSearch: true,
+      table: {
+        treeNode: true,
+        align: 'left',
+        width: '200px',
+        slots: {
+          default: 'name_default'
+        }
+      }
+    },
+    {
+      title: '菜单类型',
+      field: 'type',
+      dictType: DICT_TYPE.SYSTEM_MENU_TYPE
+    },
+    {
+      title: '路由地址',
+      field: 'path'
+    },
+    {
+      title: '组件路径',
+      field: 'component'
+    },
+    {
+      title: '权限标识',
+      field: 'permission'
+    },
+    {
+      title: '排序',
+      field: 'sort'
+    },
+    {
+      title: t('common.status'),
+      field: 'status',
+      dictType: DICT_TYPE.COMMON_STATUS,
+      dictClass: 'number',
+      isSearch: true
+    },
+    {
+      title: t('common.createTime'),
+      field: 'createTime',
+      formatter: 'formatDate'
+    }
+  ]
+})
+export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 1 - 1
yudao-ui-admin-vue3/src/views/system/notice/index.vue

@@ -53,7 +53,7 @@
       :data="detailData"
     >
       <template #content="{ row }">
-        <Editor :model-value="row.content" read-only="true" />
+        <Editor :model-value="row.content" :readonly="true" />
       </template>
     </Descriptions>
     <template #footer>

+ 1 - 1
yudao-ui-admin-vue3/src/views/system/user/index.vue

@@ -145,7 +145,7 @@
             v-for="item in postOptions"
             :key="item.id"
             :label="item.name"
-            :value="item.id"
+            :value="(item.id as unknown as number)"
           />
         </el-select>
       </template>

+ 1 - 3
yudao-ui-admin-vue3/src/views/system/user/user.data.ts

@@ -10,12 +10,10 @@ export const rules = reactive({
   username: [required],
   nickname: [required],
   email: [required],
-  postIds: [required],
   status: [required],
   mobile: [
     {
-      min: 11,
-      max: 11,
+      len: 11,
       trigger: 'blur',
       message: '请输入正确的手机号码'
     }