Commit eb5870f35e66cc71e6927b2d9c70cdb0d8c238cd

Authored by xingyu4j
1 parent fc8a2335

feat: menu vxe table

yudao-ui-admin-vue3/src/views/system/menu/indexd.vue 0 → 100644
  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'
  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'
  25 +import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
  26 +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  27 +import { useMessage } from '@/hooks/web/useMessage'
  28 +import { required } from '@/utils/formRules.js'
  29 +const message = useMessage()
  30 +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 相关 ==========
  74 +const actionLoading = ref(false) // 遮罩层
  75 +const actionType = ref('') // 操作按钮的类型
  76 +const dialogVisible = ref(false) // 是否显示弹出层
  77 +const dialogTitle = ref('edit') // 弹出层标题
  78 +const menuForm = ref<MenuVO>({
  79 + id: 0,
  80 + name: '',
  81 + permission: '',
  82 + type: SystemMenuTypeEnum.DIR,
  83 + sort: 1,
  84 + parentId: 0,
  85 + path: '',
  86 + icon: '',
  87 + component: '',
  88 + status: CommonStatusEnum.ENABLE,
  89 + visible: true,
  90 + keepAlive: true,
  91 + createTime: ''
  92 +})
  93 +// 表单校验
  94 +const rules = reactive({
  95 + name: [required],
  96 + sort: [required],
  97 + path: [required],
  98 + status: [required]
  99 +})
  100 +// 设置标题
  101 +const setDialogTile = (type: string) => {
  102 + dialogTitle.value = t('action.' + type)
  103 + actionType.value = type
  104 + dialogVisible.value = true
  105 +}
  106 +// 新建操作
  107 +const handleCreate = () => {
  108 + // 重置表单
  109 + setDialogTile('create')
  110 +}
  111 +// 修改操作
  112 +const handleUpdate = async (row: MenuVO) => {
  113 + // 设置数据
  114 + const res = await MenuApi.getMenuApi(row.id)
  115 + menuForm.value = res
  116 + setDialogTile('update')
  117 +}
  118 +// 删除操作
  119 +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()
  128 +}
  129 +// 保存操作
  130 +const isExternal = (path: string) => {
  131 + return /^(https?:|mailto:|tel:)/.test(path)
  132 +}
  133 +const submitForm = async () => {
  134 + actionLoading.value = true
  135 + // 提交请求
  136 + try {
  137 + if (
  138 + menuForm.value.type === SystemMenuTypeEnum.DIR ||
  139 + menuForm.value.type === SystemMenuTypeEnum.MENU
  140 + ) {
  141 + if (!isExternal(menuForm.value.path)) {
  142 + if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
  143 + message.error('路径必须以 / 开头')
  144 + return
  145 + } else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
  146 + message.error('路径不能以 / 开头')
  147 + return
  148 + }
  149 + }
  150 + }
  151 + if (actionType.value === 'create') {
  152 + await MenuApi.createMenuApi(menuForm.value)
  153 + message.success(t('common.createSuccess'))
  154 + } else {
  155 + await MenuApi.updateMenuApi(menuForm.value)
  156 + message.success(t('common.updateSuccess'))
  157 + }
  158 + // 操作成功,重新加载列表
  159 + dialogVisible.value = false
  160 + await getList()
  161 + } finally {
  162 + actionLoading.value = false
  163 + }
  164 +}
  165 +// ========== 初始化 ==========
  166 +onMounted(async () => {
  167 + await getList()
  168 + getTree()
  169 +})
  170 +</script>
  171 +<template>
  172 + <!-- 搜索工作区 -->
  173 + <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' }"
  213 + >
  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" />
  223 + </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" />
  232 + </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>
  237 + </template>
  238 + </el-table-column>
  239 + <el-table-column label="操作">
  240 + <template #default="scope">
  241 + <el-button
  242 + link
  243 + type="primary"
  244 + v-hasPermi="['system:menu:update']"
  245 + @click="handleUpdate(scope.row)"
  246 + >
  247 + <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
  248 + </el-button>
  249 + <el-button
  250 + link
  251 + type="primary"
  252 + v-hasPermi="['system:menu:delete']"
  253 + @click="handleDelete(scope.row)"
  254 + >
  255 + <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
  256 + </el-button>
  257 + </template>
  258 + </el-table-column>
  259 + </el-table>
  260 + </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
  393 + v-if="['create', 'update'].includes(actionType)"
  394 + type="primary"
  395 + :loading="actionLoading"
  396 + @click="submitForm"
  397 + >
  398 + {{ t('action.save') }}
  399 + </el-button>
  400 + <el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
  401 + </template>
  402 + </Dialog>
  403 +</template>
  404 +<style lang="less" scoped>
  405 +:deep(.el-button.is-text) {
  406 + margin-left: 0;
  407 + padding: 8px 10px;
  408 +}
  409 +</style>
... ...