格式化文档

This commit is contained in:
2026-01-10 10:10:57 +08:00
parent 1cc427cbb0
commit a0afedf5f3
224 changed files with 27320 additions and 26980 deletions
+368 -353
View File
@@ -1,384 +1,399 @@
<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>
<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>
<span class="dialog-footer">
<ElButton @click="handleCancel"> </ElButton>
<ElButton type="primary" @click="handleSubmit"> </ElButton>
</span>
</template>
</ElDialog>
<template #footer>
<span class="dialog-footer">
<ElButton @click="handleCancel"> </ElButton>
<ElButton type="primary" @click="handleSubmit"> </ElButton>
</span>
</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'
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()
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))
)
])
}
/**
* 创建带 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 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 Props {
visible: boolean
editData?: AppRouteRecord | any
type?: 'menu' | 'button'
lockType?: boolean
}
interface Emits {
(e: 'update:visible', value: boolean): void
(e: 'submit', data: MenuFormData): void
}
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 props = withDefaults(defineProps<Props>(), {
visible: false,
type: 'menu',
lockType: false
})
const emit = defineEmits<Emits>()
const emit = defineEmits<Emits>()
const formRef = ref()
const isEdit = ref(false)
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 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 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 }]
/**
* 表单项配置
*/
const formItems = computed<FormItem[]>(() => {
const baseItems: FormItem[] = [{ label: '菜单类型', key: 'menuType', span: 24 }]
// Switch 组件的 span:小屏幕 12,大屏幕 6
const switchSpan = width.value < 640 ? 12 : 6
// 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%' } }
}
]
}
})
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 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 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 resetForm = (): void => {
formRef.value?.reset()
form.menuType = 'menu'
}
/**
* 加载表单数据(编辑模式)
*/
const loadFormData = (): void => {
if (!props.editData) return
/**
* 加载表单数据(编辑模式)
*/
const loadFormData = (): void => {
if (!props.editData) return
isEdit.value = true
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
}
}
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
/**
* 提交表单
*/
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('表单校验失败,请检查输入')
}
}
try {
await formRef.value.validate()
emit('submit', { ...form })
ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功`)
handleCancel()
} catch {
ElMessage.error('表单校验失败,请检查输入')
}
}
/**
* 取消操作
*/
const handleCancel = (): void => {
emit('update:visible', false)
}
/**
* 取消操作
*/
const handleCancel = (): void => {
emit('update:visible', false)
}
/**
* 对话框关闭后的回调
*/
const handleClosed = (): void => {
resetForm()
isEdit.value = 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.visible,
(newVal) => {
if (newVal) {
form.menuType = props.type
nextTick(() => {
if (props.editData) {
loadFormData()
}
})
}
}
)
/**
* 监听菜单类型变化
*/
watch(
() => props.type,
(newType) => {
if (props.visible) {
form.menuType = newType
}
}
)
/**
* 监听菜单类型变化
*/
watch(
() => props.type,
(newType) => {
if (props.visible) {
form.menuType = newType
}
}
)
</script>