| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- <template>
- <ElDialog
- :title="dialogTitle"
- :model-value="visible"
- @update:model-value="handleCancel"
- width="860px"
- align-center
- class="menu-dialog"
- @closed="handleClosed"
- >
- <ArtForm
- ref="formRef"
- v-model="form"
- :items="formItems"
- :rules="rules"
- :span="width > 640 ? 12 : 24"
- :gutter="20"
- label-width="100px"
- :show-reset="false"
- :show-submit="false"
- >
- <template #menuType>
- <ElRadioGroup v-model="form.menuType" :disabled="disableMenuType">
- <ElRadioButton value="menu" label="menu">菜单</ElRadioButton>
- <ElRadioButton value="button" label="button">按钮</ElRadioButton>
- </ElRadioGroup>
- </template>
- </ArtForm>
- <template #footer>
- <div class="dialog-footer">
- <ElButton class="btn-ghost" @click="handleCancel">取消</ElButton>
- <ElButton class="btn-main" type="primary" @click="handleSubmit">确定</ElButton>
- </div>
- </template>
- </ElDialog>
- </template>
- <script setup lang="ts">
- import type { FormRules } from 'element-plus'
- import { ElIcon, ElTooltip } from 'element-plus'
- import { QuestionFilled } from '@element-plus/icons-vue'
- import { formatMenuTitle } from '@/utils/router'
- import type { AppRouteRecord } from '@/types/router'
- import type { FormItem } from '@/components/core/forms/art-form/index.vue'
- import ArtForm from '@/components/core/forms/art-form/index.vue'
- import { useWindowSize } from '@vueuse/core'
- const { width } = useWindowSize()
- /**
- * 创建带 tooltip 的表单标签
- * @param label 标签文本
- * @param tooltip 提示文本
- * @returns 渲染函数
- */
- const createLabelTooltip = (label: string, tooltip: string) => {
- return () =>
- h('span', { class: 'flex items-center' }, [
- h('span', label),
- h(
- ElTooltip,
- {
- content: tooltip,
- placement: 'top'
- },
- () => h(ElIcon, { class: 'ml-0.5 cursor-help' }, () => h(QuestionFilled))
- )
- ])
- }
- interface MenuFormData {
- id: number
- name: string
- path: string
- label: string
- component: string
- icon: string
- isEnable: boolean
- sort: number
- isMenu: boolean
- keepAlive: boolean
- isHide: boolean
- isHideTab: boolean
- link: string
- isIframe: boolean
- showBadge: boolean
- showTextBadge: string
- fixedTab: boolean
- activePath: string
- roles: string[]
- isFullPage: boolean
- authName: string
- authLabel: string
- authIcon: string
- authSort: number
- }
- interface Props {
- visible: boolean
- editData?: AppRouteRecord | any
- type?: 'menu' | 'button'
- lockType?: boolean
- }
- interface Emits {
- (e: 'update:visible', value: boolean): void
- (e: 'submit', data: MenuFormData): void
- }
- const props = withDefaults(defineProps<Props>(), {
- visible: false,
- type: 'menu',
- lockType: false
- })
- const emit = defineEmits<Emits>()
- const formRef = ref()
- const isEdit = ref(false)
- const form = reactive<MenuFormData & { menuType: 'menu' | 'button' }>({
- menuType: 'menu',
- id: 0,
- name: '',
- path: '',
- label: '',
- component: '',
- icon: '',
- isEnable: true,
- sort: 1,
- isMenu: true,
- keepAlive: true,
- isHide: false,
- isHideTab: false,
- link: '',
- isIframe: false,
- showBadge: false,
- showTextBadge: '',
- fixedTab: false,
- activePath: '',
- roles: [],
- isFullPage: false,
- authName: '',
- authLabel: '',
- authIcon: '',
- authSort: 1
- })
- const rules = reactive<FormRules>({
- name: [
- { required: true, message: '请输入菜单名称', trigger: 'blur' },
- { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
- ],
- path: [{ required: true, message: '请输入路由地址', trigger: 'blur' }],
- label: [{ required: true, message: '输入权限标识', trigger: 'blur' }],
- authName: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
- authLabel: [{ required: true, message: '请输入权限标识', trigger: 'blur' }]
- })
- /**
- * 表单项配置
- */
- const formItems = computed<FormItem[]>(() => {
- const baseItems: FormItem[] = [{ label: '菜单类型', key: 'menuType', span: 24 }]
- // Switch 组件的 span:小屏幕 12,大屏幕 6
- const switchSpan = width.value < 640 ? 12 : 6
- if (form.menuType === 'menu') {
- return [
- ...baseItems,
- { label: '菜单名称', key: 'name', type: 'input', props: { placeholder: '菜单名称' } },
- {
- label: createLabelTooltip(
- '路由地址',
- '一级菜单:以 / 开头的绝对路径(如 /dashboard)\n二级及以下:相对路径(如 console、user)'
- ),
- key: 'path',
- type: 'input',
- props: { placeholder: '如:/dashboard 或 console' }
- },
- { label: '权限标识', key: 'label', type: 'input', props: { placeholder: '如:User' } },
- {
- label: createLabelTooltip(
- '组件路径',
- '一级父级菜单:填写 /index/index\n具体页面:填写组件路径(如 /system/user)\n目录菜单:留空'
- ),
- key: 'component',
- type: 'input',
- props: { placeholder: '如:/system/user 或留空' }
- },
- { label: '图标', key: 'icon', type: 'input', props: { placeholder: '如:ri:user-line' } },
- {
- label: createLabelTooltip(
- '角色权限',
- '仅用于前端权限模式:配置角色标识(如 R_SUPER、R_ADMIN)\n后端权限模式:无需配置'
- ),
- key: 'roles',
- type: 'inputtag',
- props: { placeholder: '输入角色标识后按回车,如:R_SUPER' }
- },
- {
- label: '菜单排序',
- key: 'sort',
- type: 'number',
- props: { min: 1, controlsPosition: 'right', style: { width: '100%' } }
- },
- {
- label: '外部链接',
- key: 'link',
- type: 'input',
- props: { placeholder: '如:https://www.example.com' }
- },
- {
- label: '文本徽章',
- key: 'showTextBadge',
- type: 'input',
- props: { placeholder: '如:New、Hot' }
- },
- {
- label: createLabelTooltip(
- '激活路径',
- '用于详情页等隐藏菜单,指定高亮显示的父级菜单路径\n例如:用户详情页高亮显示"用户管理"菜单'
- ),
- key: 'activePath',
- type: 'input',
- props: { placeholder: '如:/system/user' }
- },
- { label: '是否启用', key: 'isEnable', type: 'switch', span: switchSpan },
- { label: '页面缓存', key: 'keepAlive', type: 'switch', span: switchSpan },
- { label: '隐藏菜单', key: 'isHide', type: 'switch', span: switchSpan },
- { label: '是否内嵌', key: 'isIframe', type: 'switch', span: switchSpan },
- { label: '显示徽章', key: 'showBadge', type: 'switch', span: switchSpan },
- { label: '固定标签', key: 'fixedTab', type: 'switch', span: switchSpan },
- { label: '标签隐藏', key: 'isHideTab', type: 'switch', span: switchSpan },
- { label: '全屏页面', key: 'isFullPage', type: 'switch', span: switchSpan }
- ]
- } else {
- return [
- ...baseItems,
- {
- label: '权限名称',
- key: 'authName',
- type: 'input',
- props: { placeholder: '如:新增、编辑、删除' }
- },
- {
- label: '权限标识',
- key: 'authLabel',
- type: 'input',
- props: { placeholder: '如:add、edit、delete' }
- },
- {
- label: '权限排序',
- key: 'authSort',
- type: 'number',
- props: { min: 1, controlsPosition: 'right', style: { width: '100%' } }
- }
- ]
- }
- })
- const dialogTitle = computed(() => {
- const type = form.menuType === 'menu' ? '菜单' : '按钮'
- return isEdit.value ? `编辑${type}` : `新建${type}`
- })
- /**
- * 是否禁用菜单类型切换
- */
- const disableMenuType = computed(() => {
- if (isEdit.value) return true
- if (!isEdit.value && form.menuType === 'menu' && props.lockType) return true
- return false
- })
- /**
- * 重置表单数据
- */
- const resetForm = (): void => {
- formRef.value?.reset()
- form.menuType = 'menu'
- }
- /**
- * 加载表单数据(编辑模式)
- */
- const loadFormData = (): void => {
- if (!props.editData) return
- isEdit.value = true
- if (form.menuType === 'menu') {
- const row = props.editData
- form.id = row.id || 0
- form.name = formatMenuTitle(row.meta?.title || '')
- form.path = row.path || ''
- form.label = row.name || ''
- form.component = row.component || ''
- form.icon = row.meta?.icon || ''
- form.sort = row.meta?.sort || 1
- form.isMenu = row.meta?.isMenu ?? true
- form.keepAlive = row.meta?.keepAlive ?? false
- form.isHide = row.meta?.isHide ?? false
- form.isHideTab = row.meta?.isHideTab ?? false
- form.isEnable = row.meta?.isEnable ?? true
- form.link = row.meta?.link || ''
- form.isIframe = row.meta?.isIframe ?? false
- form.showBadge = row.meta?.showBadge ?? false
- form.showTextBadge = row.meta?.showTextBadge || ''
- form.fixedTab = row.meta?.fixedTab ?? false
- form.activePath = row.meta?.activePath || ''
- form.roles = row.meta?.roles || []
- form.isFullPage = row.meta?.isFullPage ?? false
- } else {
- const row = props.editData
- form.authName = row.title || ''
- form.authLabel = row.authMark || ''
- form.authIcon = row.icon || ''
- form.authSort = row.sort || 1
- }
- }
- /**
- * 提交表单
- */
- const handleSubmit = async (): Promise<void> => {
- if (!formRef.value) return
- try {
- await formRef.value.validate()
- emit('submit', { ...form })
- ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功`)
- handleCancel()
- } catch {
- ElMessage.error('表单校验失败,请检查输入')
- }
- }
- /**
- * 取消操作
- */
- const handleCancel = (): void => {
- emit('update:visible', false)
- }
- /**
- * 对话框关闭后的回调
- */
- const handleClosed = (): void => {
- resetForm()
- isEdit.value = false
- }
- /**
- * 监听对话框显示状态
- */
- watch(
- () => props.visible,
- (newVal) => {
- if (newVal) {
- form.menuType = props.type
- nextTick(() => {
- if (props.editData) {
- loadFormData()
- }
- })
- }
- }
- )
- /**
- * 监听菜单类型变化
- */
- watch(
- () => props.type,
- (newType) => {
- if (props.visible) {
- form.menuType = newType
- }
- }
- )
- </script>
- <style scoped lang="scss">
- .dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 8px;
- }
- </style>
|