添加菜单 权限 等页面

This commit is contained in:
浅念
2025-08-13 19:27:49 +08:00
parent e797cb24de
commit f5deb7b8f7
15 changed files with 2297 additions and 20 deletions
+22
View File
@@ -153,4 +153,26 @@ export class manageService {
// showErrorMessage: false // 不显示错误消息
})
}
//用户设置角色
static userSetRole(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/user/authorize',
params
// showErrorMessage: false // 不显示错误消息
})
}
//用户菜单权限设置
static userMenuAuth(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/user/set/resources',
params
// showErrorMessage: false // 不显示错误消息
})
}
//清除缓存
static clearCache() {
return request.get<any>({
url: '/common/preset/cache/clear'
})
}
}
+5 -2
View File
@@ -1,6 +1,7 @@
import { menuDataToRouter } from '@/router/utils/menuToRouter'
import { AppRouteRecord } from '@/types/router'
import { useUserStore } from '@/store/modules/user'
import { UserService } from '@/api/usersApi'
interface MenuResponse {
menuList: AppRouteRecord[]
}
@@ -13,14 +14,16 @@ export const menuService = {
if (!userStore.isLogin) {
throw new Error('用户未登录')
}
const { menu } = await UserService.getMenuList()
const menuData = menu
// const userStore = useUserStore()
// 模拟接口返回的菜单数据
const menuData = userStore.info.menu
// 验证菜单数据
if (!menuData || !Array.isArray(menuData) || menuData.length === 0) {
ElMessage.error('暂无系统菜单权限')
return { menuList: [] }
}
console.log('menuData', menuData)
// 处理菜单数据
const menuList = menuData.map((route: any) => menuDataToRouter(route))
// 验证处理后的菜单列表
+219
View File
@@ -0,0 +1,219 @@
import request from '@/utils/http'
export class systemService {
//系统列表
static getSystemList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/systems/list',
params
})
}
//系统添加
static systemAdd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/systems/store',
params
// showErrorMessage: false // 不显示错误消息
})
}
//系统详情
static systemDetail(params: any) {
return request.get<any>({
url: '/admin/systems/show',
params
})
}
//系统编辑
static systemEdit(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/systems/update',
params
// showErrorMessage: false // 不显示错误消息
})
}
//系统删除
static systemDelete(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/systems/destroy',
params
// showErrorMessage: false // 不显示错误消息
})
}
//飞书应用列表
static getFeishuAppList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/corp/feishu/list',
params
})
}
//飞书应用添加
static feishuAppAdd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/corp/feishu/store',
params
// showErrorMessage: false // 不显示错误消息
})
}
//飞书应用详情
static feishuAppDetail(params: any) {
return request.get<any>({
url: '/admin/corp/feishu/show',
params
})
}
//飞书应用编辑
static feishuAppEdit(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/corp/feishu/update',
params
// showErrorMessage: false // 不显示错误消息
})
}
//飞书应用删除
static feishuAppDelete(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/corp/feishu/destroy',
params
// showErrorMessage: false // 不显示错误消息
})
}
//公司列表
static getCompanyList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/corp/list',
params
})
}
//公司添加
static companyAdd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/corp/store',
params
// showErrorMessage: false // 不显示错误消息
})
}
//公司详情
static companyDetail(params: any) {
return request.get<any>({
url: '/admin/corp/show',
params
})
}
//公司编辑
static companyEdit(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/corp/update',
params
// showErrorMessage: false // 不显示错误消息
})
}
//公司删除
static companyDelete(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/corp/destroy',
params
// showErrorMessage: false // 不显示错误消息
})
}
//角色列表
static getRoleList(params: Api.Common.PaginatingParams) {
return request.get<Api.Common.CommonListData>({
url: '/admin/role/list',
params
})
}
//角色添加
static roleAdd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/role/store',
params
// showErrorMessage: false // 不显示错误消息
})
}
//角色详情
static roleDetail(params: any) {
return request.get<any>({
url: '/admin/role/show',
params
})
}
//角色编辑
static roleEdit(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/role/update',
params
// showErrorMessage: false // 不显示错误消息
})
}
//角色删除
static roleDelete(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/role/destroy',
params
// showErrorMessage: false // 不显示错误消息
})
}
//角色权限设置
static roleAuth(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/role/menu/resource',
params
// showErrorMessage: false // 不显示错误消息
})
}
//角色下拉框
static getRoleSelectList() {
return request.get<Api.Common.CommonListData>({
url: '/common/preset/roles'
})
}
//菜单列表
static getMenutreeList() {
return request.get<any>({
url: '/admin/resources/list/tree'
})
}
static getMenuList() {
return request.get<Api.Common.CommonListData>({
url: '/admin/resources/list/menu'
})
}
//菜单添加
static menuAdd(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/resources/store',
params
// showErrorMessage: false // 不显示错误消息
})
}
//菜单详情
static menuDetail(params: any) {
return request.get<any>({
url: '/admin/resources/show',
params
})
}
//菜单编辑
static menuEdit(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/resources/update',
params
// showErrorMessage: false // 不显示错误消息
})
}
//菜单删除
static menuDelete(params: any) {
return request.post<Api.Common.AddResponse>({
url: '/admin/resources/destroy',
params
// showErrorMessage: false // 不显示错误消息
})
}
//权限菜单
static getPermissionMenuList(params: any) {
return request.get<Api.Common.CommonListData>({
url: '/admin/resources/list/menu/tree',
params
})
}
}
+6
View File
@@ -72,4 +72,10 @@ export class UserService {
params
})
}
//获取菜单
static getMenuList() {
return request.get<Api.User.UserInfo>({
url: '/common/preset/menus'
})
}
}
@@ -54,7 +54,11 @@
</div>
</div>
</div> -->
<div class="btn-box screen-box" @click="clearCachefn">
<div class="btn">
<i class="iconfont-sys">&#xe847;</i>
</div>
</div>
<!-- 全屏按钮 -->
<div class="btn-box screen-box" @click="toggleFullScreen">
<div
@@ -215,6 +219,7 @@
import { mittBus } from '@/utils/sys'
import { useMenuStore } from '@/store/modules/menu'
import AppConfig from '@/config'
import { manageService } from '@/api/adminApi'
import { languageOptions } from '@/locales'
const isWindows = navigator.userAgent.includes('Windows')
const { locale } = useI18n()
@@ -275,7 +280,14 @@
const toggleFullScreen = () => {
toggleFullscreen()
}
const clearCachefn = () => {
manageService.clearCache().then((res) => {
ElMessage.success(res.data?.message || '成功!')
// setTimeout(() => {
window.location.reload()
// }, 5000)
})
}
const topBarWidth = (): string => {
const { TOP, DUAL_MENU, TOP_LEFT } = MenuTypeEnum
const { getMenuOpenWidth } = settingStore
+1
View File
@@ -28,6 +28,7 @@ export interface RouteMeta extends Record<string | number | symbol, unknown> {
authList?: Array<{
title: string
auth_mark: string
authMark?: string // 兼容旧版
}>
/** 是否为一级菜单 */
isFirstLevel?: boolean
+4
View File
@@ -51,6 +51,10 @@ declare namespace Api {
code: string
message: string
}
interface CommonListDatad1 {
data: never[]
message: string
}
}
/** 认证类型 */
+186
View File
@@ -0,0 +1,186 @@
<template>
<div class="page-content">
<div v-if="!isMobile">
<ElRow>
<div style="width: 12px"></div>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<ElButton @click="showDialog('add')" v-ripple>新增</ElButton>
</ElCol>
</ElRow>
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
>
<template #default>
<ElTableColumn label="ID" prop="id" />
<ElTableColumn label="公司名称" prop="name" />
<ElTableColumn label="状态" prop="status">
<template #default="scope">
<ElTag :type="scope.row.status ? 'primary' : 'info'">
{{ scope.row.status ? '启用' : '禁用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" />
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增' : '编辑'"
width="40%"
align-center
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
<ElFormItem label="公司名称" prop="name">
<ElInput v-model="form.name" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
</div>
<div v-else class="mobile-hide-tip">
<p>该页面暂不支持移动端访问</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { Ordertype} from '@/mock/temp/formData'
import { systemService } from '@/api/systemApi'
defineOptions({ name: 'Type ' })
const loading = ref(false)
const dialogVisible = ref(false)
const typeList = ref<any[]>([])
const formRef = ref<FormInstance>()
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const rules = reactive<FormRules>({
name: [{ required: true, message: '请输入公司名称', trigger: 'blur' }]
})
const form = reactive<Ordertype>({
name: '',
id:''
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
// 表格实例引用
const tableRef = ref()
onMounted(() => {
getWorkList()
})
// 获取列表数据
const getWorkList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const { records, current, size, total } = await systemService.getCompanyList({
current: currentPage,
size: pageSize
})
tableData.value = records.map((item: any) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const dialogType = ref('add')
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.name = row.name
form.id = row.id
} else {
form.name = ''
}
}
const deleteRole = (row?: any) => {
ElMessageBox.confirm('确定删除该分类吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await systemService.companyDelete({
id: row.id,
})
ElMessage.success(message)
getWorkList()
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
if (dialogType.value === 'add') {
const { message } = await systemService.companyAdd({
name: form.name
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
} else {
const { message } = await systemService.companyEdit({
name: form.name,
id: form.id
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.page-content {
.svg-icon {
width: 1.8em;
height: 1.8em;
overflow: hidden;
vertical-align: -8px;
fill: currentcolor;
}
}
</style>
+194
View File
@@ -0,0 +1,194 @@
<template>
<div class="page-content">
<div v-if="!isMobile">
<ElRow>
<!-- <ElCol :xs="24" :sm="12" :lg="6">
<ElInput placeholder="名称"></ElInput>
</ElCol> -->
<div style="width: 12px"></div>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<!-- <ElButton v-ripple>搜索</ElButton> -->
<ElButton @click="showDialog('add')" v-ripple>新增</ElButton>
</ElCol>
</ElRow>
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
>
<template #default>
<ElTableColumn label="ID" prop="id" />
<ElTableColumn label="分类名称" prop="name" />
<ElTableColumn label="状态" prop="status">
<template #default="scope">
<ElTag :type="scope.row.status ? 'primary' : 'info'">
{{ scope.row.status ? '启用' : '禁用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" />
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增' : '编辑'"
width="40%"
align-center
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
<ElFormItem label="分类名称" prop="name">
<ElInput v-model="form.name" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
</div>
<div v-else class="mobile-hide-tip">
<p>该页面暂不支持移动端访问</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { Ordertype} from '@/mock/temp/formData'
import { systemService } from '@/api/systemApi'
defineOptions({ name: 'Type ' })
const loading = ref(false)
const dialogVisible = ref(false)
const typeList = ref<any[]>([])
const formRef = ref<FormInstance>()
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const rules = reactive<FormRules>({
name: [
{ required: true, message: '请输入分类名称', trigger: 'blur' }
]
})
const form = reactive<Ordertype>({
name: '',
id:''
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
// 表格实例引用
const tableRef = ref()
onMounted(() => {
getWorkList()
})
// 获取列表数据
const getWorkList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const { records, current, size, total } = await systemService.getFeishuAppList({
current: currentPage,
size: pageSize
})
tableData.value = records.map((item: any, index: number) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const dialogType = ref('add')
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.name = row.name
form.id = row.id
} else {
form.name = ''
}
}
const deleteRole = (row?: any) => {
ElMessageBox.confirm('确定删除该分类吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await systemService.feishuAppDelete({
id: row.id,
})
ElMessage.success(message)
getWorkList()
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
if (dialogType.value === 'add') {
const { message } = await systemService.feishuAppAdd({
name: form.name
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
} else {
const { message } = await systemService.feishuAppEdit({
name: form.name,
id: form.id
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.page-content {
.svg-icon {
width: 1.8em;
height: 1.8em;
overflow: hidden;
vertical-align: -8px;
fill: currentcolor;
}
}
</style>
+696
View File
@@ -0,0 +1,696 @@
<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>
<!-- 按钮权限:后端控制模式,使用自定义指令 -->
<ElButton v-auth="'system@menu_add'" @click="showModel('menu', null)" v-ripple>
添加菜单
</ElButton>
<ElButton @click="toggleExpand" v-ripple>
{{ isExpanded ? '收起' : '展开' }}
</ElButton>
<!-- 按钮权限:前端控制模式,使用 hasAuth 方法 -->
<!-- <ElButton v-if="hasAuth('B_CODE1')" @click="showModel('menu', null, true)" v-ripple>
添加菜单
</ElButton> -->
</template>
</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'
defineOptions({ name: 'Menus' })
const { hasAuth } = useAuth()
// const { menuList } = storeToRefs(useMenuStore())
const menuList = ref<any[]>([])
const loading = ref(false)
//系统
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' }
],
system_id: [{ required: true, message: '请选择系统', trigger: 'blur' }],
type: [{ required: true, message: '请选择资源类型', trigger: 'blur' }],
code: [{ required: true, message: '输入权限标识', trigger: 'blur' }]
})
const tableData = ref<AppRouteRecord[]>([])
onMounted(() => {
getTableData()
})
const getTableData = async () => {
loading.value = true
// const { res } = await systemService.getMenutreeList()
await systemService.getMenutreeList().then((res) => {
console.log(res)
menuList.value = res
})
// 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()
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;
}
}
}
</style>
+425
View File
@@ -0,0 +1,425 @@
<template>
<div class="page-content">
<ElRow>
<ElCol :xs="24" :sm="12" :lg="6">
<ElInput placeholder="角色名称"></ElInput>
</ElCol>
<div style="width: 12px"></div>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<ElButton v-ripple>搜索</ElButton>
<ElButton @click="showDialog('add')" v-ripple>新增角色</ElButton>
</ElCol>
</ElRow>
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="roleList"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn label="角色名称" prop="name" />
<ElTableColumn label="描述" prop="desc" />
<ElTableColumn label="启用" prop="enable">
<template #default="scope">
<ElTag :type="scope.row.status == 1 ? 'primary' : 'info'">
{{ scope.row.status == 1 ? '启用' : '禁用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at"></ElTableColumn>
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button link @click="showPermissionDialog(scope.row)"> 菜单权限 </el-button>
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增角色' : '编辑角色'"
width="30%"
align-center
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
<ElFormItem label="角色名称" prop="name">
<ElInput v-model="form.name" />
</ElFormItem>
<ElFormItem label="描述" prop="desc">
<ElInput v-model="form.desc" type="textarea" :rows="3" />
</ElFormItem>
<ElFormItem label="排序" prop="sort">
<ElInput type="number" v-model="form.sort" />
</ElFormItem>
<ElFormItem label="启用">
<ElSwitch v-model="form.status" :active-value="1" :inactive-value="0" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
<ElDialog
v-model="permissionDialog"
title="菜单权限"
width="520px"
align-center
class="el-dialog-border"
>
<ElScrollbar height="70vh">
<ElTree
ref="treeRef"
:data="processedMenuList"
show-checkbox
node-key="id"
:default-expand-all="isExpandAll"
:props="defaultProps"
@check="handleTreeCheck"
>
<template #default="{ data }">
<div style="display: flex; align-items: center">
<span v-if="data.isAuth">
{{ data.label }}
</span>
<span v-else>{{ defaultProps.label(data) }}</span>
</div>
</template>
</ElTree>
</ElScrollbar>
<template #footer>
<div class="dialog-footer">
<ElButton @click="toggleExpandAll">{{ isExpandAll ? '全部收起' : '全部展开' }}</ElButton>
<ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
isSelectAll ? '取消全选' : '全部选择'
}}</ElButton>
<ElButton type="primary" @click="savePermission">保存</ElButton>
</div>
</template>
</ElDialog>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { formatMenuTitle } from '@/router/utils/utils'
import { systemService } from '@/api/systemApi'
// import { ButtonMoreItem } from '@/components/core/forms/ArtButtonMore.vue'
// import { Role, ROLE_LIST_DATA } from '@/mock/temp/formData'
const loading = ref(false)
defineOptions({ name: 'Role' })
const dialogVisible = ref(false)
const permissionDialog = ref(false)
// 权限树数据(来自接口 getPermissionMenuList)
const permissionMenuList = ref<any[]>([])
const treeRef = ref()
const isExpandAll = ref(true)
const isSelectAll = ref(false)
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
const formRef = ref<FormInstance>()
const rules = reactive<FormRules>({
name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
des: [{ required: true, message: '请输入角色描述', trigger: 'blur' }]
})
const form = reactive<any>({
id: '',
name: '',
desc: '',
sort: 0,
status: 1
})
const roleList = ref<any[]>([])
onMounted(() => {
getTableData()
})
// const getTableData = () => {
// roleList.value = ROLE_LIST_DATA
// }
// 获取列表数据
const getTableData = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const { records, current, size, total } = await systemService.getRoleList({
current: currentPage,
size: pageSize
})
roleList.value = records.map((item: any) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取用户列表失败:', error)
} finally {
loading.value = false
}
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getTableData()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getTableData()
}
const dialogType = ref('add')
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.id = row.id
form.name = row.name
form.desc = row.desc
form.sort = row.sort
form.status = row.status
} else {
form.name = ''
form.desc = ''
form.sort = 0
form.status = 1
}
}
const roleId = ref('')
const showPermissionDialog = async (row: any) => {
permissionDialog.value = true
roleId.value = row.id
try {
const res = await systemService.getPermissionMenuList({ role_id: row.id })
// 兼容不同返回结构(data/records/直接数组)
const list = (res as any)?.data ?? (res as any)?.records ?? (res as any)
permissionMenuList.value = Array.isArray(list) ? list : []
// 根据接口返回的 selected 字段设置默认选中
nextTick(() => {
const keys = buildDefaultCheckedKeys(permissionMenuList.value)
treeRef.value?.setCheckedKeys(keys)
handleTreeCheck()
})
} catch (e) {
permissionMenuList.value = []
}
}
// 构建默认选中的 key 列表(与 node-key="id" 一致)
const buildDefaultCheckedKeys = (source: any[]): Array<string | number> => {
const keys: Array<string | number> = []
const walk = (node: any) => {
if (node?.meta?.selected) {
keys.push(node.id)
}
if (Array.isArray(node?.meta?.authList)) {
node.meta.authList.forEach((auth: any) => {
if (auth?.selected) {
keys.push(`${node.id}__auth__${auth.authMark}`)
}
})
}
if (Array.isArray(node?.children)) node.children.forEach(walk)
}
source.forEach(walk)
return keys
}
// 处理菜单数据,将 authList 转换为子节点
const processedMenuList = computed(() => {
const processNode = (node: any) => {
// 规范化 id,确保树节点唯一键与 node-key 对应
const processed: any = { ...node, id: node.id }
// 如果有 authList,将其转换为子节点
if (node.meta && node.meta.authList && node.meta.authList.length) {
const authNodes = node.meta.authList.map((auth: any) => ({
// 为权限子节点提供稳定唯一 id(数字+字符串),与 node-key="id" 匹配
id: `${node.id}__auth__${auth.authMark}`,
name: `${node.name}_${auth.authMark}`,
label: auth.title,
auth_mark: auth.authMark,
auth_id: auth.id,
isAuth: true,
checked: auth.selected || false
}))
processed.children = processed.children ? [...processed.children, ...authNodes] : authNodes
}
// 递归处理子节点
if (processed.children) {
processed.children = processed.children.map(processNode)
}
return processed
}
const source = permissionMenuList.value || []
return source.map(processNode)
})
const defaultProps = {
children: 'children',
label: (data: any) => formatMenuTitle(data.meta?.title) || ''
}
const deleteRole = async (row: any) => {
ElMessageBox.confirm('确定删除该角色吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const res = await systemService.roleDelete({ id: row.id })
ElMessage.success(res?.message || '删除成功!')
getTableData()
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
if (dialogType.value === 'add') {
// 新增角色逻辑
const res = await systemService.roleAdd(form)
ElMessage.success(res?.message || '提交成功!')
getTableData()
} else {
// 编辑角色逻辑
const res = await systemService.roleEdit(form)
ElMessage.success(res?.message || '提交成功!')
getTableData()
}
dialogVisible.value = false
formEl.resetFields()
}
const getSelectedIds = () => {
const tree = treeRef.value
if (!tree) return { menuIds: [], authIds: [] }
const checkedNodes = tree.getCheckedNodes?.(false) || []
const halfCheckedNodes = tree.getHalfCheckedNodes?.() || []
const allNodes = [...checkedNodes, ...halfCheckedNodes]
const menuIds = Array.from(
new Set(
allNodes
.filter((n: any) => !n.isAuth && n.id !== undefined && n.id !== null)
.map((n: any) => n.id)
)
)
const authIds = Array.from(
new Set(
checkedNodes.filter((n: any) => n.isAuth && n.auth_id != null).map((n: any) => n.auth_id)
)
)
const ids = Array.from(new Set([...(menuIds as any[]), ...(authIds as any[])]))
return { menuIds, authIds, ids }
}
const savePermission = async () => {
const { ids } = getSelectedIds()
// console.log('选中的菜单ID:', menuIds)
// console.log('选中的权限ID:', authIds)
// console.log('合并后的全部ID:', ids)
const res = await systemService.roleAuth({
role_id: roleId.value,
resources: ids
})
ElMessage.success(res?.message || '权限保存成功')
permissionDialog.value = false
}
const toggleExpandAll = () => {
const tree = treeRef.value
if (!tree) return
// 使用store.nodesMap直接控制所有节点的展开状态
const nodes = tree.store.nodesMap
for (const node in nodes) {
nodes[node].expanded = !isExpandAll.value
}
isExpandAll.value = !isExpandAll.value
}
const toggleSelectAll = () => {
const tree = treeRef.value
if (!tree) return
if (!isSelectAll.value) {
// 全选:获取所有节点的key并设置为选中
const allKeys = getAllNodeKeys(processedMenuList.value)
tree.setCheckedKeys(allKeys)
} else {
// 取消全选:清空所有选中
tree.setCheckedKeys([])
}
isSelectAll.value = !isSelectAll.value
}
const getAllNodeKeys = (nodes: any[]): Array<string | number> => {
const keys: Array<string | number> = []
const traverse = (nodeList: any[]) => {
nodeList.forEach((node) => {
if (node.id !== undefined && node.id !== null) {
keys.push(node.id)
}
if (node.children && node.children.length > 0) {
traverse(node.children)
}
})
}
traverse(nodes)
return keys
}
const handleTreeCheck = () => {
const tree = treeRef.value
if (!tree) return
// 使用树组件的getCheckedKeys方法获取选中的节点
const checkedKeys = tree.getCheckedKeys()
const allKeys = getAllNodeKeys(processedMenuList.value)
// 判断是否全选:选中的节点数量等于总节点数量
isSelectAll.value = checkedKeys.length === allKeys.length && allKeys.length > 0
}
</script>
<style lang="scss" scoped>
.page-content {
.svg-icon {
width: 1.8em;
height: 1.8em;
overflow: hidden;
vertical-align: -8px;
fill: currentcolor;
}
}
</style>
+194
View File
@@ -0,0 +1,194 @@
<template>
<div class="page-content">
<div v-if="!isMobile">
<ElRow>
<!-- <ElCol :xs="24" :sm="12" :lg="6">
<ElInput placeholder="名称"></ElInput>
</ElCol> -->
<div style="width: 12px"></div>
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
<!-- <ElButton v-ripple>搜索</ElButton> -->
<ElButton @click="showDialog('add')" v-ripple>新增</ElButton>
</ElCol>
</ElRow>
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
>
<template #default>
<ElTableColumn label="ID" prop="id" />
<ElTableColumn label="分类名称" prop="name" />
<ElTableColumn label="状态" prop="status">
<template #default="scope">
<ElTag :type="scope.row.status ? 'primary' : 'info'">
{{ scope.row.status ? '启用' : '禁用' }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn label="创建时间" prop="created_at" />
<ElTableColumn fixed="right" label="操作" width="200px">
<template #default="scope">
<ElRow>
<el-button link @click="showDialog('edit', scope.row)"> 编辑 </el-button>
<el-button link @click="deleteRole(scope.row)"> 删除 </el-button>
</ElRow>
</template>
</ElTableColumn>
</template>
</ArtTable>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增' : '编辑'"
width="40%"
align-center
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="160px">
<ElFormItem label="分类名称" prop="name">
<ElInput v-model="form.name" />
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
</div>
</template>
</ElDialog>
</div>
<div v-else class="mobile-hide-tip">
<p>该页面暂不支持移动端访问</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { Ordertype} from '@/mock/temp/formData'
import { systemService } from '@/api/systemApi'
defineOptions({ name: 'Type ' })
const loading = ref(false)
const dialogVisible = ref(false)
const typeList = ref<any[]>([])
const formRef = ref<FormInstance>()
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
const rules = reactive<FormRules>({
name: [
{ required: true, message: '请输入分类名称', trigger: 'blur' }
]
})
const form = reactive<Ordertype>({
name: '',
id:''
})
const pagination = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
// 表格实例引用
const tableRef = ref()
onMounted(() => {
getWorkList()
})
// 获取列表数据
const getWorkList = async () => {
loading.value = true
try {
const { currentPage, pageSize } = pagination
const { records, current, size, total } = await systemService.getSystemList({
current: currentPage,
size: pageSize
})
tableData.value = records.map((item: any, index: number) => ({
...item
}))
Object.assign(pagination, { currentPage: current, pageSize: size, total })
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
const dialogType = ref('add')
const showDialog = (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
if (type === 'edit' && row) {
form.name = row.name
form.id = row.id
} else {
form.name = ''
}
}
const deleteRole = (row?: any) => {
ElMessageBox.confirm('确定删除该分类吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(async () => {
const { message } = await systemService.systemDelete({
id: row.id,
})
ElMessage.success(message)
getWorkList()
})
}
const handleSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
if (dialogType.value === 'add') {
const { message } = await systemService.systemAdd({
name: form.name
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
} else {
const { message } = await systemService.systemEdit({
name: form.name,
id:form.id
})
ElMessage.success(message)
dialogVisible.value = false
getWorkList()
}
}
</script>
<style lang="scss" scoped>
:deep(.el-table__empty-block) {
height: 100% !important;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.page-content {
.svg-icon {
width: 1.8em;
height: 1.8em;
overflow: hidden;
vertical-align: -8px;
fill: currentcolor;
}
}
</style>
+328 -13
View File
@@ -3,16 +3,16 @@
<!-- 顶部操作栏 -->
<div class="customer-toolbar boxshadow">
<div class="customer-search-group">
<el-input v-model="keyword" placeholder="请输入关键字" />
<el-select
v-model="selectValue"
style="min-width: 120px"
filterable
placeholder="请选择用户"
>
<el-option v-for="v in userOptions" :key="v.id" :label="v.name" :value="v.id">
</el-option>
</el-select>
<el-form-item label="关键字:" label-width="70px">
<el-input style="min-width: 120px" v-model="keyword" placeholder="请输入关键字" />
</el-form-item>
<el-form-item label="状态:" label-width="50px">
<el-select v-model="selectValue" style="min-width: 120px">
<el-option label="全部" value="" />
<el-option label="正常" value="1" />
<el-option label="离职" value="2" />
</el-select>
</el-form-item>
</div>
</div>
<div class="boxshadow">
@@ -57,6 +57,24 @@
<el-table-column prop="customer_count" label="客户数量" align="center" />
<el-table-column fixed="right" label="操作" width="200px" align="center">
<template #default="scope">
<el-link
type="success"
underline="never"
style="margin-right: 8px"
@click="showPermissionDialog(scope.row)"
v-if="hasAuth('user@direct_perm')"
>
<i class="iconfont-sys">&#xe723;</i>直接授权
</el-link>
<el-link
type="warning"
underline="never"
style="margin-right: 8px"
@click="showroleDialog(scope.row)"
v-if="hasAuth('user@role_system')"
>
<i class="iconfont-sys">&#xe830;</i>角色&系统
</el-link>
<el-link
type="primary"
underline="never"
@@ -185,7 +203,6 @@
</div>
</el-form>
</el-dialog>
<!-- 图片预览组件 -->
<ElImageViewer
v-if="showImageViewer"
@@ -193,6 +210,97 @@
:hide-on-click-modal="true"
@close="showImageViewer = false"
/>
<ElDialog
v-model="permissionDialog"
title="菜单权限"
width="520px"
align-center
class="el-dialog-border"
>
<ElScrollbar height="70vh">
<ElTree
ref="treeRef"
:data="processedMenuList"
show-checkbox
node-key="id"
:default-expand-all="isExpandAll"
:props="defaultProps"
@check="handleTreeCheck"
>
<template #default="{ data }">
<div style="display: flex; align-items: center">
<span v-if="data.isAuth">
{{ data.label }}
</span>
<span v-else>{{ defaultProps.label(data) }}</span>
</div>
</template>
</ElTree>
</ElScrollbar>
<template #footer>
<div class="dialog-footer">
<ElButton @click="toggleExpandAll">{{ isExpandAll ? '全部收起' : '全部展开' }}</ElButton>
<ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
isSelectAll ? '取消全选' : '全部选择'
}}</ElButton>
<ElButton type="primary" @click="savePermission">保存</ElButton>
</div>
</template>
</ElDialog>
<el-dialog
title="角色&系统设置"
v-model="roleDialogVisible"
width="700px"
:close-on-click-modal="false"
>
<el-form :model="roleForm" ref="addFormRef" label-width="80px" class="add-form">
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="角色" prop="status">
<ElSelect
v-model="roleForm.role_ids"
multiple
class="account-select"
placeholder="请选择角色"
>
<ElOption
v-for="account in roleOptions"
:key="account.id"
:label="account.name"
:value="account.id"
>
<span>{{ account.name }}</span>
</ElOption>
</ElSelect>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="系统" prop="status">
<ElSelect
v-model="roleForm.system_ids"
multiple
class="account-select"
placeholder="请选择系统"
>
<ElOption
v-for="account in systemsList"
:key="account.id"
:label="account.name"
:value="account.id"
>
<span>{{ account.name }}</span>
</ElOption>
</ElSelect>
</el-form-item>
</el-col>
</el-row>
<div class="form-submit-row">
<el-button type="primary" size="large" style="width: 70%" @click="handleRoleSubmit"
>提交</el-button
>
</div>
</el-form>
</el-dialog>
</template>
<script setup lang="ts">
@@ -200,6 +308,9 @@
import { ElImageViewer } from 'element-plus'
import { manageService } from '@/api/adminApi'
import { useAuth } from '@/composables/useAuth'
import { systemService } from '@/api/systemApi'
import { formatMenuTitle } from '@/router/utils/utils'
import { UserService } from '@/api/usersApi'
const { hasAuth } = useAuth()
const selectValue = ref<string[]>([])
const keyword = ref<string>('')
@@ -208,6 +319,13 @@
const detailDialogVisible = ref(false)
const plzyDialogVisible = ref(false)
const AddDialogVisible = ref(false)
const permissionDialog = ref(false)
const roleDialogVisible = ref(false)
// 权限树数据(来自接口 getPermissionMenuList)
const permissionMenuList = ref<any[]>([])
const treeRef = ref()
const isExpandAll = ref(true)
const isSelectAll = ref(false)
const pagination = reactive({
currentPage: 1,
pageSize: 10,
@@ -232,7 +350,24 @@
phone: '',
status: ''
})
const roleForm = reactive({
user_id: '',
role_ids: '',
system_ids: ''
})
const userOptions = ref<any>()
//系统
const systemsList = ref<any>()
const getsystemsList = async () => {
const response = await UserService.getsystemsList()
systemsList.value = response || []
}
//角色下拉框
const roleOptions = ref<any>()
const getroleOptions = async () => {
const response = await systemService.getRoleSelectList()
roleOptions.value = response || []
}
const getuserList = async () => {
// 这里调用获取用户列表的接口
const res = await manageService.presetusers()
@@ -260,7 +395,7 @@
size: pageSize
}
if (selectValue.value) {
params.id = selectValue.value
params.status = selectValue.value
}
if (keyword.value) {
params.keyword = keyword.value
@@ -327,6 +462,187 @@
zyForm.customer_id = row.id
detailDialogVisible.value = true
}
// 显示角色&系统对话框
const showroleDialog = (row: any) => {
getsystemsList()
getroleOptions()
roleDialogVisible.value = true
roleForm.user_id = row.id
roleForm.role_ids = row.role_ids || []
roleForm.system_ids = row.system_ids || []
}
// 提交角色&系统
const handleRoleSubmit = async () => {
const res = await manageService.userSetRole(roleForm)
ElMessage.success(res?.message || '角色&系统设置成功!')
roleDialogVisible.value = false
gettableList()
}
// 显示权限对话框
const userId = ref('')
const showPermissionDialog = async (row: any) => {
permissionDialog.value = true
userId.value = row.id
try {
const res = await systemService.getPermissionMenuList({ user_id: row.id })
// 兼容不同返回结构(data/records/直接数组)
const list = (res as any)?.data ?? (res as any)?.records ?? (res as any)
permissionMenuList.value = Array.isArray(list) ? list : []
// 根据接口返回的 selected 字段设置默认选中
nextTick(() => {
const keys = buildDefaultCheckedKeys(permissionMenuList.value)
treeRef.value?.setCheckedKeys(keys)
handleTreeCheck()
})
} catch (e) {
permissionMenuList.value = []
}
}
// 构建默认选中的 key 列表(与 node-key="id" 一致)
const buildDefaultCheckedKeys = (source: any[]): Array<string | number> => {
const keys: Array<string | number> = []
const walk = (node: any) => {
if (node?.meta?.selected) {
keys.push(node.id)
}
if (Array.isArray(node?.meta?.authList)) {
node.meta.authList.forEach((auth: any) => {
if (auth?.selected) {
keys.push(`${node.id}__auth__${auth.authMark}`)
}
})
}
if (Array.isArray(node?.children)) node.children.forEach(walk)
}
source.forEach(walk)
return keys
}
// 处理菜单数据,将 authList 转换为子节点
const processedMenuList = computed(() => {
const processNode = (node: any) => {
// 规范化 id,确保树节点唯一键与 node-key 对应
const processed: any = { ...node, id: node.id }
// 如果有 authList,将其转换为子节点
if (node.meta && node.meta.authList && node.meta.authList.length) {
const authNodes = node.meta.authList.map((auth: any) => ({
// 为权限子节点提供稳定唯一 id(数字+字符串),与 node-key="id" 匹配
id: `${node.id}__auth__${auth.authMark}`,
name: `${node.name}_${auth.authMark}`,
label: auth.title,
auth_mark: auth.authMark,
auth_id: auth.id,
isAuth: true,
checked: auth.selected || false
}))
processed.children = processed.children ? [...processed.children, ...authNodes] : authNodes
}
// 递归处理子节点
if (processed.children) {
processed.children = processed.children.map(processNode)
}
return processed
}
const source = permissionMenuList.value || []
return source.map(processNode)
})
const defaultProps = {
children: 'children',
label: (data: any) => formatMenuTitle(data.meta?.title) || ''
}
const toggleExpandAll = () => {
const tree = treeRef.value
if (!tree) return
// 使用store.nodesMap直接控制所有节点的展开状态
const nodes = tree.store.nodesMap
for (const node in nodes) {
nodes[node].expanded = !isExpandAll.value
}
isExpandAll.value = !isExpandAll.value
}
const toggleSelectAll = () => {
const tree = treeRef.value
if (!tree) return
if (!isSelectAll.value) {
// 全选:获取所有节点的key并设置为选中
const allKeys = getAllNodeKeys(processedMenuList.value)
tree.setCheckedKeys(allKeys)
} else {
// 取消全选:清空所有选中
tree.setCheckedKeys([])
}
isSelectAll.value = !isSelectAll.value
}
const getAllNodeKeys = (nodes: any[]): Array<string | number> => {
const keys: Array<string | number> = []
const traverse = (nodeList: any[]) => {
nodeList.forEach((node) => {
if (node.id !== undefined && node.id !== null) {
keys.push(node.id)
}
if (node.children && node.children.length > 0) {
traverse(node.children)
}
})
}
traverse(nodes)
return keys
}
const handleTreeCheck = () => {
const tree = treeRef.value
if (!tree) return
// 使用树组件的getCheckedKeys方法获取选中的节点
const checkedKeys = tree.getCheckedKeys()
const allKeys = getAllNodeKeys(processedMenuList.value)
// 判断是否全选:选中的节点数量等于总节点数量
isSelectAll.value = checkedKeys.length === allKeys.length && allKeys.length > 0
}
const getSelectedIds = () => {
const tree = treeRef.value
if (!tree) return { menuIds: [], authIds: [] }
const checkedNodes = tree.getCheckedNodes?.(false) || []
const halfCheckedNodes = tree.getHalfCheckedNodes?.() || []
const allNodes = [...checkedNodes, ...halfCheckedNodes]
const menuIds = Array.from(
new Set(
allNodes
.filter((n: any) => !n.isAuth && n.id !== undefined && n.id !== null)
.map((n: any) => n.id)
)
)
const authIds = Array.from(
new Set(
checkedNodes.filter((n: any) => n.isAuth && n.auth_id != null).map((n: any) => n.auth_id)
)
)
const ids = Array.from(new Set([...(menuIds as any[]), ...(authIds as any[])]))
return { menuIds, authIds, ids }
}
const savePermission = async () => {
const { ids } = getSelectedIds()
const res = await manageService.userMenuAuth({
user_id: userId.value,
resources: ids
})
ElMessage.success(res?.message || '权限保存成功')
permissionDialog.value = false
}
// 选中的行数据
const selectedRows = ref<any[]>([])
// 处理表格行选择变化
@@ -390,7 +706,6 @@
height: 40px;
border-radius: 50%;
transition: opacity 0.2s;
&:hover {
opacity: 0.8;
}
+2 -2
View File
@@ -36,11 +36,11 @@
<div class="form">
<!-- 登录方式切换 tab -->
<div class="login-type-tabs">
<!-- <span
<span
:class="{ active: activeLoginType === 'password' }"
@click="activeLoginType = 'password'"
>账号密码登录</span
> -->
>
<span
:class="{ active: activeLoginType === 'qrcode' }"
@click="activeLoginType = 'qrcode'"
+1 -1
View File
@@ -151,7 +151,7 @@
type="danger"
underline="never"
@click="delectfn(scope.row)"
v-if="hasAuth('crm_customer@follow_delete')"
v-if="hasAuth('crm_customer@delete')"
>
<i class="iconfont-sys">&#xe6d4;</i> 删除
</el-link>