Compare commits
No commits in common. "ca4c17042d0cc14175cde2748b2dc15bf46578a8" and "86c7d75963ea1fd74767887ec1ac578db6340a78" have entirely different histories.
ca4c17042d
...
86c7d75963
@ -1,39 +1,24 @@
|
||||
export interface MenuItem {
|
||||
title: string
|
||||
path: string
|
||||
permission: string // 需要的权限标识(list类型)
|
||||
}
|
||||
|
||||
export interface MenuGroup {
|
||||
name: string
|
||||
title: string
|
||||
icon: string
|
||||
child: MenuItem[]
|
||||
}
|
||||
|
||||
const menus: MenuGroup[] = [
|
||||
export default [
|
||||
{
|
||||
name: 'system',
|
||||
title: '系统管理',
|
||||
icon: 'Operation',
|
||||
child: [
|
||||
{ title: '系统配置', path: '/system/config', permission: 'config:list' },
|
||||
{ title: '用户管理', path: '/system/user', permission: 'user:list' },
|
||||
{ title: '角色管理', path: '/system/role', permission: 'role:list' },
|
||||
{ title: '博客文章', path: '/system/article', permission: 'article:list' },
|
||||
{ title: '分析统计', path: '/system/statistics', permission: 'article:list' }
|
||||
{ title: '系统配置', path: '/system/config' },
|
||||
{ title: '用户管理', path: '/system/user' },
|
||||
{ title: '角色管理', path: '/system/role' },
|
||||
{ title: '博客文章', path: '/system/article' },
|
||||
{ title: '分析统计', path: '/system/statistics' }
|
||||
]
|
||||
},{
|
||||
name: 'api',
|
||||
title: 'API数据',
|
||||
icon: 'Histogram',
|
||||
child: [
|
||||
{ title: '一言', path: '/api/hitokoto', permission: 'hitokoto:list' },
|
||||
{ title: '照片墙', path: '/api/photoWall', permission: 'photoWall:list' },
|
||||
{ title: '图片资源库', path: '/api/sourceImage', permission: 'sourceImage:list' },
|
||||
{ title: '歌曲库', path: '/api/music', permission: 'music:list' }
|
||||
{ title: '一言', path: '/api/hitokoto' },
|
||||
{ title: '照片墙', path: '/api/photoWall' },
|
||||
{ title: '图片资源库', path: '/api/sourceImage' },
|
||||
{ title: '歌曲库', path: '/api/music' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export default menus
|
||||
@ -10,7 +10,6 @@ import 'element-plus/theme-chalk/el-message-box.css'
|
||||
import 'element-plus/theme-chalk/el-image-viewer.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import { permissionDirective } from '@/utils/permission'
|
||||
|
||||
// 全局路由导航前置守卫
|
||||
router.beforeEach(function (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||
@ -33,5 +32,4 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.use(router)
|
||||
.use(store)
|
||||
.directive('loading', ElLoading.directive)
|
||||
.directive('permission', permissionDirective)
|
||||
.mount('#app')
|
||||
|
||||
@ -2,15 +2,9 @@ export interface SystemRoleModel {
|
||||
_id: string | null
|
||||
name: string | null // 角色名称
|
||||
description: string | null // 描述
|
||||
permissions: string[] // 权限标识列表
|
||||
methods: string[] // 允许的请求类型(优先级3)
|
||||
includeUri: string[] // 包含的URI(优先级2)
|
||||
excludeUri: string[] // 排除的URI(优先级1)
|
||||
createdAt?: Date // 创建时间
|
||||
updatedAt?: Date // 更新时间
|
||||
}
|
||||
|
||||
export interface PermissionTreeNode {
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
leaf: boolean
|
||||
children: PermissionTreeNode[]
|
||||
}
|
||||
@ -8,8 +8,7 @@ class Store {
|
||||
state: StateType = {
|
||||
loginInfo: {
|
||||
userInfo: null,
|
||||
token: localStorage.getItem('login_token'),
|
||||
permissions: []
|
||||
token: localStorage.getItem('login_token')
|
||||
},
|
||||
tabs: [],
|
||||
activeTab: null,
|
||||
@ -22,15 +21,12 @@ class Store {
|
||||
* @param {Object} state
|
||||
* @param {UserInfo} data 登录数据
|
||||
*/
|
||||
login(state: StateType, data: {token?: string, userInfo: UserInfo, permissions?: string[]}): void {
|
||||
login(state: StateType, data: {token?: string, userInfo: UserInfo}): void {
|
||||
if (data.token) {
|
||||
localStorage.setItem('login_token', data.token)
|
||||
state.loginInfo.token = data.token
|
||||
}
|
||||
state.loginInfo.userInfo = data.userInfo
|
||||
if (data.permissions) {
|
||||
state.loginInfo.permissions = data.permissions
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 注销
|
||||
@ -40,7 +36,6 @@ class Store {
|
||||
localStorage.removeItem('login_token')
|
||||
state.loginInfo.token = null
|
||||
state.loginInfo.userInfo = null
|
||||
state.loginInfo.permissions = []
|
||||
},
|
||||
/**
|
||||
* 添加导航页签
|
||||
|
||||
@ -15,7 +15,6 @@ export interface StateType {
|
||||
loginInfo: { // 登录信息
|
||||
userInfo: null | UserInfo
|
||||
token: null | string
|
||||
permissions: string[] // 用户权限列表
|
||||
}
|
||||
tabs: TabItem[] // 导航页签
|
||||
activeTab: string | null // 当前激活的页签
|
||||
|
||||
@ -43,7 +43,11 @@ http.interceptors.response.use(res => {
|
||||
ElMessage.error('服务器内部错误')
|
||||
} else if (err.response?.status >= 400) {
|
||||
const message = err.response.data?.message
|
||||
message && ElMessage.error(message)
|
||||
if (typeof message === 'string') {
|
||||
ElMessage.error(message)
|
||||
} else if (Array.isArray(message)) {
|
||||
ElMessage.error(message.join('<br/>'))
|
||||
}
|
||||
if (err.response.status === 403) {
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
import type { Directive } from 'vue'
|
||||
import store from '@/store'
|
||||
|
||||
/**
|
||||
* 检查用户是否具备指定权限
|
||||
* 支持上下级关系:如果用户拥有 'user' 权限,则 'user:list' 也视为拥有
|
||||
*/
|
||||
export function hasPermission(permission: string): boolean {
|
||||
const permissions: string[] = store.state.loginInfo.permissions
|
||||
if (!permissions || !permissions.length) return false
|
||||
// 直接匹配
|
||||
if (permissions.includes(permission)) return true
|
||||
// 上级权限匹配:如果权限是 'user:list',检查用户是否拥有 'user'
|
||||
const colonIndex = permission.indexOf(':')
|
||||
if (colonIndex !== -1) {
|
||||
const parent = permission.substring(0, colonIndex)
|
||||
if (permissions.includes(parent)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* v-permission 自定义指令
|
||||
* 用法:v-permission="'user:save'" 或 v-permission="['user:save', 'user:delete']"
|
||||
* 当传入数组时,满足其中任一权限即可显示
|
||||
*/
|
||||
export const permissionDirective: Directive<HTMLElement, string | string[]> = {
|
||||
mounted(el, binding) {
|
||||
const value = binding.value
|
||||
if (!value) return
|
||||
const permissions = Array.isArray(value) ? value : [value]
|
||||
const hasPerm = permissions.some(p => hasPermission(p))
|
||||
if (!hasPerm) {
|
||||
el.parentNode?.removeChild(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,12 +81,10 @@
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import allMenus from '../config/menu'
|
||||
import type { MenuGroup } from '../config/menu'
|
||||
import menus from '../config/menu'
|
||||
import http from '@/utils/http'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { VForm } from '@/types'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
@ -99,16 +97,6 @@ const openMenuNames = ref<string[]>([])
|
||||
const mainViewReady = ref(false)
|
||||
|
||||
const isDark = ref(false)
|
||||
|
||||
const menus = computed((): MenuGroup[] => {
|
||||
return allMenus
|
||||
.map(group => ({
|
||||
...group,
|
||||
child: group.child.filter(item => hasPermission(item.permission))
|
||||
}))
|
||||
.filter(group => group.child.length > 0)
|
||||
})
|
||||
|
||||
function applyDarkMode(dark: boolean) {
|
||||
isDark.value = dark
|
||||
document.documentElement.classList.toggle('dark', dark)
|
||||
@ -182,7 +170,7 @@ if (!store.state.loginInfo.token) {
|
||||
router.push('/login')
|
||||
} else {
|
||||
http.post<{token: string}, any>('/common/verifyToken', {token: store.state.loginInfo.token}).then(data => {
|
||||
store.commit('login', {userInfo: data.userInfo, permissions: data.permissions})
|
||||
store.commit('login', {userInfo: data.userInfo})
|
||||
mainViewReady.value = true
|
||||
}).catch(() => {
|
||||
router.push('/login')
|
||||
|
||||
@ -26,19 +26,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import allMenus from '../config/menu'
|
||||
import type { MenuGroup } from '../config/menu'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const menus = computed((): MenuGroup[] => {
|
||||
return allMenus
|
||||
.map(group => ({
|
||||
...group,
|
||||
child: group.child.filter(item => hasPermission(item.permission))
|
||||
}))
|
||||
.filter(group => group.child.length > 0)
|
||||
})
|
||||
import menus from '../config/menu'
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.welcome-page {
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn-container">
|
||||
<el-button type="primary" @click="addModal = true" v-permission="'hitokoto:save'">添加</el-button>
|
||||
<el-button type="danger" @click="deleteAll" v-permission="'hitokoto:delete'">删除</el-button>
|
||||
<el-button type="primary" @click="addModal = true">添加</el-button>
|
||||
<el-button type="danger" @click="deleteAll">删除</el-button>
|
||||
<div class="search-btn">
|
||||
<el-button type="primary" @click="loadDataBase(true)" icon="Search">搜索</el-button>
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
|
||||
@ -23,8 +23,8 @@
|
||||
</el-form>
|
||||
<div class="btn-container">
|
||||
<el-button type="success" plain icon="VideoPlay" @click="playMusic">播放</el-button>
|
||||
<el-button type="primary" icon="Upload" @click="openUploadModal" v-permission="'music:save'">上传音乐</el-button>
|
||||
<el-button type="warning" plain icon="Notebook" @click="libDrawerVisible = true" v-permission="'music:save'">歌单管理</el-button>
|
||||
<el-button type="primary" icon="Upload" @click="openUploadModal">上传音乐</el-button>
|
||||
<el-button type="warning" plain icon="Notebook" @click="libDrawerVisible = true">歌单管理</el-button>
|
||||
<div class="search-btn">
|
||||
<el-button type="primary" @click="loadDataBase(true)" icon="Search">搜索</el-button>
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
@ -72,9 +72,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" >
|
||||
<template #default="scope">
|
||||
<el-button link icon="Document" @click="updateLyric(scope.row)" title="歌词" v-permission="'music:save'"></el-button>
|
||||
<el-button link icon="Document" @click="updateLyric(scope.row)" title="歌词"></el-button>
|
||||
<el-button link icon="Download" @click="download(scope.row)" title="下载"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除" v-permission="'music:delete'"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@ -41,9 +41,9 @@
|
||||
auto-upload
|
||||
:show-file-list="false"
|
||||
style="display: inline-block;margin-right: 10px;">
|
||||
<el-button type="primary" icon="Upload" :loading="isUploading" v-permission="'photoWall:save'">上传图片</el-button>
|
||||
<el-button type="primary" icon="Upload" :loading="isUploading">上传图片</el-button>
|
||||
</el-upload>
|
||||
<el-button type="danger" @click="deleteAll" style="vertical-align: bottom" v-permission="'photoWall:delete'">批量删除</el-button>
|
||||
<el-button type="danger" @click="deleteAll" style="vertical-align: bottom">批量删除</el-button>
|
||||
<div class="search-btn">
|
||||
<el-button type="primary" @click="loadDataBase(true)" icon="Search">搜索</el-button>
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
|
||||
@ -16,9 +16,9 @@
|
||||
auto-upload
|
||||
:show-file-list="false"
|
||||
style="display: inline-block;margin-right: 10px;">
|
||||
<el-button type="primary" icon="Upload" :loading="isUploading" v-permission="'sourceImage:save'">上传图片</el-button>
|
||||
<el-button type="primary" icon="Upload" :loading="isUploading">上传图片</el-button>
|
||||
</el-upload>
|
||||
<el-button type="danger" @click="deleteAll" style="vertical-align: bottom" v-permission="'sourceImage:delete'">批量删除</el-button>
|
||||
<el-button type="danger" @click="deleteAll" style="vertical-align: bottom">批量删除</el-button>
|
||||
</div>
|
||||
<el-table class="table-container" :data="sourceImageData" v-loading="loading" stripe @selection-change="dataSelect">
|
||||
<el-table-column type="selection" width="55" />
|
||||
@ -41,7 +41,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" >
|
||||
<template #default="scope">
|
||||
<el-button link icon="EditPen" type="primary" plain @click="modifyTags(scope.row)" title="修改标签" v-permission="'sourceImage:save'"></el-button>
|
||||
<el-button link icon="EditPen" type="primary" plain @click="modifyTags(scope.row)" title="修改标签"></el-button>
|
||||
<el-button link icon="View" plain @click="preview(scope.row)" title="预览"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@ -30,8 +30,8 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn-container">
|
||||
<el-button type="primary" @click="splitWord" style="vertical-align: bottom" v-permission="'article:edit'">分词处理</el-button>
|
||||
<el-button @click="pullArticles" style="vertical-align: bottom" v-permission="'article:edit'">拉取文章</el-button>
|
||||
<el-button type="primary" @click="splitWord" style="vertical-align: bottom">分词处理</el-button>
|
||||
<el-button @click="pullArticles" style="vertical-align: bottom">拉取文章</el-button>
|
||||
<el-upload
|
||||
:action="`${apiBase}/system/deployBlog`"
|
||||
accept="application/zip"
|
||||
@ -42,8 +42,7 @@
|
||||
:on-error="uploadError"
|
||||
auto-upload
|
||||
:show-file-list="false"
|
||||
style="display: inline-block;margin-left: 10px;"
|
||||
v-permission="'system:deploy'">
|
||||
style="display: inline-block;margin-left: 10px;">
|
||||
<el-button type="primary" icon="Upload" :loading="isUploading">发布博客</el-button>
|
||||
</el-upload>
|
||||
<div class="search-btn">
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn-container">
|
||||
<el-button type="primary" @click="add" v-permission="'config:save'">添加</el-button>
|
||||
<el-button type="primary" @click="add">添加</el-button>
|
||||
<div class="search-btn">
|
||||
<el-button type="primary" @click="loadData" icon="Search">搜索</el-button>
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
@ -37,8 +37,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" >
|
||||
<template #default="scope">
|
||||
<el-button link icon="Edit" @click="update(scope.row)" title="修改" v-permission="'config:save'"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除" v-permission="'config:delete'"></el-button>
|
||||
<el-button link icon="Edit" @click="update(scope.row)" title="修改"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn-container">
|
||||
<el-button type="primary" @click="add" v-permission="'role:save'">添加</el-button>
|
||||
<el-button type="primary" @click="add">添加</el-button>
|
||||
<div class="search-btn">
|
||||
<el-button type="primary" @click="loadDataBase(true)" icon="Search">搜索</el-button>
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
@ -14,7 +14,11 @@
|
||||
</div>
|
||||
<el-table class="table-container" :data="systemRoleData" v-loading="loading" stripe>
|
||||
<el-table-column prop="name" label="角色名称" />
|
||||
<el-table-column prop="description" label="描述" />
|
||||
<el-table-column prop="methods" label="允许的请求类型" >
|
||||
<template #default="scope">
|
||||
<el-tag type="info" v-for="method in scope.row.methods" :key="method">{{method}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="创建时间" >
|
||||
<template #default="scope">
|
||||
{{ datetimeFormat(scope.row.createdAt) }}
|
||||
@ -27,8 +31,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" >
|
||||
<template #default="scope">
|
||||
<el-button link icon="Edit" @click="update(scope.row)" title="修改" v-permission="'role:save'"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除" v-permission="'role:delete'"></el-button>
|
||||
<el-button link icon="Edit" @click="update(scope.row)" title="修改"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -50,17 +54,29 @@
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="formData.description" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限" prop="permissions">
|
||||
<el-tree
|
||||
ref="permTree"
|
||||
:data="permissionTreeData"
|
||||
:props="treeProps"
|
||||
show-checkbox
|
||||
node-key="code"
|
||||
:default-checked-keys="formData.permissions"
|
||||
:check-strictly="true"
|
||||
@check="handlePermCheckWithValidate"
|
||||
/>
|
||||
<el-form-item label="允许的请求类型">
|
||||
<el-select v-model="formData.methods" multiple collapse-tags>
|
||||
<el-option value="GET">GET</el-option>
|
||||
<el-option value="POST">POST</el-option>
|
||||
<el-option value="PUT">PUT</el-option>
|
||||
<el-option value="DELETE">DELETE</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="允许的URI">
|
||||
<el-input v-model="uri.include">
|
||||
<template #append>
|
||||
<el-button icon="Plus" @click="addUri('includeUri', uri.include)"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-tag v-for="uri in formData.includeUri" :key="uri" closable @close="removeUri('includeUri', uri)">{{uri}}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="禁止的URI">
|
||||
<el-input v-model="uri.exclude">
|
||||
<template #append>
|
||||
<el-button icon="Plus" @click="addUri('excludeUri', uri.exclude)"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-tag v-for="uri in formData.excludeUri" :key="uri" closable @close="removeUri('excludeUri', uri)">{{uri}}</el-tag>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@ -79,9 +95,9 @@ import { useStore } from 'vuex'
|
||||
import { useBaseList } from '@/model/baselist'
|
||||
import { Page } from '@/model/common.dto'
|
||||
import http from '@/utils/http'
|
||||
import { SystemRoleModel, PermissionTreeNode } from '@/model/system/system-role'
|
||||
import { SystemRoleModel } from '@/model/system/system-role'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { VForm } from '@/types'
|
||||
import { VForm } from '@/types'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
@ -99,33 +115,26 @@ const ruleValidate = {
|
||||
name: [
|
||||
{ required: true, message: '请输入角色名称', trigger: 'blur' }
|
||||
],
|
||||
permissions: [
|
||||
{ validator: (rule: any, value: any, callback: any) => {
|
||||
const permissions = getPermissionsToSave()
|
||||
if (permissions.length === 0) {
|
||||
callback(new Error('至少需要勾选一项权限'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
]
|
||||
}
|
||||
const systemRoleData = ref<SystemRoleModel[]>([])
|
||||
const addModal = ref(false)
|
||||
const modalTitle = ref<string | null>(null)
|
||||
const permissionTreeData = ref<PermissionTreeNode[]>([])
|
||||
const treeProps = {
|
||||
children: 'children',
|
||||
label: 'name'
|
||||
}
|
||||
const uri = reactive<{
|
||||
include: string | null,
|
||||
exclude: string | null
|
||||
}>({
|
||||
include: null,
|
||||
exclude: null
|
||||
})
|
||||
const formData = reactive<SystemRoleModel>({
|
||||
_id: null,
|
||||
name: null,
|
||||
description: null,
|
||||
permissions: []
|
||||
methods: [],
|
||||
includeUri: [],
|
||||
excludeUri: []
|
||||
})
|
||||
const roleForm = ref<VForm>()
|
||||
const permTree = ref<any>()
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
@ -137,142 +146,49 @@ async function loadData() {
|
||||
setLoadData(loadData)
|
||||
|
||||
function add() {
|
||||
uri.include = null
|
||||
uri.exclude = null
|
||||
Object.assign(formData, {
|
||||
_id: null,
|
||||
name: null,
|
||||
description: null,
|
||||
permissions: []
|
||||
methods: [],
|
||||
includeUri: [],
|
||||
excludeUri: []
|
||||
})
|
||||
modalTitle.value = '新增角色'
|
||||
addModal.value = true
|
||||
clearValidate()
|
||||
nextTick(() => {
|
||||
permTree.value?.setCheckedKeys([])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理权限树勾选:
|
||||
* - 勾选父节点时,自动勾选所有子节点
|
||||
* - 取消父节点时,自动取消所有子节点
|
||||
* - 所有子节点都被勾选时,自动勾选父节点并取消子节点勾选(只保留父code)
|
||||
*/
|
||||
function handlePermCheck(nodeData: PermissionTreeNode, checkState: { checkedKeys: string[], halfCheckedKeys: string[] }) {
|
||||
const tree = permTree.value
|
||||
if (!tree) return
|
||||
const node = tree.getNode(nodeData.code)
|
||||
const isChecked = node.checked
|
||||
|
||||
if (!nodeData.leaf) {
|
||||
// 父节点操作:联动所有子节点
|
||||
const childCodes = nodeData.children.map((c: PermissionTreeNode) => c.code)
|
||||
if (isChecked) {
|
||||
// 勾选父节点 → 勾选所有子节点
|
||||
childCodes.forEach((code: string) => tree.setChecked(code, true, false))
|
||||
} else {
|
||||
// 取消父节点 → 取消所有子节点
|
||||
childCodes.forEach((code: string) => tree.setChecked(code, false, false))
|
||||
}
|
||||
} else {
|
||||
// 子节点操作:检查是否所有兄弟节点都被勾选
|
||||
const parentNode = findParentNode(permissionTreeData.value, nodeData.code)
|
||||
if (parentNode) {
|
||||
const allChildCodes = parentNode.children.map(c => c.code)
|
||||
const allChecked = allChildCodes.every(code => tree.getNode(code)?.checked)
|
||||
if (allChecked) {
|
||||
// 所有子节点勾选 → 勾选父节点
|
||||
tree.setChecked(parentNode.code, true, false)
|
||||
} else {
|
||||
// 不是全部 → 取消父节点
|
||||
tree.setChecked(parentNode.code, false, false)
|
||||
}
|
||||
}
|
||||
function addUri(fieldName: 'includeUri' | 'excludeUri', uriValue: string | null) {
|
||||
if(!uriValue) return
|
||||
if(formData[fieldName].indexOf(uriValue) === -1) {
|
||||
formData[fieldName].push(uriValue)
|
||||
}
|
||||
}
|
||||
|
||||
function handlePermCheckWithValidate(nodeData: PermissionTreeNode, checkState: { checkedKeys: string[], halfCheckedKeys: string[] }) {
|
||||
handlePermCheck(nodeData, checkState)
|
||||
roleForm.value?.validateField('permissions')
|
||||
}
|
||||
|
||||
function findParentNode(nodes: PermissionTreeNode[], childCode: string): PermissionTreeNode | null {
|
||||
for (const node of nodes) {
|
||||
if (node.children.some(c => c.code === childCode)) {
|
||||
return node
|
||||
function removeUri(fieldName: 'includeUri' | 'excludeUri', uriValue: string) {
|
||||
let index = formData[fieldName].indexOf(uriValue)
|
||||
if(index !== -1) {
|
||||
formData[fieldName].splice(index, 1)
|
||||
}
|
||||
const found = findParentNode(node.children, childCode)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 从树中获取要保存的 permissions:
|
||||
* 如果父节点被勾选,只传父节点code;否则传各个被勾选的子节点code
|
||||
*/
|
||||
function getPermissionsToSave(): string[] {
|
||||
const tree = permTree.value
|
||||
if (!tree) return []
|
||||
const result: string[] = []
|
||||
for (const group of permissionTreeData.value) {
|
||||
const parentNode = tree.getNode(group.code)
|
||||
if (parentNode?.checked) {
|
||||
result.push(group.code)
|
||||
} else {
|
||||
for (const child of group.children) {
|
||||
const childNode = tree.getNode(child.code)
|
||||
if (childNode?.checked) {
|
||||
result.push(child.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑角色时,根据已有 permissions 回显树的勾选状态
|
||||
* 如果有父级code(如'user'),需要勾选父节点及所有子节点
|
||||
*/
|
||||
function setTreeCheckedByPermissions(permissions: string[]) {
|
||||
nextTick(() => {
|
||||
const tree = permTree.value
|
||||
if (!tree) return
|
||||
tree.setCheckedKeys([])
|
||||
for (const perm of permissions) {
|
||||
// 查找是否是父节点
|
||||
const parentGroup = permissionTreeData.value.find(g => g.code === perm)
|
||||
if (parentGroup) {
|
||||
// 是父节点:勾选自身和所有子节点
|
||||
tree.setChecked(perm, true, false)
|
||||
parentGroup.children.forEach(c => tree.setChecked(c.code, true, false))
|
||||
} else {
|
||||
tree.setChecked(perm, true, false)
|
||||
}
|
||||
}
|
||||
// 再次检查:如果某个父级的所有子节点都勾选了,勾选父级
|
||||
for (const group of permissionTreeData.value) {
|
||||
if (!permissions.includes(group.code)) {
|
||||
const allChildChecked = group.children.length > 0 && group.children.every(c => tree.getNode(c.code)?.checked)
|
||||
if (allChildChecked) {
|
||||
tree.setChecked(group.code, true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function update(row: SystemRoleModel) {
|
||||
uri.include = null
|
||||
uri.exclude = null
|
||||
Object.assign(formData, {
|
||||
_id: row._id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
permissions: [...row.permissions]
|
||||
methods: row.methods,
|
||||
includeUri: row.includeUri,
|
||||
excludeUri: row.excludeUri
|
||||
})
|
||||
modalTitle.value = '修改角色'
|
||||
addModal.value = true
|
||||
clearValidate()
|
||||
setTreeCheckedByPermissions(row.permissions)
|
||||
}
|
||||
|
||||
function remove(row: SystemRoleModel) {
|
||||
@ -287,8 +203,7 @@ async function save() {
|
||||
roleForm.value?.validate(async (valid: boolean) => {
|
||||
if (!valid) return
|
||||
modalLoading.value = true
|
||||
const saveData = { ...formData, permissions: getPermissionsToSave() }
|
||||
await http.post<SystemRoleModel, any>('/system/role/save', saveData)
|
||||
await http.post<SystemRoleModel, any>('/system/role/save', formData)
|
||||
modalLoading.value = false
|
||||
addModal.value = false
|
||||
ElMessage.success("保存成功")
|
||||
@ -303,7 +218,4 @@ function clearValidate() {
|
||||
}
|
||||
|
||||
loadData()
|
||||
http.get<never, any>('/system/role/permissionTree').then(data => {
|
||||
permissionTreeData.value = data
|
||||
})
|
||||
</script>
|
||||
@ -6,7 +6,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="btn-container">
|
||||
<el-button type="primary" @click="add" v-permission="'user:save'">添加</el-button>
|
||||
<el-button type="primary" @click="add">添加</el-button>
|
||||
<div class="search-btn">
|
||||
<el-button type="primary" @click="loadDataBase(true)" icon="Search">搜索</el-button>
|
||||
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
|
||||
@ -27,8 +27,8 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" >
|
||||
<template #default="scope">
|
||||
<el-button link icon="Edit" @click="update(scope.row)" title="修改" v-permission="'user:save'"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除" v-permission="'user:delete'"></el-button>
|
||||
<el-button link icon="Edit" @click="update(scope.row)" title="修改"></el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user