Commit bc97bd30a0c9e840077f31fbfd530b5417557be5

Authored by xingyu
1 parent 5d4673ac

perf: tree table

yudao-ui-admin-vue3/src/hooks/web/useVxeCrudSchemas.ts
... ... @@ -17,10 +17,10 @@ import { ComponentOptions } from '@/types/components'
17 17 export type VxeCrudSchema = {
18 18 primaryKey?: string // 主键ID
19 19 primaryTitle?: string // 主键标题 默认为序号
20   - primaryType?: VxeColumnPropTypes.Type // 不填写为数据库编号 还支持 "seq" | "radio" | "checkbox" | "expand" | "html" | null
  20 + primaryType?: VxeColumnPropTypes.Type // 不填写为数据库编号 null为不显示 还支持 "seq" | "radio" | "checkbox" | "expand" | "html" | null
21 21 action?: boolean // 是否开启操作栏插槽
22 22 actionTitle?: string // 操作栏标题 默认为操作
23   - actionWidth?: string // 操作栏插槽宽度,一般2个字带图标 text 类型按钮 50-70
  23 + actionWidth?: string // 操作栏插槽宽度,一般2个字带图标 text 类型按钮 50-70
24 24 columns: VxeCrudColumns[]
25 25 }
26 26 type VxeCrudColumns = Omit<VxeTableColumn, 'children'> & {
... ... @@ -170,7 +170,7 @@ const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns
170 170 const { t } = useI18n()
171 171 const tableSchema: VxeGridPropTypes.Columns = []
172 172 // 主键ID
173   - if (crudSchema.primaryKey) {
  173 + if (crudSchema.primaryKey && crudSchema.primaryType) {
174 174 const tableSchemaItem = {
175 175 title: crudSchema.primaryTitle ? crudSchema.primaryTitle : t('common.index'),
176 176 field: crudSchema.primaryKey,
... ... @@ -179,6 +179,7 @@ const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns
179 179 }
180 180 tableSchema.push(tableSchemaItem)
181 181 }
  182 +
182 183 eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
183 184 // 判断是否显示
184 185 if (schemaItem?.isTable !== false && schemaItem?.table?.show !== false) {
... ...
yudao-ui-admin-vue3/src/hooks/web/useVxeGrid.ts
1 1 import { computed, nextTick, reactive } from 'vue'
2   -import { SizeType, VxeGridProps } from 'vxe-table'
  2 +import { SizeType, VxeGridProps, VxeTablePropTypes } from 'vxe-table'
3 3 import { useAppStore } from '@/store/modules/app'
4 4 import { VxeAllSchemas } from './useVxeCrudSchemas'
5 5 import { useI18n } from '@/hooks/web/useI18n'
... ... @@ -11,6 +11,7 @@ const message = useMessage() // 消息弹窗
11 11  
12 12 interface UseVxeGridConfig<T = any> {
13 13 allSchemas: VxeAllSchemas
  14 + treeConfig?: VxeTablePropTypes.TreeConfig
14 15 getListApi: (option: any) => Promise<T>
15 16 deleteApi?: (option: any) => Promise<T>
16 17 exportListApi?: (option: any) => Promise<T>
... ... @@ -41,6 +42,7 @@ export const useVxeGrid = &lt;T = any&gt;(config?: UseVxeGridConfig&lt;T&gt;) =&gt; {
41 42 /**
42 43 * grid options 初始化
43 44 */
  45 + console.info(config?.allSchemas.tableSchema)
44 46 const gridOptions = reactive<VxeGridProps>({
45 47 loading: true,
46 48 size: currentSize as any,
... ... @@ -62,25 +64,6 @@ export const useVxeGrid = &lt;T = any&gt;(config?: UseVxeGridConfig&lt;T&gt;) =&gt; {
62 64 items: config?.allSchemas.searchSchema
63 65 },
64 66 columns: config?.allSchemas.tableSchema,
65   - pagerConfig: {
66   - border: false, // 带边框
67   - background: true, // 带背景颜色
68   - perfect: false, // 配套的样式
69   - pageSize: 10, // 每页大小
70   - pagerCount: 7, // 显示页码按钮的数量
71   - autoHidden: true, // 当只有一页时自动隐藏
72   - pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
73   - layouts: [
74   - 'PrevJump',
75   - 'PrevPage',
76   - 'JumpNumber',
77   - 'NextPage',
78   - 'NextJump',
79   - 'Sizes',
80   - 'FullJump',
81   - 'Total'
82   - ]
83   - },
84 67 proxyConfig: {
85 68 seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
86 69 form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
... ... @@ -91,8 +74,10 @@ export const useVxeGrid = &lt;T = any&gt;(config?: UseVxeGridConfig&lt;T&gt;) =&gt; {
91 74 if (config?.queryParams) {
92 75 queryParams = Object.assign(queryParams, config.queryParams)
93 76 }
94   - queryParams.pageSize = page.pageSize
95   - queryParams.pageNo = page.currentPage
  77 + if (!config?.treeConfig) {
  78 + queryParams.pageSize = page.pageSize
  79 + queryParams.pageNo = page.currentPage
  80 + }
96 81 gridOptions.loading = false
97 82 return new Promise(async (resolve) => {
98 83 resolve(await config?.getListApi(queryParams))
... ... @@ -120,6 +105,30 @@ export const useVxeGrid = &lt;T = any&gt;(config?: UseVxeGridConfig&lt;T&gt;) =&gt; {
120 105 }
121 106 })
122 107  
  108 + if (config?.treeConfig) {
  109 + gridOptions.treeConfig = config.treeConfig
  110 + } else {
  111 + gridOptions.pagerConfig = {
  112 + border: false, // 带边框
  113 + background: true, // 带背景颜色
  114 + perfect: false, // 配套的样式
  115 + pageSize: 10, // 每页大小
  116 + pagerCount: 7, // 显示页码按钮的数量
  117 + autoHidden: false, // 当只有一页时自动隐藏
  118 + pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
  119 + layouts: [
  120 + 'PrevJump',
  121 + 'PrevPage',
  122 + 'JumpNumber',
  123 + 'NextPage',
  124 + 'NextJump',
  125 + 'Sizes',
  126 + 'FullJump',
  127 + 'Total'
  128 + ]
  129 + }
  130 + }
  131 +
123 132 /**
124 133 * 刷新列表
125 134 * @param ref
... ...
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
1   -import { required } from '@/utils/formRules'
2 1 import { reactive } from 'vue'
3   -import { FormSchema } from '@/types/form'
  2 +import { useI18n } from '@/hooks/web/useI18n'
  3 +import { required } from '@/utils/formRules'
  4 +import { DICT_TYPE } from '@/utils/dict'
  5 +import { VxeCrudSchema, useVxeCrudSchemas } from '@/hooks/web/useVxeCrudSchemas'
  6 +const { t } = useI18n() // 国际化
4 7  
5 8 // 表单校验
6 9 export const rules = reactive({
... ... @@ -17,55 +20,49 @@ export const rules = reactive({
17 20 ]
18 21 })
19 22  
20   -export const modelSchema = reactive<FormSchema[]>([
21   - {
22   - label: '上级部门',
23   - field: 'parentId',
24   - component: 'Input'
25   - },
26   - {
27   - label: '部门名称',
28   - field: 'name',
29   - component: 'Input',
30   - formItemProps: {
31   - rules: [required]
32   - }
33   - },
34   - {
35   - label: '负责人',
36   - field: 'leaderUserId',
37   - component: 'Input'
38   - },
39   - {
40   - label: '联系电话',
41   - field: 'phone',
42   - component: 'Input'
43   - },
44   - {
45   - label: '邮箱',
46   - field: 'email',
47   - component: 'Input'
48   - },
49   - {
50   - label: '显示排序',
51   - field: 'sort',
52   - component: 'Input'
53   - },
54   - {
55   - label: '状态',
56   - field: 'status',
57   - component: 'RadioButton',
58   - componentProps: {
59   - options: [
60   - {
61   - label: '开启',
62   - value: 0
63   - },
64   - {
65   - label: '关闭',
66   - value: 1
67   - }
68   - ]
  23 +// CrudSchema
  24 +const crudSchemas = reactive<VxeCrudSchema>({
  25 + primaryKey: 'id',
  26 + primaryType: null,
  27 + action: true,
  28 + columns: [
  29 + {
  30 + title: '上级部门',
  31 + field: 'parentId',
  32 + isTable: false
  33 + },
  34 + {
  35 + title: '部门名称',
  36 + field: 'name',
  37 + isSearch: true,
  38 + table: {
  39 + treeNode: true,
  40 + align: 'left'
  41 + }
  42 + },
  43 + {
  44 + title: '负责人',
  45 + field: 'leaderUserId'
  46 + },
  47 + {
  48 + title: '联系电话',
  49 + field: 'phone'
  50 + },
  51 + {
  52 + title: '邮箱',
  53 + field: 'email'
  54 + },
  55 + {
  56 + title: '显示排序',
  57 + field: 'sort'
  58 + },
  59 + {
  60 + title: t('common.status'),
  61 + field: 'status',
  62 + dictType: DICT_TYPE.COMMON_STATUS,
  63 + dictData: 'number',
  64 + isSearch: true
69 65 }
70   - }
71   -])
  66 + ]
  67 +})
  68 +export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
... ...
yudao-ui-admin-vue3/src/views/system/dept/index.vue
1 1 <template>
2 2 <ContentWrap>
3   - <!-- 搜索工作栏 -->
4   - <el-form :model="queryParams" ref="queryForm" :inline="true">
5   - <el-form-item label="部门名称" prop="name">
6   - <el-input v-model="queryParams.name" placeholder="请输入部门名称" />
7   - </el-form-item>
8   - <el-form-item label="状态" prop="status">
9   - <el-select v-model="queryParams.status" placeholder="请选择部门状态">
10   - <el-option
11   - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
12   - :key="dict.value"
13   - :label="dict.label"
14   - :value="dict.value"
15   - />
16   - </el-select>
17   - </el-form-item>
18   - <el-form-item>
19   - <!-- 操作:搜索 -->
20   - <XButton
21   - type="primary"
22   - preIcon="ep:search"
23   - :title="t('common.query')"
24   - @click="handleQuery()"
25   - />
26   - <!-- 操作:重置 -->
27   - <XButton preIcon="ep:refresh-right" :title="t('common.reset')" @click="resetQuery()" />
28   - </el-form-item>
29   - </el-form>
30   - <vxe-toolbar>
31   - <template #buttons>
  3 + <!-- 列表 -->
  4 + <vxe-grid ref="xGrid" v-bind="gridOptions" show-overflow class="xtable-scrollbar">
  5 + <template #toolbar_buttons>
32 6 <!-- 操作:新增 -->
33 7 <XButton
34 8 type="primary"
... ... @@ -37,58 +11,32 @@
37 11 v-hasPermi="['system:dept:create']"
38 12 @click="handleCreate()"
39 13 />
40   - <XButton title="展开所有" @click="xTable?.setAllTreeExpand(true)" />
41   - <XButton title="关闭所有" @click="xTable?.clearTreeExpand()" />
  14 + <XButton title="展开所有" @click="xGrid?.setAllTreeExpand(true)" />
  15 + <XButton title="关闭所有" @click="xGrid?.clearTreeExpand()" />
42 16 </template>
43   - </vxe-toolbar>
44   - <!-- 列表 -->
45   - <vxe-table
46   - show-overflow
47   - keep-source
48   - ref="xTable"
49   - :loading="tableLoading"
50   - :row-config="{ keyField: 'id' }"
51   - :column-config="{ resizable: true }"
52   - :tree-config="{ transform: true, rowField: 'id', parentField: 'parentId' }"
53   - :print-config="{}"
54   - :export-config="{}"
55   - :data="tableData"
56   - class="xtable"
57   - >
58   - <vxe-column title="部门名称" field="name" width="200" tree-node />
59   - <vxe-column title="负责人" field="leaderUserId" :formatter="userNicknameFormat" />
60   - <vxe-column title="排序" field="sort" />
61   - <vxe-column title="状态" field="status">
62   - <template #default="{ row }">
63   - <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
64   - </template>
65   - </vxe-column>
66   - <vxe-column title="创建时间" field="createTime" formatter="formatDate" />
67   - <vxe-column title="操作" width="200">
68   - <template #default="{ row }">
69   - <!-- 操作:修改 -->
70   - <XTextButton
71   - preIcon="ep:edit"
72   - :title="t('action.edit')"
73   - v-hasPermi="['system:dept:update']"
74   - @click="handleUpdate(row.id)"
75   - />
76   - <!-- 操作:删除 -->
77   - <XTextButton
78   - preIcon="ep:delete"
79   - :title="t('action.del')"
80   - v-hasPermi="['system:dept:delete']"
81   - @click="handleDelete(row.id)"
82   - />
83   - </template>
84   - </vxe-column>
85   - </vxe-table>
  17 + <template #actionbtns_default="{ row }">
  18 + <!-- 操作:修改 -->
  19 + <XTextButton
  20 + preIcon="ep:edit"
  21 + :title="t('action.edit')"
  22 + v-hasPermi="['system:dept:update']"
  23 + @click="handleUpdate(row.id)"
  24 + />
  25 + <!-- 操作:删除 -->
  26 + <XTextButton
  27 + preIcon="ep:delete"
  28 + :title="t('action.del')"
  29 + v-hasPermi="['system:dept:delete']"
  30 + @click="handleDelete(row.id)"
  31 + />
  32 + </template>
  33 + </vxe-grid>
86 34 </ContentWrap>
87 35 <!-- 添加或修改菜单对话框 -->
88 36 <XModal id="deptModel" v-model="dialogVisible" :title="dialogTitle">
89 37 <!-- 对话框(添加 / 修改) -->
90 38 <!-- 操作工具栏 -->
91   - <Form ref="formRef" :schema="modelSchema" :rules="rules">
  39 + <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules">
92 40 <template #parentId>
93 41 <el-tree-select
94 42 node-key="id"
... ... @@ -128,22 +76,32 @@
128 76 import { nextTick, onMounted, reactive, ref, unref } from 'vue'
129 77 import { useI18n } from '@/hooks/web/useI18n'
130 78 import { useMessage } from '@/hooks/web/useMessage'
131   -import { ElForm, ElFormItem, ElInput, ElSelect, ElTreeSelect, ElOption } from 'element-plus'
132   -import { VxeTableInstance } from 'vxe-table'
133   -import { modelSchema } from './dept.data'
  79 +import { VxeGridInstance } from 'vxe-table'
  80 +import { ElSelect, ElTreeSelect, ElOption } from 'element-plus'
  81 +import { allSchemas } from './dept.data'
134 82 import * as DeptApi from '@/api/system/dept'
135 83 import { getListSimpleUsersApi } from '@/api/system/user'
136 84 import { required } from '@/utils/formRules.js'
137   -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
138 85 import { handleTree } from '@/utils/tree'
139 86 import { FormExpose } from '@/components/Form'
  87 +import { useVxeGrid } from '@/hooks/web/useVxeGrid'
140 88  
141 89 const { t } = useI18n() // 国际化
142 90 const message = useMessage() // 消息弹窗
143 91 // 列表相关的变量
144   -const xTable = ref<VxeTableInstance>()
145   -const tableLoading = ref(false)
146   -const tableData = ref()
  92 +const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
  93 +const treeConfig = {
  94 + transform: true,
  95 + rowField: 'id',
  96 + parentField: 'parentId',
  97 + expandAll: true
  98 +}
  99 +const { gridOptions, getList, deleteData } = useVxeGrid<DeptApi.DeptVO>({
  100 + allSchemas: allSchemas,
  101 + treeConfig: treeConfig,
  102 + getListApi: DeptApi.getDeptPageApi,
  103 + deleteApi: DeptApi.deleteDeptApi
  104 +})
147 105 // 弹窗相关的变量
148 106 const dialogVisible = ref(false) // 是否显示弹出层
149 107 const dialogTitle = ref('edit') // 弹出层标题
... ... @@ -181,30 +139,6 @@ const getUserList = async () =&gt; {
181 139 const res = await getListSimpleUsersApi()
182 140 userOption.value = res
183 141 }
184   -// ========== 查询 ==========
185   -const queryParams = reactive<DeptApi.DeptPageReqVO>({
186   - name: undefined,
187   - status: undefined
188   -})
189   -// 执行查询
190   -const getList = async () => {
191   - tableLoading.value = true
192   - const res = await DeptApi.getDeptPageApi(queryParams)
193   - tableData.value = res
194   - tableLoading.value = false
195   -}
196   -
197   -// 查询操作
198   -const handleQuery = async () => {
199   - await getList()
200   -}
201   -
202   -// 重置操作
203   -const resetQuery = async () => {
204   - queryParams.name = undefined
205   - queryParams.status = undefined
206   - await getList()
207   -}
208 142  
209 143 // ========== 新增/修改 ==========
210 144  
... ... @@ -247,13 +181,15 @@ const submitForm = async () =&gt; {
247 181 data.leaderUserId = leaderUserId.value
248 182 if (dialogTitle.value.startsWith('新增')) {
249 183 await DeptApi.createDeptApi(data)
  184 + message.success(t('common.createSuccess'))
250 185 } else if (dialogTitle.value.startsWith('修改')) {
251 186 await DeptApi.updateDeptApi(data)
  187 + message.success(t('common.updateSuccess'))
252 188 }
253   - // 操作成功,重新加载列表
254 189 dialogVisible.value = false
255 190 } finally {
256 191 actionLoading.value = false
  192 + await getList(xGrid)
257 193 }
258 194 }
259 195 })
... ... @@ -261,29 +197,24 @@ const submitForm = async () =&gt; {
261 197  
262 198 // 删除操作
263 199 const handleDelete = async (rowId: number) => {
264   - message.delConfirm().then(async () => {
265   - await DeptApi.deleteDeptApi(rowId)
266   - message.success(t('common.delSuccess'))
267   - await getList()
268   - })
269   -}
270   -
271   -const userNicknameFormat = (row) => {
272   - if (!row && !row.row && !row.row.leaderUserId) {
273   - return '未设置'
274   - }
275   - for (const user of userOption.value) {
276   - if (row.row.leaderUserId === user.id) {
277   - return user.nickname
278   - }
279   - }
280   - return '未知【' + row.row.leaderUserId + '】'
  200 + await deleteData(xGrid, rowId)
281 201 }
282 202  
  203 +//const userNicknameFormat = (row) => {
  204 +// if (!row && !row.row && !row.row.leaderUserId) {
  205 +// return '未设置'
  206 +// }
  207 +// for (const user of userOption.value) {
  208 +// if (row.row.leaderUserId === user.id) {
  209 +// return user.nickname
  210 +// }
  211 +// }
  212 +// return '未知【' + row.row.leaderUserId + '】'
  213 +//}
  214 +//
283 215 // ========== 初始化 ==========
284 216 onMounted(async () => {
285 217 await getTree()
286 218 await getUserList()
287   - await getList()
288 219 })
289 220 </script>
... ...