Commit fc8a23355238007f7926335b3c862281be10ccee

Authored by xingyu4j
1 parent b5a4a544

feat: menu vxe table

yudao-ui-admin-vue3/src/plugins/vxeTable/index.ts
... ... @@ -60,7 +60,14 @@ VXETable.setup({
60 60 autoResize: true, // 自动监听父元素的变化去重新计算表格
61 61 resizable: true, // 列是否允许拖动列宽调整大小
62 62 emptyText: '暂无数据', // 空表单
63   - highlightHoverRow: true // 自动监听父元素的变化去重新计算表格
  63 + highlightHoverRow: true, // 自动监听父元素的变化去重新计算表格
  64 + treeConfig: {
  65 + rowField: 'id',
  66 + parentField: 'parentId',
  67 + children: 'children',
  68 + indent: 20,
  69 + showIcon: true
  70 + }
64 71 },
65 72 grid: {
66 73 toolbarConfig: {
... ... @@ -127,13 +134,28 @@ VXETable.setup({
127 134 : XEUtils.toFormatString(XEUtils.get(enUS, key), args)
128 135 }
129 136 })
130   -// 格式金额,默认2位数
131   -VXETable.formats.add('formatAmount', ({ cellValue }, digits = 2) => {
132   - return XEUtils.commafy(XEUtils.toNumber(cellValue), { digits })
133   -})
134   -// 格式日期,默认 yyyy-MM-dd HH:mm:ss
135   -VXETable.formats.add('formatDate', ({ cellValue }, format = 'yyyy-MM-dd HH:mm:ss') => {
136   - return XEUtils.toDateString(cellValue, format)
  137 +// 自定义全局的格式化处理函数
  138 +VXETable.formats.mixin({
  139 + // 格式日期,默认 yyyy-MM-dd HH:mm:ss
  140 + formatDate({ cellValue }, format) {
  141 + return XEUtils.toDateString(cellValue, format || 'yyyy-MM-dd HH:mm:ss')
  142 + },
  143 + // 四舍五入金额,每隔3位逗号分隔,默认2位数
  144 + formatAmount({ cellValue }, digits = 2) {
  145 + return XEUtils.commafy(Number(cellValue), { digits })
  146 + },
  147 + // 格式化银行卡,默认每4位空格隔开
  148 + formatBankcard({ cellValue }) {
  149 + return XEUtils.commafy(XEUtils.toValueString(cellValue), { spaceNumber: 4, separator: ' ' })
  150 + },
  151 + // 四舍五入,默认两位数
  152 + formatFixedNumber({ cellValue }, digits = 2) {
  153 + return XEUtils.toFixed(XEUtils.round(cellValue, digits), digits)
  154 + },
  155 + // 向下舍入,默认两位数
  156 + formatCutNumber({ cellValue }, digits = 2) {
  157 + return XEUtils.toFixed(XEUtils.floor(cellValue, digits), digits)
  158 + }
137 159 })
138 160 export const setupVxeTable = (app: App<Element>) => {
139 161 // 表格功能
... ...
yudao-ui-admin-vue3/src/views/system/menu/index.vue
1 1 <script setup lang="ts">
2   -import { ref, reactive, onMounted } from 'vue'
3   -import { handleTree } from '@/utils/tree'
4   -import dayjs from 'dayjs'
5   -import { IconSelect } from '@/components/Icon'
6   -import { Tooltip } from '@/components/Tooltip'
7 2 import * as MenuApi from '@/api/system/menu'
8   -import { useI18n } from '@/hooks/web/useI18n'
9   -import {
10   - ElRow,
11   - ElCol,
12   - ElTable,
13   - ElTableColumn,
14   - ElForm,
15   - ElFormItem,
16   - ElInput,
17   - ElInputNumber,
18   - ElSelect,
19   - ElTreeSelect,
20   - ElOption,
21   - ElRadioGroup,
22   - ElRadioButton
23   -} from 'element-plus'
24 3 import { MenuVO } from '@/api/system/menu/types'
25   -import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
26   -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  4 +import { useI18n } from '@/hooks/web/useI18n'
27 5 import { useMessage } from '@/hooks/web/useMessage'
28 6 import { required } from '@/utils/formRules.js'
29   -const message = useMessage()
  7 +import { onMounted, reactive, ref } from 'vue'
  8 +import { VxeTableInstance } from 'vxe-table'
  9 +import { DICT_TYPE } from '@/utils/dict'
  10 +import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
  11 +
30 12 const { t } = useI18n() // 国际化
31   -// ========== 创建菜单树结构 ==========
32   -const loading = ref(true)
33   -const menuData = ref<any[]>([]) // 树形结构
34   -const getList = async () => {
35   - const res = await MenuApi.getMenuListApi(queryParams)
36   - menuData.value = handleTree(res)
37   - loading.value = false
38   -}
39   -const menuProps = {
40   - checkStrictly: true,
41   - children: 'children',
42   - label: 'name',
43   - value: 'id'
44   -}
45   -interface Tree {
46   - id: number
47   - name: string
48   - children?: Tree[] | any[]
49   -}
50   -const menuOptions = ref<any[]>([]) // 树形结构
51   -const getTree = async () => {
52   - menuOptions.value = []
53   - const res = await MenuApi.listSimpleMenusApi()
54   - let menu: Tree = { id: 0, name: '主类目', children: [] }
55   - menu.children = handleTree(res)
56   - menuOptions.value.push(menu)
57   -}
58   -// ========== 查询 ==========
59   -const queryParams = reactive({
60   - name: undefined,
61   - status: undefined
62   -})
63   -// 查询操作
64   -const handleQuery = async () => {
65   - await getList()
66   -}
67   -// 重置操作
68   -const resetQuery = async () => {
69   - queryParams.name = undefined
70   - queryParams.status = undefined
71   - await getList()
72   -}
73   -// ========== CRUD 相关 ==========
  13 +const message = useMessage()
  14 +const xTable = ref<VxeTableInstance>()
  15 +const tableLoading = ref(false)
  16 +const tableData = ref()
74 17 const actionLoading = ref(false) // 遮罩层
75 18 const actionType = ref('') // 操作按钮的类型
76 19 const dialogVisible = ref(false) // 是否显示弹出层
... ... @@ -90,42 +33,46 @@ const menuForm = ref&lt;MenuVO&gt;({
90 33 keepAlive: true,
91 34 createTime: ''
92 35 })
93   -// 表单校验
94   -const rules = reactive({
95   - name: [required],
96   - sort: [required],
97   - path: [required],
98   - status: [required]
  36 +// ========== 查询 ==========
  37 +const queryParams = reactive({
  38 + name: undefined,
  39 + status: undefined
99 40 })
  41 +const getList = async () => {
  42 + tableLoading.value = true
  43 + const res = await MenuApi.getMenuListApi(queryParams)
  44 + tableData.value = res
  45 + tableLoading.value = false
  46 +}
100 47 // 设置标题
101 48 const setDialogTile = (type: string) => {
102 49 dialogTitle.value = t('action.' + type)
103 50 actionType.value = type
104 51 dialogVisible.value = true
105 52 }
106   -// 新建操作
107   -const handleCreate = () => {
108   - // 重置表单
109   - setDialogTile('create')
110   -}
111 53 // 修改操作
112 54 const handleUpdate = async (row: MenuVO) => {
113 55 // 设置数据
114 56 const res = await MenuApi.getMenuApi(row.id)
  57 + console.log(res)
115 58 menuForm.value = res
116 59 setDialogTile('update')
117 60 }
118 61 // 删除操作
119 62 const handleDelete = async (row: MenuVO) => {
120   - message
121   - .confirm(t('common.delDataMessage'), t('common.confirmTitle'))
122   - .then(async () => {
123   - await MenuApi.deleteMenuApi(row.id)
124   - message.success(t('common.delSuccess'))
125   - })
126   - .catch(() => {})
127   - await getList()
  63 + message.confirm(t('common.delDataMessage'), t('common.confirmTitle')).then(async () => {
  64 + await MenuApi.deleteMenuApi(row.id)
  65 + message.success(t('common.delSuccess'))
  66 + await getList()
  67 + })
128 68 }
  69 +// 表单校验
  70 +const rules = reactive({
  71 + name: [required],
  72 + sort: [required],
  73 + path: [required],
  74 + status: [required]
  75 +})
129 76 // 保存操作
130 77 const isExternal = (path: string) => {
131 78 return /^(https?:|mailto:|tel:)/.test(path)
... ... @@ -162,248 +109,103 @@ const submitForm = async () =&gt; {
162 109 actionLoading.value = false
163 110 }
164 111 }
165   -// ========== 初始化 ==========
166 112 onMounted(async () => {
167 113 await getList()
168   - getTree()
169 114 })
170 115 </script>
171 116 <template>
172   - <!-- 搜索工作区 -->
173 117 <ContentWrap>
174   - <el-form :model="queryParams" ref="queryForm" :inline="true">
175   - <el-form-item label="菜单名称" prop="name">
176   - <el-input v-model="queryParams.name" placeholder="请输入菜单名称" />
177   - </el-form-item>
178   - <el-form-item label="状态" prop="status">
179   - <el-select v-model="queryParams.status" placeholder="请选择菜单状态">
180   - <el-option
181   - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
182   - :key="dict.value"
183   - :label="dict.label"
184   - :value="dict.value"
185   - />
186   - </el-select>
187   - </el-form-item>
188   - <el-form-item>
189   - <el-button type="primary" @click="handleQuery">
190   - <Icon icon="ep:search" class="mr-5px" />
191   - {{ t('common.query') }}
192   - </el-button>
193   - <el-button @click="resetQuery">
194   - <Icon icon="ep:refresh-right" class="mr-5px" />
195   - {{ t('common.reset') }}
196   - </el-button>
197   - </el-form-item>
198   - </el-form>
199   - </ContentWrap>
200   - <!-- 列表 -->
201   - <ContentWrap>
202   - <div class="mb-10px">
203   - <el-button type="primary" v-hasPermi="['system:notice:create']" @click="handleCreate">
204   - <Icon icon="ep:zoom-in" class="mr-1px" /> {{ t('action.add') }}
205   - </el-button>
206   - </div>
207   - <el-table
208   - v-loading="loading"
209   - table-layout="auto"
210   - row-key="id"
211   - :data="menuData"
212   - :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  118 + <vxe-toolbar>
  119 + <template #buttons>
  120 + <vxe-button @click="xTable?.setAllTreeExpand(true)">展开所有</vxe-button>
  121 + <vxe-button @click="xTable?.clearTreeExpand()">关闭所有</vxe-button>
  122 + </template>
  123 + </vxe-toolbar>
  124 + <vxe-table
  125 + show-overflow
  126 + keep-source
  127 + ref="xTable"
  128 + :loading="tableLoading"
  129 + :row-config="{ keyField: 'id' }"
  130 + :column-config="{ resizable: true }"
  131 + :tree-config="{ transform: true, rowField: 'id', parentField: 'parentId' }"
  132 + :print-config="{}"
  133 + :export-config="{}"
  134 + :data="tableData"
213 135 >
214   - <el-table-column label="菜单名称" prop="name" width="240px">
215   - <template #default="scope">
216   - <Icon :icon="scope.row.icon" />
217   - <span class="ml-3">{{ scope.row.name }}</span>
218   - </template>
219   - </el-table-column>
220   - <el-table-column label="菜单类型" prop="type">
221   - <template #default="scope">
222   - <DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="scope.row.type" />
  136 + <vxe-column title="菜单名称" field="name" width="200" tree-node>
  137 + <template #default="{ row }">
  138 + <Icon :icon="row.icon" />
  139 + <span class="ml-3">{{ row.name }}</span>
223 140 </template>
224   - </el-table-column>
225   - <el-table-column label="路由地址" prop="path" />
226   - <el-table-column label="组件路径" prop="component" />
227   - <el-table-column label="权限标识" prop="permission" />
228   - <el-table-column label="排序" prop="sort" />
229   - <el-table-column label="状态" prop="status">
230   - <template #default="scope">
231   - <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
  141 + </vxe-column>
  142 + <vxe-column title="菜单类型" field="type">
  143 + <template #default="{ row }">
  144 + <DictTag :type="DICT_TYPE.SYSTEM_MENU_TYPE" :value="row.type" />
232 145 </template>
233   - </el-table-column>
234   - <el-table-column label="创建时间" prop="createTime">
235   - <template #default="scope">
236   - <span>{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
  146 + </vxe-column>
  147 + <vxe-column title="路由地址" field="path" />
  148 + <vxe-column title="组件路径" field="component" />
  149 + <vxe-column title="权限标识" field="permission" />
  150 + <vxe-column title="排序" field="sort" />
  151 + <vxe-column title="状态" field="status">
  152 + <template #default="{ row }">
  153 + <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
237 154 </template>
238   - </el-table-column>
239   - <el-table-column label="操作">
240   - <template #default="scope">
241   - <el-button
242   - link
243   - type="primary"
  155 + </vxe-column>
  156 + <vxe-column title="创建时间" field="createTime" formatter="formatDate" />
  157 + <vxe-column title="操作" width="200">
  158 + <template #default="{ row }">
  159 + <vxe-button
  160 + type="text"
  161 + status="primary"
244 162 v-hasPermi="['system:menu:update']"
245   - @click="handleUpdate(scope.row)"
  163 + @click="handleUpdate(row)"
246 164 >
247 165 <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
248   - </el-button>
249   - <el-button
250   - link
251   - type="primary"
  166 + </vxe-button>
  167 + <vxe-button
  168 + type="text"
  169 + status="primary"
252 170 v-hasPermi="['system:menu:delete']"
253   - @click="handleDelete(scope.row)"
  171 + @click="handleDelete(row)"
254 172 >
255 173 <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
256   - </el-button>
  174 + </vxe-button>
257 175 </template>
258   - </el-table-column>
259   - </el-table>
  176 + </vxe-column>
  177 + </vxe-table>
260 178 </ContentWrap>
261   - <!-- 添加或修改菜单对话框 -->
262   - <Dialog v-model="dialogVisible" :title="dialogTitle" maxHeight="400px" width="45%">
263   - <el-form
264   - :model="menuForm"
265   - :rules="rules"
266   - :inline="true"
267   - label-width="120px"
268   - label-position="right"
269   - >
270   - <el-row :gutter="24">
271   - <el-col :span="24">
272   - <el-form-item label="上级菜单">
273   - <el-tree-select
274   - node-key="id"
275   - v-model="menuForm.parentId"
276   - :props="menuProps"
277   - :data="menuOptions"
278   - check-strictly
279   - />
280   - </el-form-item>
281   - </el-col>
282   - <el-col :span="12">
283   - <el-form-item label="菜单类型" prop="type">
284   - <el-radio-group v-model="menuForm.type">
285   - <el-radio-button
286   - v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
287   - :key="dict.value"
288   - :label="dict.value"
289   - >
290   - {{ dict.label }}
291   - </el-radio-button>
292   - </el-radio-group>
293   - </el-form-item>
294   - </el-col>
295   - <el-col :span="12">
296   - <el-form-item label="菜单名称" prop="name">
297   - <el-input v-model="menuForm.name" placeholder="请输入菜单名称" clearable />
298   - </el-form-item>
299   - </el-col>
300   - <template v-if="menuForm.type !== 3">
301   - <el-col :span="12">
302   - <el-form-item label="菜单图标">
303   - <IconSelect v-model="menuForm.icon" clearable />
304   - </el-form-item>
305   - </el-col>
306   - <el-col :span="12">
307   - <el-form-item label="路由地址" prop="path">
308   - <template #label>
309   - <Tooltip
310   - titel="路由地址"
311   - message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
312   - />
313   - </template>
314   - <el-input v-model="menuForm.path" placeholder="请输入路由地址" clearable />
315   - </el-form-item>
316   - </el-col>
317   - </template>
318   - <template v-if="menuForm.type === 2">
319   - <el-col :span="12">
320   - <el-form-item label="路由地址" prop="component">
321   - <el-input v-model="menuForm.component" placeholder="请输入组件地址" clearable />
322   - </el-form-item>
323   - </el-col>
324   - </template>
325   - <template v-if="menuForm.type !== 1">
326   - <el-col :span="12">
327   - <el-form-item label="权限标识" prop="permission">
328   - <template #label>
329   - <Tooltip
330   - titel="权限标识"
331   - message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
332   - />
333   - </template>
334   - <el-input v-model="menuForm.permission" placeholder="请输入权限标识" clearable />
335   - </el-form-item>
336   - </el-col>
337   - </template>
338   - <el-col :span="12">
339   - <el-form-item label="显示排序" prop="sort">
340   - <el-input-number v-model="menuForm.sort" controls-position="right" :min="0" clearable />
341   - </el-form-item>
342   - </el-col>
343   - <el-col :span="12">
344   - <el-form-item label="菜单状态" prop="status">
345   - <el-radio-group v-model="menuForm.status">
346   - <el-radio-button
347   - v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
348   - :key="dict.value"
349   - :label="dict.value"
350   - >
351   - {{ dict.label }}
352   - </el-radio-button>
353   - </el-radio-group>
354   - </el-form-item>
355   - </el-col>
356   - <template v-if="menuForm.type !== 3">
357   - <el-col :span="12">
358   - <el-form-item label="显示状态" prop="status">
359   - <template #label>
360   - <Tooltip
361   - titel="显示状态"
362   - message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问"
363   - />
364   - </template>
365   - <el-radio-group v-model="menuForm.visible">
366   - <el-radio-button key="true" :label="true">显示</el-radio-button>
367   - <el-radio-button key="false" :label="false">隐藏</el-radio-button>
368   - </el-radio-group>
369   - </el-form-item>
370   - </el-col>
371   - </template>
372   - <template v-if="menuForm.type === 2">
373   - <el-col :span="12">
374   - <el-form-item label="缓存状态" prop="keepAlive">
375   - <template #label>
376   - <Tooltip
377   - titel="缓存状态"
378   - message="选择缓存时,则会被 `keep-alive` 缓存,需要匹配组件的 `name` 和路由地址保持一致"
379   - />
380   - </template>
381   - <el-radio-group v-model="menuForm.keepAlive">
382   - <el-radio-button key="true" :label="true">缓存</el-radio-button>
383   - <el-radio-button key="false" :label="false">不缓存</el-radio-button>
384   - </el-radio-group>
385   - </el-form-item>
386   - </el-col>
387   - </template>
388   - </el-row>
389   - </el-form>
390   - <!-- 操作按钮 -->
391   - <template #footer>
392   - <el-button
  179 + <vxe-modal
  180 + v-model="dialogVisible"
  181 + id="menuModel"
  182 + :title="dialogTitle"
  183 + width="800"
  184 + height="400"
  185 + min-width="460"
  186 + min-height="320"
  187 + show-zoom
  188 + resize
  189 + remember
  190 + storage
  191 + transfer
  192 + show-footer
  193 + >
  194 + <template #default>
  195 + <!-- 对话框(添加 / 修改) -->
  196 + <vxe-form
  197 + ref="xForm"
393 198 v-if="['create', 'update'].includes(actionType)"
394   - type="primary"
395   - :loading="actionLoading"
396   - @click="submitForm"
  199 + :data="menuForm"
  200 + :rules="rules"
  201 + @submit="submitForm"
397 202 >
398   - {{ t('action.save') }}
399   - </el-button>
400   - <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
  203 + <vxe-form-item title="菜单名称" field="name" :item-render="{}">
  204 + <template #default="{ data }">
  205 + <vxe-input v-model="data.name" placeholder="请输入菜单名称" clearable />
  206 + </template>
  207 + </vxe-form-item>
  208 + </vxe-form>
401 209 </template>
402   - </Dialog>
  210 + </vxe-modal>
403 211 </template>
404   -<style lang="less" scoped>
405   -:deep(.el-button.is-text) {
406   - margin-left: 0;
407   - padding: 8px 10px;
408   -}
409   -</style>
... ...
yudao-ui-admin-vue3/src/views/system/post/index.vue
... ... @@ -123,7 +123,7 @@ const submitForm: VxeFormEvents.Submit = async () =&gt; {
123 123 </ContentWrap>
124 124 <vxe-modal
125 125 v-model="dialogVisible"
126   - id="myModal6"
  126 + id="postModel"
127 127 :title="dialogTitle"
128 128 width="800"
129 129 height="400"
... ...