<!-- 菜单管理页面 -->
|
<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">
|
<!-- 表格头部 -->
|
<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>
|
import MenuDialog from './modules/menu-dialog.vue'
|
|
import { formatMenuTitle } from '@/utils/router'
|
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
|
import { useTableColumns } from '@/hooks/core/useTableColumns'
|
import { fetchGetMenuList } from '@/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')
|
const editData = ref(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 () => {
|
loading.value = true
|
try {
|
const list = await fetchGetMenuList()
|
tableData.value = list
|
} catch (error) {
|
throw error instanceof Error ? error : new Error('获取菜单失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
const getMenuTypeTag = (row) => {
|
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'
|
}
|
const getMenuTypeText = (row) => {
|
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) => formatMenuTitle(row.meta?.title)
|
},
|
{
|
prop: 'type',
|
label: '菜单类型',
|
formatter: (row) => {
|
return h(ElTag, { type: getMenuTypeTag(row) }, () => getMenuTypeText(row))
|
}
|
},
|
{
|
prop: 'path',
|
label: '路由',
|
formatter: (row) => {
|
if (row.meta?.isAuthButton) return ''
|
return row.meta?.link || row.path || ''
|
}
|
},
|
{
|
prop: 'meta.authList',
|
label: '权限标识',
|
formatter: (row) => {
|
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) => {
|
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([])
|
const handleReset = () => {
|
Object.assign(formFilters, { ...initialSearchState })
|
Object.assign(appliedFilters, { ...initialSearchState })
|
getMenuList()
|
}
|
const handleSearch = () => {
|
Object.assign(appliedFilters, { ...formFilters })
|
getMenuList()
|
}
|
const handleRefresh = () => {
|
getMenuList()
|
}
|
const deepClone = (obj) => {
|
if (obj === null || typeof obj !== 'object') return obj
|
if (obj instanceof Date) return new Date(obj)
|
if (Array.isArray(obj)) return obj.map((item) => deepClone(item))
|
const cloned = {}
|
for (const key in obj) {
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
cloned[key] = deepClone(obj[key])
|
}
|
}
|
return cloned
|
}
|
const convertAuthListToChildren = (items) => {
|
return items.map((item) => {
|
const clonedItem = deepClone(item)
|
if (clonedItem.children?.length) {
|
clonedItem.children = convertAuthListToChildren(clonedItem.children)
|
}
|
if (item.meta?.authList?.length) {
|
const authChildren = item.meta.authList.map((auth) => ({
|
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
|
})
|
}
|
const searchMenu = (items) => {
|
const results = []
|
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 = () => {
|
dialogType.value = 'menu'
|
editData.value = null
|
lockMenuType.value = true
|
dialogVisible.value = true
|
}
|
const handleAddAuth = () => {
|
dialogType.value = 'menu'
|
editData.value = null
|
lockMenuType.value = false
|
dialogVisible.value = true
|
}
|
const handleEditMenu = (row) => {
|
dialogType.value = 'menu'
|
editData.value = row
|
lockMenuType.value = true
|
dialogVisible.value = true
|
}
|
const handleEditAuth = (row) => {
|
dialogType.value = 'button'
|
editData.value = {
|
title: row.meta?.title,
|
authMark: row.meta?.authMark
|
}
|
lockMenuType.value = false
|
dialogVisible.value = true
|
}
|
const handleSubmit = (formData) => {
|
console.log('提交数据:', formData)
|
getMenuList()
|
}
|
const handleDeleteMenu = async () => {
|
try {
|
await ElMessageBox.confirm('确定要删除该菜单吗?删除后无法恢复', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
ElMessage.success('删除成功')
|
getMenuList()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error('删除失败')
|
}
|
}
|
}
|
const handleDeleteAuth = async () => {
|
try {
|
await ElMessageBox.confirm('确定要删除该权限吗?删除后无法恢复', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
ElMessage.success('删除成功')
|
getMenuList()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error('删除失败')
|
}
|
}
|
}
|
const toggleExpand = () => {
|
isExpanded.value = !isExpanded.value
|
nextTick(() => {
|
if (tableRef.value?.elTableRef && filteredTableData.value) {
|
const processRows = (rows) => {
|
rows.forEach((row) => {
|
if (row.children?.length) {
|
tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)
|
processRows(row.children)
|
}
|
})
|
}
|
processRows(filteredTableData.value)
|
}
|
})
|
}
|
</script>
|