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,7 +60,14 @@ VXETable.setup({
60 autoResize: true, // 自动监听父元素的变化去重新计算表格 60 autoResize: true, // 自动监听父元素的变化去重新计算表格
61 resizable: true, // 列是否允许拖动列宽调整大小 61 resizable: true, // 列是否允许拖动列宽调整大小
62 emptyText: '暂无数据', // 空表单 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 grid: { 72 grid: {
66 toolbarConfig: { 73 toolbarConfig: {
@@ -127,13 +134,28 @@ VXETable.setup({ @@ -127,13 +134,28 @@ VXETable.setup({
127 : XEUtils.toFormatString(XEUtils.get(enUS, key), args) 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 export const setupVxeTable = (app: App<Element>) => { 160 export const setupVxeTable = (app: App<Element>) => {
139 // 表格功能 161 // 表格功能
yudao-ui-admin-vue3/src/views/system/menu/index.vue
1 <script setup lang="ts"> 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 import * as MenuApi from '@/api/system/menu' 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 import { MenuVO } from '@/api/system/menu/types' 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 import { useMessage } from '@/hooks/web/useMessage' 5 import { useMessage } from '@/hooks/web/useMessage'
28 import { required } from '@/utils/formRules.js' 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 const { t } = useI18n() // 国际化 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 const actionLoading = ref(false) // 遮罩层 17 const actionLoading = ref(false) // 遮罩层
75 const actionType = ref('') // 操作按钮的类型 18 const actionType = ref('') // 操作按钮的类型
76 const dialogVisible = ref(false) // 是否显示弹出层 19 const dialogVisible = ref(false) // 是否显示弹出层
@@ -90,42 +33,46 @@ const menuForm = ref&lt;MenuVO&gt;({ @@ -90,42 +33,46 @@ const menuForm = ref&lt;MenuVO&gt;({
90 keepAlive: true, 33 keepAlive: true,
91 createTime: '' 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 const setDialogTile = (type: string) => { 48 const setDialogTile = (type: string) => {
102 dialogTitle.value = t('action.' + type) 49 dialogTitle.value = t('action.' + type)
103 actionType.value = type 50 actionType.value = type
104 dialogVisible.value = true 51 dialogVisible.value = true
105 } 52 }
106 -// 新建操作  
107 -const handleCreate = () => {  
108 - // 重置表单  
109 - setDialogTile('create')  
110 -}  
111 // 修改操作 53 // 修改操作
112 const handleUpdate = async (row: MenuVO) => { 54 const handleUpdate = async (row: MenuVO) => {
113 // 设置数据 55 // 设置数据
114 const res = await MenuApi.getMenuApi(row.id) 56 const res = await MenuApi.getMenuApi(row.id)
  57 + console.log(res)
115 menuForm.value = res 58 menuForm.value = res
116 setDialogTile('update') 59 setDialogTile('update')
117 } 60 }
118 // 删除操作 61 // 删除操作
119 const handleDelete = async (row: MenuVO) => { 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 const isExternal = (path: string) => { 77 const isExternal = (path: string) => {
131 return /^(https?:|mailto:|tel:)/.test(path) 78 return /^(https?:|mailto:|tel:)/.test(path)
@@ -162,248 +109,103 @@ const submitForm = async () =&gt; { @@ -162,248 +109,103 @@ const submitForm = async () =&gt; {
162 actionLoading.value = false 109 actionLoading.value = false
163 } 110 }
164 } 111 }
165 -// ========== 初始化 ==========  
166 onMounted(async () => { 112 onMounted(async () => {
167 await getList() 113 await getList()
168 - getTree()  
169 }) 114 })
170 </script> 115 </script>
171 <template> 116 <template>
172 - <!-- 搜索工作区 -->  
173 <ContentWrap> 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 </template> 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 </template> 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 </template> 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 v-hasPermi="['system:menu:update']" 162 v-hasPermi="['system:menu:update']"
245 - @click="handleUpdate(scope.row)" 163 + @click="handleUpdate(row)"
246 > 164 >
247 <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }} 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 v-hasPermi="['system:menu:delete']" 170 v-hasPermi="['system:menu:delete']"
253 - @click="handleDelete(scope.row)" 171 + @click="handleDelete(row)"
254 > 172 >
255 <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }} 173 <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
256 - </el-button> 174 + </vxe-button>
257 </template> 175 </template>
258 - </el-table-column>  
259 - </el-table> 176 + </vxe-column>
  177 + </vxe-table>
260 </ContentWrap> 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 v-if="['create', 'update'].includes(actionType)" 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 </template> 209 </template>
402 - </Dialog> 210 + </vxe-modal>
403 </template> 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,7 +123,7 @@ const submitForm: VxeFormEvents.Submit = async () =&gt; {
123 </ContentWrap> 123 </ContentWrap>
124 <vxe-modal 124 <vxe-modal
125 v-model="dialogVisible" 125 v-model="dialogVisible"
126 - id="myModal6" 126 + id="postModel"
127 :title="dialogTitle" 127 :title="dialogTitle"
128 width="800" 128 width="800"
129 height="400" 129 height="400"