初始化本地仓库
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
<!-- 菜单管理页面 -->
|
||||
<template>
|
||||
<div class="menu-page art-full-height">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model="formFilters"
|
||||
:items="formItems"
|
||||
:showExpand="false"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<ElCard class="art-table-card" shadow="never">
|
||||
<!-- 表格头部 -->
|
||||
<ArtTableHeader
|
||||
:showZebra="false"
|
||||
:loading="loading"
|
||||
v-model:columns="columnChecks"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<template #left>
|
||||
<ElButton v-auth="'add'" @click="handleAddMenu" v-ripple> 添加菜单 </ElButton>
|
||||
<ElButton @click="toggleExpand" v-ripple>
|
||||
{{ isExpanded ? '收起' : '展开' }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ArtTableHeader>
|
||||
|
||||
<ArtTable
|
||||
ref="tableRef"
|
||||
rowKey="path"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="filteredTableData"
|
||||
:stripe="false"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="false"
|
||||
/>
|
||||
|
||||
<!-- 菜单弹窗 -->
|
||||
<MenuDialog
|
||||
v-model:visible="dialogVisible"
|
||||
:type="dialogType"
|
||||
:editData="editData"
|
||||
:lockType="lockMenuType"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatMenuTitle } from '@/utils/router'
|
||||
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
|
||||
import { useTableColumns } from '@/hooks/core/useTableColumns'
|
||||
import type { AppRouteRecord } from '@/types/router'
|
||||
import MenuDialog from './modules/menu-dialog.vue'
|
||||
import { fetchGetMenuList } from '@/apps/demo/api/system-manage'
|
||||
import { ElTag, ElMessageBox } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'Menus' })
|
||||
|
||||
// 状态管理
|
||||
const loading = ref(false)
|
||||
const isExpanded = ref(false)
|
||||
const tableRef = ref()
|
||||
|
||||
// 弹窗相关
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref<'menu' | 'button'>('menu')
|
||||
const editData = ref<AppRouteRecord | any>(null)
|
||||
const lockMenuType = ref(false)
|
||||
|
||||
// 搜索相关
|
||||
const initialSearchState = {
|
||||
name: '',
|
||||
route: ''
|
||||
}
|
||||
|
||||
const formFilters = reactive({ ...initialSearchState })
|
||||
const appliedFilters = reactive({ ...initialSearchState })
|
||||
|
||||
const formItems = computed(() => [
|
||||
{
|
||||
label: '菜单名称',
|
||||
key: 'name',
|
||||
type: 'input',
|
||||
props: { clearable: true }
|
||||
},
|
||||
{
|
||||
label: '路由地址',
|
||||
key: 'route',
|
||||
type: 'input',
|
||||
props: { clearable: true }
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
getMenuList()
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取菜单列表数据
|
||||
*/
|
||||
const getMenuList = async (): Promise<void> => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const list = await fetchGetMenuList()
|
||||
tableData.value = list
|
||||
} catch (error) {
|
||||
throw error instanceof Error ? error : new Error('获取菜单失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单类型标签颜色
|
||||
* @param row 菜单行数据
|
||||
* @returns 标签颜色类型
|
||||
*/
|
||||
const getMenuTypeTag = (
|
||||
row: AppRouteRecord
|
||||
): 'primary' | 'success' | 'warning' | 'info' | 'danger' => {
|
||||
if (row.meta?.isAuthButton) return 'danger'
|
||||
if (row.children?.length) return 'info'
|
||||
if (row.meta?.link && row.meta?.isIframe) return 'success'
|
||||
if (row.path) return 'primary'
|
||||
if (row.meta?.link) return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单类型文本
|
||||
* @param row 菜单行数据
|
||||
* @returns 菜单类型文本
|
||||
*/
|
||||
const getMenuTypeText = (row: AppRouteRecord): string => {
|
||||
if (row.meta?.isAuthButton) return '按钮'
|
||||
if (row.children?.length) return '目录'
|
||||
if (row.meta?.link && row.meta?.isIframe) return '内嵌'
|
||||
if (row.path) return '菜单'
|
||||
if (row.meta?.link) return '外链'
|
||||
return '未知'
|
||||
}
|
||||
|
||||
// 表格列配置
|
||||
const { columnChecks, columns } = useTableColumns(() => [
|
||||
{
|
||||
prop: 'meta.title',
|
||||
label: '菜单名称',
|
||||
minWidth: 120,
|
||||
formatter: (row: AppRouteRecord) => formatMenuTitle(row.meta?.title)
|
||||
},
|
||||
{
|
||||
prop: 'type',
|
||||
label: '菜单类型',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
return h(ElTag, { type: getMenuTypeTag(row) }, () => getMenuTypeText(row))
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'path',
|
||||
label: '路由',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
if (row.meta?.isAuthButton) return ''
|
||||
return row.meta?.link || row.path || ''
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'meta.authList',
|
||||
label: '权限标识',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
if (row.meta?.isAuthButton) {
|
||||
return row.meta?.authMark || ''
|
||||
}
|
||||
if (!row.meta?.authList?.length) return ''
|
||||
return `${row.meta.authList.length} 个权限标识`
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'date',
|
||||
label: '编辑时间',
|
||||
formatter: () => '2022-3-12 12:00:00'
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
formatter: () => h(ElTag, { type: 'success' }, () => '启用')
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 180,
|
||||
align: 'right',
|
||||
formatter: (row: AppRouteRecord) => {
|
||||
const buttonStyle = { style: 'text-align: right' }
|
||||
|
||||
if (row.meta?.isAuthButton) {
|
||||
return h('div', buttonStyle, [
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => handleEditAuth(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => handleDeleteAuth()
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
return h('div', buttonStyle, [
|
||||
h(ArtButtonTable, {
|
||||
type: 'add',
|
||||
onClick: () => handleAddAuth(),
|
||||
title: '新增权限'
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => handleEditMenu(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => handleDeleteMenu()
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 数据相关
|
||||
const tableData = ref<AppRouteRecord[]>([])
|
||||
|
||||
/**
|
||||
* 重置搜索条件
|
||||
*/
|
||||
const handleReset = (): void => {
|
||||
Object.assign(formFilters, { ...initialSearchState })
|
||||
Object.assign(appliedFilters, { ...initialSearchState })
|
||||
getMenuList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行搜索
|
||||
*/
|
||||
const handleSearch = (): void => {
|
||||
Object.assign(appliedFilters, { ...formFilters })
|
||||
getMenuList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新菜单列表
|
||||
*/
|
||||
const handleRefresh = (): void => {
|
||||
getMenuList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度克隆对象
|
||||
* @param obj 要克隆的对象
|
||||
* @returns 克隆后的对象
|
||||
*/
|
||||
const deepClone = <T,>(obj: T): T => {
|
||||
if (obj === null || typeof obj !== 'object') return obj
|
||||
if (obj instanceof Date) return new Date(obj) as T
|
||||
if (Array.isArray(obj)) return obj.map((item) => deepClone(item)) as T
|
||||
|
||||
const cloned = {} as T
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
cloned[key] = deepClone(obj[key])
|
||||
}
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
/**
|
||||
* 将权限列表转换为子节点
|
||||
* @param items 菜单项数组
|
||||
* @returns 转换后的菜单项数组
|
||||
*/
|
||||
const convertAuthListToChildren = (items: AppRouteRecord[]): AppRouteRecord[] => {
|
||||
return items.map((item) => {
|
||||
const clonedItem = deepClone(item)
|
||||
|
||||
if (clonedItem.children?.length) {
|
||||
clonedItem.children = convertAuthListToChildren(clonedItem.children)
|
||||
}
|
||||
|
||||
if (item.meta?.authList?.length) {
|
||||
const authChildren: AppRouteRecord[] = item.meta.authList.map(
|
||||
(auth: { title: string; authMark: string }) => ({
|
||||
path: `${item.path}_auth_${auth.authMark}`,
|
||||
name: `${String(item.name)}_auth_${auth.authMark}`,
|
||||
meta: {
|
||||
title: auth.title,
|
||||
authMark: auth.authMark,
|
||||
isAuthButton: true,
|
||||
parentPath: item.path
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
clonedItem.children = clonedItem.children?.length
|
||||
? [...clonedItem.children, ...authChildren]
|
||||
: authChildren
|
||||
}
|
||||
|
||||
return clonedItem
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索菜单
|
||||
* @param items 菜单项数组
|
||||
* @returns 搜索结果数组
|
||||
*/
|
||||
const searchMenu = (items: AppRouteRecord[]): AppRouteRecord[] => {
|
||||
const results: AppRouteRecord[] = []
|
||||
|
||||
for (const item of items) {
|
||||
const searchName = appliedFilters.name?.toLowerCase().trim() || ''
|
||||
const searchRoute = appliedFilters.route?.toLowerCase().trim() || ''
|
||||
const menuTitle = formatMenuTitle(item.meta?.title || '').toLowerCase()
|
||||
const menuPath = (item.path || '').toLowerCase()
|
||||
const nameMatch = !searchName || menuTitle.includes(searchName)
|
||||
const routeMatch = !searchRoute || menuPath.includes(searchRoute)
|
||||
|
||||
if (item.children?.length) {
|
||||
const matchedChildren = searchMenu(item.children)
|
||||
if (matchedChildren.length > 0) {
|
||||
const clonedItem = deepClone(item)
|
||||
clonedItem.children = matchedChildren
|
||||
results.push(clonedItem)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (nameMatch && routeMatch) {
|
||||
results.push(deepClone(item))
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// 过滤后的表格数据
|
||||
const filteredTableData = computed(() => {
|
||||
const searchedData = searchMenu(tableData.value)
|
||||
return convertAuthListToChildren(searchedData)
|
||||
})
|
||||
|
||||
/**
|
||||
* 添加菜单
|
||||
*/
|
||||
const handleAddMenu = (): void => {
|
||||
dialogType.value = 'menu'
|
||||
editData.value = null
|
||||
lockMenuType.value = true
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加权限按钮
|
||||
*/
|
||||
const handleAddAuth = (): void => {
|
||||
dialogType.value = 'menu'
|
||||
editData.value = null
|
||||
lockMenuType.value = false
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑菜单
|
||||
* @param row 菜单行数据
|
||||
*/
|
||||
const handleEditMenu = (row: AppRouteRecord): void => {
|
||||
dialogType.value = 'menu'
|
||||
editData.value = row
|
||||
lockMenuType.value = true
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑权限按钮
|
||||
* @param row 权限行数据
|
||||
*/
|
||||
const handleEditAuth = (row: AppRouteRecord): void => {
|
||||
dialogType.value = 'button'
|
||||
editData.value = {
|
||||
title: row.meta?.title,
|
||||
authMark: row.meta?.authMark
|
||||
}
|
||||
lockMenuType.value = false
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单表单数据类型
|
||||
*/
|
||||
interface MenuFormData {
|
||||
name: string
|
||||
path: string
|
||||
component?: string
|
||||
icon?: string
|
||||
roles?: string[]
|
||||
sort?: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交表单数据
|
||||
* @param formData 表单数据
|
||||
*/
|
||||
const handleSubmit = (formData: MenuFormData): void => {
|
||||
console.log('提交数据:', formData)
|
||||
// TODO: 调用API保存数据
|
||||
getMenuList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*/
|
||||
const handleDeleteMenu = async (): Promise<void> => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该菜单吗?删除后无法恢复', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
ElMessage.success('删除成功')
|
||||
getMenuList()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限按钮
|
||||
*/
|
||||
const handleDeleteAuth = async (): Promise<void> => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该权限吗?删除后无法恢复', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
ElMessage.success('删除成功')
|
||||
getMenuList()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换展开/收起所有菜单
|
||||
*/
|
||||
const toggleExpand = (): void => {
|
||||
isExpanded.value = !isExpanded.value
|
||||
nextTick(() => {
|
||||
if (tableRef.value?.elTableRef && filteredTableData.value) {
|
||||
const processRows = (rows: AppRouteRecord[]) => {
|
||||
rows.forEach((row) => {
|
||||
if (row.children?.length) {
|
||||
tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)
|
||||
processRows(row.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
processRows(filteredTableData.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
<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>
|
||||
<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'
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user