2025-08-13 19:27:49 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<ArtTableFullScreen>
|
|
|
|
|
|
<div class="menu-page" id="table-full-screen">
|
|
|
|
|
|
<!-- 搜索栏 -->
|
|
|
|
|
|
<ArtSearchBar
|
|
|
|
|
|
v-model:filter="formFilters"
|
|
|
|
|
|
:items="formItems"
|
|
|
|
|
|
:showExpand="false"
|
|
|
|
|
|
@reset="handleReset"
|
|
|
|
|
|
@search="handleSearch"
|
|
|
|
|
|
></ArtSearchBar>
|
|
|
|
|
|
|
|
|
|
|
|
<ElCard shadow="never" class="art-table-card">
|
|
|
|
|
|
<!-- 表格头部 -->
|
|
|
|
|
|
<ArtTableHeader :showZebra="false" v-model:columns="columnChecks" @refresh="handleRefresh">
|
|
|
|
|
|
<template #left>
|
2025-08-15 16:31:55 +08:00
|
|
|
|
<el-segmented
|
|
|
|
|
|
v-model="yearVal"
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
:options="segmentedOptions"
|
|
|
|
|
|
@change="searchArticleByYear"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- <template #default="scope">
|
|
|
|
|
|
<div class="flex flex-col items-center gap-2 p-2">
|
|
|
|
|
|
<div>{{ scope.item.label }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template> -->
|
|
|
|
|
|
</el-segmented>
|
2025-08-13 19:27:49 +08:00
|
|
|
|
<!-- 按钮权限:后端控制模式,使用自定义指令 -->
|
2025-08-19 16:18:15 +08:00
|
|
|
|
<!-- <ElButton
|
2025-08-15 16:31:55 +08:00
|
|
|
|
v-auth="'system@menu_add'"
|
|
|
|
|
|
@click="showModel('menu', null)"
|
|
|
|
|
|
v-ripple
|
|
|
|
|
|
style="margin-left: 20px"
|
|
|
|
|
|
>
|
2025-08-13 19:27:49 +08:00
|
|
|
|
添加菜单
|
|
|
|
|
|
</ElButton>
|
|
|
|
|
|
<ElButton @click="toggleExpand" v-ripple>
|
|
|
|
|
|
{{ isExpanded ? '收起' : '展开' }}
|
2025-08-19 16:18:15 +08:00
|
|
|
|
</ElButton> -->
|
2025-08-13 19:27:49 +08:00
|
|
|
|
<!-- 按钮权限:前端控制模式,使用 hasAuth 方法 -->
|
|
|
|
|
|
<!-- <ElButton v-if="hasAuth('B_CODE1')" @click="showModel('menu', null, true)" v-ripple>
|
|
|
|
|
|
添加菜单
|
|
|
|
|
|
</ElButton> -->
|
|
|
|
|
|
</template>
|
2025-08-19 16:18:15 +08:00
|
|
|
|
<template #right>
|
|
|
|
|
|
<div class="btnadds" @click="showModel('menu', null)" v-auth="'system@menu_add'">
|
|
|
|
|
|
<i class="iconfont-sys"></i>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-08-13 19:27:49 +08:00
|
|
|
|
</ArtTableHeader>
|
|
|
|
|
|
<!-- 表格 -->
|
|
|
|
|
|
<ArtTable
|
|
|
|
|
|
rowKey="path"
|
|
|
|
|
|
ref="tableRef"
|
|
|
|
|
|
:loading="loading"
|
|
|
|
|
|
:data="filteredTableData"
|
|
|
|
|
|
:marginTop="10"
|
|
|
|
|
|
:stripe="false"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #default>
|
|
|
|
|
|
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</ArtTable>
|
|
|
|
|
|
|
|
|
|
|
|
<ElDialog :title="dialogTitle" v-model="dialogVisible" width="700px" align-center>
|
|
|
|
|
|
<ElForm ref="formRef" :model="form" :rules="rules" label-width="85px">
|
|
|
|
|
|
<ElRow :gutter="20">
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="资源类型" prop="type">
|
|
|
|
|
|
<el-select v-model="form.type">
|
|
|
|
|
|
<el-option label="目录" value="directory" />
|
|
|
|
|
|
<el-option label="菜单" value="menu" />
|
|
|
|
|
|
<el-option label="权限" value="permission" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="资源名称" prop="name">
|
|
|
|
|
|
<ElInput v-model="form.name" placeholder="资源名称"></ElInput>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
<ElRow :gutter="20">
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="权限标识" prop="code">
|
|
|
|
|
|
<ElInput v-model="form.code" placeholder="权限标识"></ElInput>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="图标" prop="icon">
|
|
|
|
|
|
<ArtIconSelector v-model="form.icon" :iconType="iconType" width="100%" />
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
<ElRow :gutter="20" v-if="form.type !== 'permission'">
|
|
|
|
|
|
<ElCol :span="12" v-if="form.type !== 'permission'">
|
|
|
|
|
|
<ElFormItem label="路由地址" prop="url">
|
|
|
|
|
|
<ElInput v-model="form.url" placeholder="路由地址"></ElInput>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="打开方式" prop="open_type">
|
|
|
|
|
|
<ElSelect v-model="form.open_type" class="account-select">
|
|
|
|
|
|
<el-option label="内部" value="internal" />
|
|
|
|
|
|
<el-option label="外部" value="external" />
|
|
|
|
|
|
<el-option label="嵌入" value="iframe" />
|
|
|
|
|
|
</ElSelect>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
<ElRow :gutter="20" v-if="form.type !== 'permission'">
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="资源排序" prop="sort" style="width: 100%">
|
|
|
|
|
|
<ElInputNumber
|
|
|
|
|
|
v-model="form.sort"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
@change="handleChange"
|
|
|
|
|
|
:min="1"
|
|
|
|
|
|
controls-position="right"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="外部链接" prop="link">
|
|
|
|
|
|
<ElInput
|
|
|
|
|
|
v-model="form.link"
|
|
|
|
|
|
placeholder="外部链接/内嵌地址(https://www.baidu.com)"
|
|
|
|
|
|
></ElInput>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
<ElRow :gutter="20">
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="系统" prop="system_id">
|
|
|
|
|
|
<ElSelect v-model="form.system_id" class="account-select">
|
|
|
|
|
|
<ElOption
|
|
|
|
|
|
v-for="account in systemsList"
|
|
|
|
|
|
:key="account.id"
|
|
|
|
|
|
:label="account.name"
|
|
|
|
|
|
:value="account.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>{{ account.name }}</span>
|
|
|
|
|
|
</ElOption>
|
|
|
|
|
|
</ElSelect>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="12">
|
|
|
|
|
|
<ElFormItem label="父级" prop="parent_id">
|
|
|
|
|
|
<el-tree-select
|
|
|
|
|
|
v-model="form.parent_id"
|
|
|
|
|
|
:data="parentList"
|
|
|
|
|
|
:props="{
|
|
|
|
|
|
value: 'id',
|
|
|
|
|
|
label: 'name',
|
|
|
|
|
|
children: 'children'
|
|
|
|
|
|
}"
|
|
|
|
|
|
check-strictly
|
|
|
|
|
|
:render-after-expand="false"
|
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<!-- <ElSelect v-model="form.parent_id" class="account-select">
|
|
|
|
|
|
<ElOption
|
|
|
|
|
|
v-for="account in parentList"
|
|
|
|
|
|
:key="account.id"
|
|
|
|
|
|
:label="account.name"
|
|
|
|
|
|
:value="account.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>{{ account.name }}</span>
|
|
|
|
|
|
</ElOption>
|
|
|
|
|
|
</ElSelect> -->
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
|
|
|
|
|
|
<ElRow :gutter="20" v-if="form.type !== 'permission'">
|
|
|
|
|
|
<ElCol :span="5">
|
|
|
|
|
|
<ElFormItem label="是否启用" prop="status">
|
|
|
|
|
|
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0"></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="5">
|
|
|
|
|
|
<ElFormItem label="页面缓存" prop="keep_alive">
|
|
|
|
|
|
<ElSwitch
|
|
|
|
|
|
v-model="form.keep_alive"
|
|
|
|
|
|
:active-value="1"
|
|
|
|
|
|
:inactive-value="0"
|
|
|
|
|
|
></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="5">
|
|
|
|
|
|
<ElFormItem label="是否隐藏" prop="is_hide">
|
|
|
|
|
|
<ElSwitch v-model="form.is_hide" :active-value="1" :inactive-value="0"></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="5">
|
|
|
|
|
|
<ElFormItem label="标签页是否隐藏" prop="is_hide_tab" label-width="120px">
|
|
|
|
|
|
<ElSwitch
|
|
|
|
|
|
v-model="form.is_hide_tab"
|
|
|
|
|
|
:active-value="1"
|
|
|
|
|
|
:inactive-value="0"
|
|
|
|
|
|
></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<!-- <ElCol :span="5">
|
|
|
|
|
|
<ElFormItem label="是否内嵌" prop="isMenu">
|
|
|
|
|
|
<ElSwitch v-model="form.isIframe"></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol> -->
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
<ElRow :gutter="20" v-if="form.type !== 'permission'">
|
|
|
|
|
|
<ElCol :span="5">
|
|
|
|
|
|
<ElFormItem label="是否首页" prop="is_home">
|
|
|
|
|
|
<ElSwitch v-model="form.is_home" :active-value="1" :inactive-value="0"></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
<ElCol :span="10">
|
|
|
|
|
|
<ElFormItem label="不需要认证" prop="no_auth" label-width="120px">
|
|
|
|
|
|
<ElSwitch v-model="form.no_auth" :active-value="1" :inactive-value="0"></ElSwitch>
|
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
|
</ElCol>
|
|
|
|
|
|
</ElRow>
|
|
|
|
|
|
</ElForm>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
|
<ElButton @click="dialogVisible = false">取 消</ElButton>
|
|
|
|
|
|
<ElButton type="primary" @click="submitForm()">确 定</ElButton>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</ElDialog>
|
|
|
|
|
|
</ElCard>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ArtTableFullScreen>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import type { FormInstance, FormRules } from 'element-plus'
|
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
import { IconTypeEnum } from '@/enums/appEnum'
|
|
|
|
|
|
import { formatMenuTitle } from '@/router/utils/utils'
|
|
|
|
|
|
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
|
|
|
|
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
|
|
|
|
|
import { ElPopover, ElButton } from 'element-plus'
|
|
|
|
|
|
import { AppRouteRecord } from '@/types/router'
|
|
|
|
|
|
import { useAuth } from '@/composables/useAuth'
|
|
|
|
|
|
import { SearchFormItem } from '@/types'
|
|
|
|
|
|
import { systemService } from '@/api/systemApi'
|
|
|
|
|
|
import { UserService } from '@/api/usersApi'
|
2025-08-15 16:31:55 +08:00
|
|
|
|
const yearVal = ref<any>('')
|
2025-08-13 19:27:49 +08:00
|
|
|
|
defineOptions({ name: 'Menus' })
|
|
|
|
|
|
|
|
|
|
|
|
const { hasAuth } = useAuth()
|
|
|
|
|
|
|
|
|
|
|
|
// const { menuList } = storeToRefs(useMenuStore())
|
|
|
|
|
|
const menuList = ref<any[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
2025-08-15 16:31:55 +08:00
|
|
|
|
const systemsListtab = ref<any>([])
|
|
|
|
|
|
const segmentedOptions = computed(() =>
|
|
|
|
|
|
Array.isArray(systemsListtab.value)
|
|
|
|
|
|
? systemsListtab.value.map((item: any) => ({ label: item.name, value: item.id }))
|
|
|
|
|
|
: []
|
|
|
|
|
|
)
|
|
|
|
|
|
const getsystemsListtab = async () => {
|
|
|
|
|
|
const response = await UserService.getsystemsList()
|
|
|
|
|
|
const list = (response as any)?.data?.records ?? (response as any)?.records ?? response ?? []
|
|
|
|
|
|
systemsListtab.value = Array.isArray(list) ? list : []
|
|
|
|
|
|
if (!yearVal.value && systemsListtab.value.length > 0) {
|
|
|
|
|
|
yearVal.value = systemsListtab.value[0].id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-13 19:27:49 +08:00
|
|
|
|
//系统
|
|
|
|
|
|
const systemsList = ref<any>()
|
|
|
|
|
|
const getsystemsList = async () => {
|
|
|
|
|
|
const response = await UserService.getsystemsList()
|
|
|
|
|
|
systemsList.value = response || []
|
|
|
|
|
|
}
|
|
|
|
|
|
//父级
|
|
|
|
|
|
const parentList = ref<any>()
|
|
|
|
|
|
const getparentList = async () => {
|
|
|
|
|
|
const response = await systemService.getMenuList()
|
|
|
|
|
|
parentList.value = response || []
|
|
|
|
|
|
}
|
|
|
|
|
|
// 定义表单搜索初始值
|
|
|
|
|
|
const initialSearchState = {
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
route: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式表单数据
|
|
|
|
|
|
const formFilters = reactive({ ...initialSearchState })
|
|
|
|
|
|
|
|
|
|
|
|
// 增加实际应用的搜索条件状态
|
|
|
|
|
|
const appliedFilters = reactive({ ...initialSearchState })
|
|
|
|
|
|
|
|
|
|
|
|
// 重置表单
|
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
|
Object.assign(formFilters, { ...initialSearchState })
|
|
|
|
|
|
Object.assign(appliedFilters, { ...initialSearchState })
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索处理
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
|
// 将当前输入的筛选条件应用到实际搜索
|
|
|
|
|
|
Object.assign(appliedFilters, { ...formFilters })
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 表单配置项
|
|
|
|
|
|
const formItems: SearchFormItem[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
label: '菜单名称',
|
|
|
|
|
|
prop: 'name',
|
|
|
|
|
|
type: 'input',
|
|
|
|
|
|
config: {
|
|
|
|
|
|
clearable: true
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: '路由地址',
|
|
|
|
|
|
prop: 'route',
|
|
|
|
|
|
type: 'input',
|
|
|
|
|
|
config: {
|
|
|
|
|
|
clearable: true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// 构建菜单类型标签
|
|
|
|
|
|
const buildMenuTypeTag = (row: AppRouteRecord) => {
|
|
|
|
|
|
if (row.children && row.children.length > 0) {
|
|
|
|
|
|
return 'info'
|
|
|
|
|
|
} else if (row.meta?.link && row.meta?.isIframe) {
|
|
|
|
|
|
return 'success'
|
|
|
|
|
|
} else if (row.path) {
|
|
|
|
|
|
return 'primary'
|
|
|
|
|
|
} else if (row.meta?.link) {
|
|
|
|
|
|
return 'warning'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建菜单类型文本
|
|
|
|
|
|
const buildMenuTypeText = (row: AppRouteRecord) => {
|
|
|
|
|
|
if (row.children && row.children.length > 0) {
|
|
|
|
|
|
return '目录'
|
|
|
|
|
|
} else if (row.meta?.link && row.meta?.isIframe) {
|
|
|
|
|
|
return '内嵌'
|
|
|
|
|
|
} else if (row.path) {
|
|
|
|
|
|
return '菜单'
|
|
|
|
|
|
} else if (row.meta?.link) {
|
|
|
|
|
|
return '外链'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 动态列配置
|
|
|
|
|
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'meta.title',
|
|
|
|
|
|
label: '菜单名称',
|
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
|
formatter: (row: AppRouteRecord) => {
|
|
|
|
|
|
return formatMenuTitle(row.meta?.title)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'type',
|
|
|
|
|
|
label: '菜单类型',
|
|
|
|
|
|
formatter: (row: AppRouteRecord) => {
|
|
|
|
|
|
return h(ElTag, { type: buildMenuTypeTag(row) }, () => buildMenuTypeText(row))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'path',
|
|
|
|
|
|
label: '路由',
|
|
|
|
|
|
formatter: (row: AppRouteRecord) => {
|
|
|
|
|
|
return row.meta?.link || row.path || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'meta.authList',
|
|
|
|
|
|
label: '可操作权限',
|
|
|
|
|
|
formatter: (row: AppRouteRecord) => {
|
|
|
|
|
|
return h(
|
|
|
|
|
|
'div',
|
|
|
|
|
|
{},
|
|
|
|
|
|
row.meta.authList?.map((item: { title: string; auth_mark: string }, index: number) => {
|
|
|
|
|
|
return h(
|
|
|
|
|
|
ElPopover,
|
|
|
|
|
|
{
|
|
|
|
|
|
placement: 'top-start',
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
width: 200,
|
|
|
|
|
|
trigger: 'click',
|
|
|
|
|
|
key: index
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
default: () =>
|
|
|
|
|
|
h('div', { style: 'margin: 0; text-align: right' }, [
|
|
|
|
|
|
h(
|
|
|
|
|
|
ElButton,
|
|
|
|
|
|
{
|
|
|
|
|
|
size: 'small',
|
|
|
|
|
|
type: 'primary',
|
|
|
|
|
|
onClick: () => showModel('button', item, false, row)
|
|
|
|
|
|
},
|
|
|
|
|
|
{ default: () => '编辑' }
|
|
|
|
|
|
),
|
|
|
|
|
|
h(
|
|
|
|
|
|
ElButton,
|
|
|
|
|
|
{
|
|
|
|
|
|
size: 'small',
|
|
|
|
|
|
type: 'danger',
|
|
|
|
|
|
onClick: () => deleteAuth(item)
|
|
|
|
|
|
},
|
|
|
|
|
|
{ default: () => '删除' }
|
|
|
|
|
|
)
|
|
|
|
|
|
]),
|
|
|
|
|
|
reference: () => h(ElButton, { class: 'small-btn' }, { default: () => item.title })
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'date',
|
|
|
|
|
|
label: '编辑时间',
|
|
|
|
|
|
formatter: () => '2022-3-12 12:00:00'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'status',
|
|
|
|
|
|
label: '隐藏菜单',
|
|
|
|
|
|
formatter: (row) => {
|
|
|
|
|
|
return h(ElTag, { type: row.meta.isHide ? 'danger' : 'info' }, () =>
|
|
|
|
|
|
row.meta.isHide ? '是' : '否'
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'operation',
|
|
|
|
|
|
label: '操作',
|
|
|
|
|
|
width: 180,
|
|
|
|
|
|
formatter: (row: AppRouteRecord) => {
|
|
|
|
|
|
return h('div', [
|
|
|
|
|
|
// 后端模式权限标识
|
|
|
|
|
|
hasAuth('system@menu_add') &&
|
|
|
|
|
|
h(ArtButtonTable, {
|
|
|
|
|
|
type: 'add',
|
|
|
|
|
|
onClick: () => showModel('menu', row, true)
|
|
|
|
|
|
}),
|
|
|
|
|
|
hasAuth('system@menu_update') &&
|
|
|
|
|
|
h(ArtButtonTable, {
|
|
|
|
|
|
type: 'edit',
|
|
|
|
|
|
onClick: () => showDialog('edit', row)
|
|
|
|
|
|
}),
|
|
|
|
|
|
hasAuth('system@menu_destroy') &&
|
|
|
|
|
|
h(ArtButtonTable, {
|
|
|
|
|
|
type: 'delete',
|
|
|
|
|
|
onClick: () => deleteMenu(row)
|
|
|
|
|
|
})
|
|
|
|
|
|
])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
|
const form = reactive({
|
|
|
|
|
|
// 菜单
|
|
|
|
|
|
system_id: '',
|
|
|
|
|
|
type: 'menu', // 资源类型
|
|
|
|
|
|
parent_id: '',
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
url: '',
|
|
|
|
|
|
code: '',
|
|
|
|
|
|
icon: '',
|
|
|
|
|
|
status: 1,
|
|
|
|
|
|
sort: 1,
|
|
|
|
|
|
is_home: 0,
|
|
|
|
|
|
keep_alive: 1,
|
|
|
|
|
|
is_hide: 0,
|
|
|
|
|
|
is_hide_tab: 0,
|
|
|
|
|
|
no_auth: 0,
|
|
|
|
|
|
link: '',
|
|
|
|
|
|
open_type: 'internal'
|
|
|
|
|
|
})
|
|
|
|
|
|
const iconType = ref(IconTypeEnum.UNICODE)
|
|
|
|
|
|
|
|
|
|
|
|
// const labelPosition = ref('menu')
|
|
|
|
|
|
const rules = reactive<FormRules>({
|
|
|
|
|
|
name: [
|
|
|
|
|
|
{ required: true, message: '请输入菜单名称', trigger: 'blur' },
|
|
|
|
|
|
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
|
|
|
|
|
],
|
|
|
|
|
|
type: [{ required: true, message: '请选择资源类型', trigger: 'blur' }],
|
|
|
|
|
|
code: [{ required: true, message: '输入权限标识', trigger: 'blur' }]
|
|
|
|
|
|
})
|
|
|
|
|
|
const tableData = ref<AppRouteRecord[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
2025-08-15 16:31:55 +08:00
|
|
|
|
getsystemsListtab()
|
2025-08-13 19:27:49 +08:00
|
|
|
|
getTableData()
|
|
|
|
|
|
})
|
2025-08-15 16:31:55 +08:00
|
|
|
|
const searchArticleByYear = () => {
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
}
|
|
|
|
|
|
// const handleSegmentedClick = (item: any) => {
|
|
|
|
|
|
// // 若点击的是当前项也强制刷新
|
|
|
|
|
|
// yearVal.value = item?.value ?? yearVal.value
|
|
|
|
|
|
// getTableData()
|
|
|
|
|
|
// }
|
2025-08-13 19:27:49 +08:00
|
|
|
|
const getTableData = async () => {
|
|
|
|
|
|
loading.value = true
|
2025-08-15 16:31:55 +08:00
|
|
|
|
// 清空旧数据,避免视觉上出现追加
|
|
|
|
|
|
menuList.value = []
|
|
|
|
|
|
tableData.value = []
|
|
|
|
|
|
// 并发安全:本次请求标识
|
|
|
|
|
|
requestCounter += 1
|
|
|
|
|
|
const reqId = requestCounter
|
|
|
|
|
|
const params: any = {
|
|
|
|
|
|
system_id: yearVal.value || '1'
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await systemService.getMenutreeList(params)
|
|
|
|
|
|
// 若在等待过程中又发起了新请求,则丢弃本次结果
|
|
|
|
|
|
if (reqId !== requestCounter) return
|
|
|
|
|
|
menuList.value = Array.isArray(res) ? res : []
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(e)
|
|
|
|
|
|
menuList.value = []
|
|
|
|
|
|
}
|
2025-08-13 19:27:49 +08:00
|
|
|
|
// console.log(res)
|
|
|
|
|
|
// menuList.value = data
|
|
|
|
|
|
|
|
|
|
|
|
// 基于 appliedFilters 在这里进行过滤,并将结果赋值给 tableData
|
|
|
|
|
|
const searchName = appliedFilters.name?.toLowerCase().trim() || ''
|
|
|
|
|
|
const searchRoute = appliedFilters.route?.toLowerCase().trim() || ''
|
|
|
|
|
|
|
|
|
|
|
|
const filterMenus = (items: AppRouteRecord[]): AppRouteRecord[] => {
|
|
|
|
|
|
return items.reduce((acc: AppRouteRecord[], item: AppRouteRecord) => {
|
|
|
|
|
|
// 浅拷贝节点,避免直接修改原数据
|
|
|
|
|
|
const copy: AppRouteRecord = { ...item }
|
|
|
|
|
|
if (copy.children && copy.children.length > 0) {
|
|
|
|
|
|
copy.children = filterMenus(copy.children)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const menuTitle = formatMenuTitle(copy.meta?.title || '').toLowerCase()
|
|
|
|
|
|
const menuPath = (copy.path || '').toLowerCase()
|
|
|
|
|
|
const nameMatch = !searchName || menuTitle.includes(searchName)
|
|
|
|
|
|
const routeMatch = !searchRoute || menuPath.includes(searchRoute)
|
|
|
|
|
|
const hasChildren = Array.isArray(copy.children) && copy.children.length > 0
|
|
|
|
|
|
|
|
|
|
|
|
if (hasChildren || (nameMatch && routeMatch)) {
|
|
|
|
|
|
acc.push(copy)
|
|
|
|
|
|
}
|
|
|
|
|
|
return acc
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tableData.value = filterMenus(menuList.value)
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤后的表格数据改为来源于 getTableData 的结果
|
|
|
|
|
|
const filteredTableData = computed(() => tableData.value)
|
|
|
|
|
|
|
|
|
|
|
|
const isEdit = ref(false)
|
|
|
|
|
|
const formRef = ref<FormInstance>()
|
|
|
|
|
|
const dialogTitle = computed(() => {
|
|
|
|
|
|
// const type = labelPosition.value === 'menu' ? '菜单' : '权限'
|
|
|
|
|
|
return isEdit.value ? `编辑资源` : `新建资源`
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const showDialog = (type: string, row: AppRouteRecord) => {
|
|
|
|
|
|
showModel('menu', row)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleChange = () => {}
|
|
|
|
|
|
|
|
|
|
|
|
const submitForm = async () => {
|
|
|
|
|
|
if (!formRef.value) return
|
|
|
|
|
|
|
|
|
|
|
|
await formRef.value.validate(async (valid) => {
|
|
|
|
|
|
if (valid) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (isEdit.value) {
|
|
|
|
|
|
const payload = { ...form, id: itemid.value }
|
|
|
|
|
|
await systemService.menuEdit(payload as any)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await systemService.menuAdd(form)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ElMessage.success(`${isEdit.value ? '编辑' : '新增'}成功`)
|
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
|
// 刷新表格数据
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
ElMessage.error(`${isEdit.value ? '编辑' : '新增'}失败`)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
const itemid = ref('')
|
|
|
|
|
|
const showModel = (type: string, row?: any, padd: boolean = false, data?: any) => {
|
|
|
|
|
|
getsystemsList()
|
|
|
|
|
|
getparentList()
|
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
|
// labelPosition.value = type
|
|
|
|
|
|
isEdit.value = false
|
|
|
|
|
|
// lockMenuType.value = lock
|
|
|
|
|
|
resetForm()
|
|
|
|
|
|
itemid.value = ''
|
|
|
|
|
|
if (data) {
|
|
|
|
|
|
isEdit.value = true
|
|
|
|
|
|
form.type = 'permission'
|
|
|
|
|
|
form.system_id = data.meta.systemId
|
|
|
|
|
|
form.parent_id = data.id
|
|
|
|
|
|
form.name = row.title
|
|
|
|
|
|
form.code = row.authMark
|
|
|
|
|
|
itemid.value = row.id
|
|
|
|
|
|
} else if (padd) {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
form.parent_id = row.id
|
|
|
|
|
|
form.system_id = row.meta.systemId
|
|
|
|
|
|
})
|
|
|
|
|
|
} else if (row && !padd) {
|
|
|
|
|
|
isEdit.value = true
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
// 回显数据
|
|
|
|
|
|
itemid.value = row.id
|
|
|
|
|
|
form.system_id = row.meta.systemId
|
|
|
|
|
|
form.parent_id = row.meta.parentId == 0 ? '' : row.meta.parentId
|
|
|
|
|
|
form.name = row.name
|
|
|
|
|
|
form.type = row.meta.type
|
|
|
|
|
|
form.url = row.path
|
|
|
|
|
|
form.code = row.meta.code
|
|
|
|
|
|
form.icon = row.meta.icon
|
|
|
|
|
|
form.sort = row.meta.sort
|
|
|
|
|
|
form.is_home = row.meta.isHome
|
|
|
|
|
|
form.keep_alive = row.meta.keepAlive
|
|
|
|
|
|
form.is_hide = row.meta.isHide
|
|
|
|
|
|
form.is_hide_tab = row.meta.isHideTab
|
|
|
|
|
|
form.no_auth = row.meta.noAuth
|
|
|
|
|
|
form.status = row.meta.status
|
|
|
|
|
|
form.link = row.meta.link
|
|
|
|
|
|
form.open_type = row.meta.openType
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
formRef.value?.resetFields()
|
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
|
// 菜单
|
|
|
|
|
|
system_id: '',
|
|
|
|
|
|
type: 'menu', // 资源类型
|
|
|
|
|
|
parent_id: '',
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
url: '',
|
|
|
|
|
|
code: '',
|
|
|
|
|
|
icon: '',
|
|
|
|
|
|
status: 1,
|
|
|
|
|
|
sort: 1,
|
|
|
|
|
|
is_home: 0,
|
|
|
|
|
|
keep_alive: 1,
|
|
|
|
|
|
is_hide: 0,
|
|
|
|
|
|
is_hide_tab: 0,
|
|
|
|
|
|
no_auth: 0,
|
|
|
|
|
|
link: '',
|
|
|
|
|
|
open_type: 'internal'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const deleteMenu = async (item: any) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm('确定要删除该菜单吗?删除后无法恢复', '提示', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
})
|
|
|
|
|
|
const res = await systemService.menuDelete({ id: item.id })
|
|
|
|
|
|
ElMessage.success(res?.message || '删除成功!')
|
|
|
|
|
|
// 刷新表格数据
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
ElMessage.error('删除失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const deleteAuth = async (item: any) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm('确定要删除该权限吗?删除后无法恢复', '提示', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
})
|
|
|
|
|
|
const res = await systemService.menuDelete({ id: item.id })
|
|
|
|
|
|
ElMessage.success(res?.message || '删除成功!')
|
|
|
|
|
|
// 刷新表格数据
|
|
|
|
|
|
getTableData()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
ElMessage.error('删除失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 添加一个控制变量
|
|
|
|
|
|
// const lockMenuType = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
const isExpanded = ref(false)
|
|
|
|
|
|
const tableRef = ref()
|
|
|
|
|
|
|
2025-08-15 16:31:55 +08:00
|
|
|
|
// 数据请求并发保护计数器
|
|
|
|
|
|
let requestCounter = 0
|
|
|
|
|
|
|
2025-08-13 19:27:49 +08:00
|
|
|
|
const toggleExpand = () => {
|
|
|
|
|
|
isExpanded.value = !isExpanded.value
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
if (tableRef.value) {
|
|
|
|
|
|
tableRef.value[isExpanded.value ? 'expandAll' : 'collapseAll']()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.menu-page {
|
|
|
|
|
|
.svg-icon {
|
|
|
|
|
|
width: 1.8em;
|
|
|
|
|
|
height: 1.8em;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
vertical-align: -8px;
|
|
|
|
|
|
fill: currentcolor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.small-btn) {
|
|
|
|
|
|
height: 22px !important;
|
|
|
|
|
|
padding: 0 5px !important;
|
|
|
|
|
|
font-size: 10px !important;
|
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
:deep(.el-dialog__footer) {
|
|
|
|
|
|
z-index: inherit;
|
|
|
|
|
|
.el-button > span {
|
|
|
|
|
|
z-index: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-19 16:18:15 +08:00
|
|
|
|
.btnadds {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
margin-left: 10px;
|
|
|
|
|
|
color: var(--art-gray-700);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
background-color: rgba(var(--art-gray-200-rgb), 0.8);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
transition: color 0.3s;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
}
|
2025-08-13 19:27:49 +08:00
|
|
|
|
</style>
|