<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>{{ t('pages.system.menu.buttons.add') }}</ElButton>
|
<ElButton @click="toggleExpand" v-ripple>
|
{{ isExpanded ? t('pages.system.menu.actions.collapse') : t('pages.system.menu.actions.expand') }}
|
</ElButton>
|
</template>
|
</ArtTableHeader>
|
|
<ArtTable
|
ref="tableRef"
|
rowKey="id"
|
: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"
|
:menuTreeOptions="menuTreeOptions"
|
@submit="handleSubmit"
|
/>
|
</ElCard>
|
</div>
|
</template>
|
|
<script setup>
|
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
import { useI18n } from 'vue-i18n'
|
import MenuDialog from './modules/menu-dialog.vue'
|
|
import { formatMenuTitle } from '@/utils/router'
|
import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
|
import { useTableColumns } from '@/hooks/core/useTableColumns'
|
import {
|
fetchDeleteMenu,
|
fetchGetMenuList,
|
fetchSaveMenu,
|
fetchUpdateMenu
|
} from '@/api/system-manage'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { createMenuTableColumns } from './menuTable.columns'
|
import {
|
buildMenuSubmitPayload,
|
buildMenuTreeOptions,
|
createMenuSearchState,
|
expandMenuAuthChildren,
|
filterMenuTree,
|
getMenuDisplayTitle
|
} from './menuPage.helpers'
|
|
defineOptions({ name: 'Menus' })
|
const { t } = useI18n()
|
|
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(true)
|
const tableData = ref([])
|
const menuTreeOptions = ref([])
|
|
const initialSearchState = createMenuSearchState()
|
|
const formFilters = reactive({ ...initialSearchState })
|
const appliedFilters = reactive({ ...initialSearchState })
|
|
const formItems = computed(() => [
|
{
|
label: t('pages.system.menu.search.name'),
|
key: 'name',
|
type: 'input',
|
props: { clearable: true }
|
},
|
{
|
label: t('pages.system.menu.search.route'),
|
key: 'route',
|
type: 'input',
|
props: { clearable: true }
|
}
|
])
|
|
const loadMenuResources = async () => {
|
loading.value = true
|
try {
|
const list = await guardRequestWithMessage(fetchGetMenuList({}), null, {
|
timeoutMessage: t('pages.system.menu.messages.loadTimeout')
|
})
|
if (list === null) {
|
tableData.value = []
|
menuTreeOptions.value = []
|
return
|
}
|
tableData.value = Array.isArray(list) ? list : []
|
menuTreeOptions.value = buildMenuTreeOptions(tableData.value, formatMenuTitle, t)
|
} catch (error) {
|
ElMessage.error(error?.message || t('pages.system.menu.messages.loadFailed'))
|
} finally {
|
loading.value = false
|
}
|
}
|
|
onMounted(() => {
|
loadMenuResources()
|
})
|
|
const { columnChecks, columns } = useTableColumns(() =>
|
createMenuTableColumns({
|
t,
|
titleFormatter: formatMenuTitle,
|
handleAddAuth,
|
handleEditAuth,
|
handleDeleteAuth,
|
handleEditMenu,
|
handleDeleteMenu
|
})
|
)
|
|
const filteredTableData = computed(() => {
|
const searchedData = filterMenuTree(tableData.value, appliedFilters, formatMenuTitle)
|
return expandMenuAuthChildren(searchedData)
|
})
|
|
function closeDialog() {
|
dialogVisible.value = false
|
editData.value = null
|
}
|
|
function handleAddMenu() {
|
dialogType.value = 'menu'
|
editData.value = null
|
lockMenuType.value = true
|
dialogVisible.value = true
|
}
|
|
function handleAddAuth(row) {
|
dialogType.value = 'button'
|
editData.value = {
|
parentId: row.id,
|
type: 1,
|
status: 1,
|
sort: 0
|
}
|
lockMenuType.value = true
|
dialogVisible.value = true
|
}
|
|
function handleEditMenu(row) {
|
dialogType.value = 'menu'
|
editData.value = row
|
lockMenuType.value = true
|
dialogVisible.value = true
|
}
|
|
function handleEditAuth(row) {
|
dialogType.value = 'button'
|
editData.value = row
|
lockMenuType.value = true
|
dialogVisible.value = true
|
}
|
|
async function handleSubmit(formData) {
|
const payload = buildMenuSubmitPayload(formData)
|
if (payload.id && payload.id === payload.parentId) {
|
ElMessage.error(t('pages.system.menu.messages.menuSelfParent'))
|
return
|
}
|
|
try {
|
if (payload.id) {
|
await fetchUpdateMenu(payload)
|
ElMessage.success(t('crud.messages.updateSuccess'))
|
} else {
|
await fetchSaveMenu(payload)
|
ElMessage.success(t('crud.messages.createSuccess'))
|
}
|
closeDialog()
|
await loadMenuResources()
|
} catch (error) {
|
ElMessage.error(error?.message || t('pages.system.menu.messages.submitFailed'))
|
}
|
}
|
|
async function handleDeleteMenu(row) {
|
try {
|
await ElMessageBox.confirm(
|
t('pages.system.menu.messages.deleteMenuConfirm', {
|
title: getMenuDisplayTitle(row, formatMenuTitle)
|
}),
|
t('crud.confirm.deleteTitle'),
|
{
|
confirmButtonText: t('common.confirm'),
|
cancelButtonText: t('common.cancel'),
|
type: 'warning'
|
}
|
)
|
await fetchDeleteMenu(row.id)
|
ElMessage.success(t('crud.messages.deleteSuccess'))
|
await loadMenuResources()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
|
}
|
}
|
}
|
|
async function handleDeleteAuth(row) {
|
try {
|
await ElMessageBox.confirm(
|
t('pages.system.menu.messages.deleteAuthConfirm', {
|
title: row.name || row.authority || row.id
|
}),
|
t('crud.confirm.deleteTitle'),
|
{
|
confirmButtonText: t('common.confirm'),
|
cancelButtonText: t('common.cancel'),
|
type: 'warning'
|
}
|
)
|
await fetchDeleteMenu(row.id)
|
ElMessage.success(t('crud.messages.deleteSuccess'))
|
await loadMenuResources()
|
} catch (error) {
|
if (error !== 'cancel') {
|
ElMessage.error(error?.message || t('crud.messages.deleteFailed'))
|
}
|
}
|
}
|
|
function handleReset() {
|
Object.assign(formFilters, { ...initialSearchState })
|
Object.assign(appliedFilters, { ...initialSearchState })
|
loadMenuResources()
|
}
|
|
function handleSearch() {
|
Object.assign(appliedFilters, { ...formFilters })
|
}
|
|
function handleRefresh() {
|
loadMenuResources()
|
}
|
|
function 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>
|