feat: 搭建后台管理框架

This commit is contained in:
Vicky committed 2026-10-13 13:58:05 +08:00
1 parent 6d28bab72b
commit 40546bfd01
238 files changed
+18160 -3837

No files matched your search

+7 -2
View File
@@ -4,10 +4,15 @@ import { useRoute } from 'vue-router'
import { customMenus } from '@/data/customMenus'
const route = useRoute()
const menu = computed(() => customMenus.find(item => item.key === route.meta.menuKey))
const menu = computed(() => customMenus.find((item) => item.key === route.meta.menuKey))
</script>
<template>
<div class="page-heading"><div><div class="eyebrow">自定义菜单</div><h1>{{menu?.title || '菜单页面'}}</h1><p>这是「{{menu?.title}}」菜单的前端演示页面。</p></div></div>
<div class="page-heading"
><div
><div class="eyebrow">自定义菜单</div><h1>{{ menu?.title || '菜单页面' }}</h1
><p>这是「{{ menu?.title }}」菜单的前端演示页面。</p></div
></div
>
<section class="panel"><el-empty description="菜单已创建,可以继续配置页面内容。" /></section>
</template>
+25 -4
View File
@@ -4,11 +4,25 @@ import { menuIconNames } from '@/data/customMenus'
import { useMenuManagement } from '@/views/permission/composables/useMenuManagement'
import PageHeading from '@/views/components/PageHeading.vue'
const { selectedKey, selectedParent, form, menuTree, parentOptions, resetForm, selectNode, saveMenu, deleteMenu } = useMenuManagement()
const {
selectedKey,
selectedParent,
form,
menuTree,
parentOptions,
resetForm,
selectNode,
saveMenu,
deleteMenu
} = useMenuManagement()
</script>
<template>
<PageHeading eyebrow="后台管理" title="菜单管理" description="维护系统菜单结构、路由和按钮权限。" />
<PageHeading
eyebrow="后台管理"
title="菜单管理"
description="维护系统菜单结构、路由和按钮权限。"
/>
<div class="menu-editor-layout">
<section class="panel menu-tree-panel">
@@ -30,7 +44,9 @@ const { selectedKey, selectedParent, form, menuTree, parentOptions, resetForm, s
<section class="panel menu-form-panel">
<div class="panel-heading">
<div><h2>{{ selectedKey ? '编辑菜单权限' : '新增菜单权限' }}</h2></div>
<div
><h2>{{ selectedKey ? '编辑菜单权限' : '新增菜单权限' }}</h2></div
>
<el-button v-if="selectedKey" type="danger" plain @click="deleteMenu">删除菜单</el-button>
</div>
<el-form label-width="110px" label-position="right" class="menu-editor-form">
@@ -64,7 +80,12 @@ const { selectedKey, selectedParent, form, menuTree, parentOptions, resetForm, s
</el-select>
</el-form-item>
<el-form-item label="使用方法">
<el-input v-model="form.method" type="textarea" :rows="3" placeholder="可填写按钮权限对应的操作说明" />
<el-input
v-model="form.method"
type="textarea"
:rows="3"
placeholder="可填写按钮权限对应的操作说明"
/>
</el-form-item>
<el-form-item label="菜单排序">
<el-input-number v-model="form.sort" :min="1" :max="999" />
+22 -3
View File
@@ -3,11 +3,24 @@ import { Plus } from '@element-plus/icons-vue'
import { useRoleCategories } from '@/views/permission/composables/useRoleCategories'
import PageHeading from '@/views/components/PageHeading.vue'
const { dialogVisible, name, editingId, tableData, openCreateDialog, editCategory, saveCategory, deleteCategory } = useRoleCategories()
const {
dialogVisible,
name,
editingId,
tableData,
openCreateDialog,
editCategory,
saveCategory,
deleteCategory
} = useRoleCategories()
</script>
<template>
<PageHeading eyebrow="后台管理" title="角色分类" description="维护角色分类,方便按职责整理权限角色。">
<PageHeading
eyebrow="后台管理"
title="角色分类"
description="维护角色分类,方便按职责整理权限角色。"
>
<el-button type="primary" class="primary-button" @click="openCreateDialog">
<el-icon><Plus /></el-icon>
新增分类
@@ -15,7 +28,13 @@ const { dialogVisible, name, editingId, tableData, openCreateDialog, editCategor
</PageHeading>
<section class="panel">
<el-table :data="tableData" row-key="id" border class="drama-el-table" empty-text="暂无角色分类">
<el-table
:data="tableData"
row-key="id"
border
class="drama-el-table"
empty-text="暂无角色分类"
>
<el-table-column type="index" label="序号" width="100" />
<el-table-column prop="id" label="ID" width="140" />
<el-table-column prop="name" label="分类名称" min-width="260" />
+20 -6
View File
@@ -5,7 +5,6 @@ import PageHeading from '@/views/components/PageHeading.vue'
const {
roleData,
rolePermissions,
selectedRole,
selectedRoleRecord,
canEditSelectedRole,
@@ -15,12 +14,16 @@ const {
newRoleName,
selectRole,
handleCheck,
addRole,
addRole
} = useRolePermissionEditor()
</script>
<template>
<PageHeading eyebrow="后台管理" title="角色权限配置" description="角色等级数字越小权限越高,低等级角色不能修改高等级角色。">
<PageHeading
eyebrow="后台管理"
title="角色权限配置"
description="角色等级数字越小权限越高,低等级角色不能修改高等级角色。"
>
<el-button type="primary" class="primary-button" @click="dialogVisible = true">
<el-icon><Plus /></el-icon>
新增角色
@@ -30,7 +33,9 @@ const {
<div class="role-permission-layout">
<section class="panel role-table-panel">
<div class="panel-heading">
<div><h2>角色列表({{ roleData.length }})</h2></div>
<div
><h2>角色列表({{ roleData.length }})</h2></div
>
</div>
<el-table
:data="roleData"
@@ -80,9 +85,18 @@ const {
<el-dialog v-model="dialogVisible" title="新增角色" width="440px">
<el-form label-position="top">
<el-form-item label="角色名称" required>
<el-input v-model="newRoleName" maxlength="30" placeholder="请输入角色名称" @keyup.enter="addRole" />
<el-input
v-model="newRoleName"
maxlength="30"
placeholder="请输入角色名称"
@keyup.enter="addRole"
/>
</el-form-item>
<el-alert title="新角色等级将低于当前账号,等级数字越大权限越低。" type="info" :closable="false" />
<el-alert
title="新角色等级将低于当前账号,等级数字越大权限越低。"
type="info"
:closable="false"
/>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
@@ -5,38 +5,102 @@ import { useRbac } from '../../../stores/rbac'
import router, { registerCustomMenu } from '../../../router'
const builtInMenus = [
{ id: 'builtin-content', label: '内容管理', children: [
{ id: 'builtin-dramas', label: '短剧管理', route: '/dramas', permission: '查看短剧列表' },
{ id: 'builtin-episodes', label: '剧集管理', route: '/episodes', permission: '查看剧集列表' },
] },
{ id: 'builtin-admin', label: '后台管理', children: [
{ id: 'builtin-access', label: '角色权限配置', route: '/access', permission: '角色与权限配置' },
{ id: 'builtin-users', label: '管理员用户列表', route: '/admin-users', permission: '管理员用户列表' },
{ id: 'builtin-menus', label: '菜单管理', route: '/menus', permission: '菜单管理' },
{ id: 'builtin-categories', label: '角色分类', route: '/role-categories', permission: '角色分类管理' },
] },
{
id: 'builtin-content',
label: '内容管理',
children: [
{
id: 'builtin-dramas',
label: '短剧管理',
route: '/content/dramas',
permission: '查看短剧列表'
},
{
id: 'builtin-episodes',
label: '剧集管理',
route: '/content/episodes',
permission: '查看剧集列表'
}
]
},
{
id: 'builtin-admin',
label: '后台管理',
children: [
{
id: 'builtin-access',
label: '角色权限配置',
route: '/system/access',
permission: '角色与权限配置'
},
{
id: 'builtin-users',
label: '管理员用户列表',
route: '/system/admin-users',
permission: '管理员用户列表'
},
{ id: 'builtin-menus', label: '菜单管理', route: '/system/menus', permission: '菜单管理' },
{
id: 'builtin-categories',
label: '角色分类',
route: '/system/role-categories',
permission: '角色分类管理'
}
]
}
]
function buildCustomNodes(parent) {
return customMenus.filter(menu => (menu.parent || '后台管理') === parent).map(menu => ({
id: menu.key,
label: menu.title,
menu,
children: buildCustomNodes(menu.title),
}))
return customMenus
.filter((menu) => (menu.parent || '后台管理') === parent)
.map((menu) => ({
id: menu.key,
label: menu.title,
menu,
children: buildCustomNodes(menu.title)
}))
}
export function useMenuManagement() {
const { createPermission, removePermission } = useRbac()
const selectedKey = ref('')
const selectedParent = ref('后台管理')
const form = reactive({ key: '', title: '', route: '', permission: '', parent: '后台管理', type: '页面', icon: 'Document', method: '', sort: 1 })
const menuTree = computed(() => builtInMenus.map(group => ({ ...group, children: [...group.children, ...buildCustomNodes(group.label)] })))
const parentOptions = computed(() => ['内容管理', '后台管理', ...customMenus.filter(menu => menu.type !== '按钮').map(menu => menu.title)])
const form = reactive({
key: '',
title: '',
route: '',
permission: '',
parent: '后台管理',
type: '页面',
icon: 'Document',
method: '',
sort: 1
})
const menuTree = computed(() =>
builtInMenus.map((group) => ({
...group,
children: [...group.children, ...buildCustomNodes(group.label)]
}))
)
const parentOptions = computed(() => [
'内容管理',
'后台管理',
...customMenus.filter((menu) => menu.type !== '按钮').map((menu) => menu.title)
])
function resetForm(parent = selectedParent.value) {
selectedKey.value = ''
Object.assign(form, { key: '', title: '', route: '', permission: '', parent, type: '页面', icon: 'Document', method: '', sort: 1 })
Object.assign(form, {
key: '',
title: '',
route: '',
permission: '',
parent,
type: '页面',
icon: 'Document',
method: '',
sort: 1
})
}
function selectNode(node) {
if (node.menu) {
@@ -48,7 +112,7 @@ export function useMenuManagement() {
permission: node.menu.permission || `访问菜单:${node.menu.title}`,
type: node.menu.type || '页面',
method: node.menu.method || '',
sort: node.menu.sort || 1,
sort: node.menu.sort || 1
})
} else if (node.children) {
selectedParent.value = node.label
@@ -58,11 +122,13 @@ export function useMenuManagement() {
function saveMenu() {
if (!form.title.trim()) return ElMessage.warning('请填写菜单名称')
if (form.type !== '按钮' && !form.route.trim()) return ElMessage.warning('请填写对应路由')
const duplicate = customMenus.some(menu => menu.title === form.title.trim() && menu.key !== selectedKey.value)
const duplicate = customMenus.some(
(menu) => menu.title === form.title.trim() && menu.key !== selectedKey.value
)
if (duplicate) return ElMessage.warning('菜单名称已存在')
const isNew = !selectedKey.value
const key = selectedKey.value || `${Date.now()}`
const existingMenu = customMenus.find(menu => menu.key === key)
const existingMenu = customMenus.find((menu) => menu.key === key)
const permission = form.permission.trim() || `访问菜单:${form.title.trim()}`
if (existingMenu) {
if (router.hasRoute(`custom-menu-${key}`)) router.removeRoute(`custom-menu-${key}`)
@@ -70,26 +136,40 @@ export function useMenuManagement() {
removePermission(existingMenu.permission)
createPermission(permission)
}
Object.assign(existingMenu, { ...form, key, title: form.title.trim(), permission, route: form.route.trim(), sort: Number(form.sort) || 1 })
Object.assign(existingMenu, {
...form,
key,
title: form.title.trim(),
permission,
route: form.route.trim(),
sort: Number(form.sort) || 1
})
} else {
customMenus.push({ ...form, key, title: form.title.trim(), permission, route: form.route.trim(), sort: Number(form.sort) || 1 })
customMenus.push({
...form,
key,
title: form.title.trim(),
permission,
route: form.route.trim(),
sort: Number(form.sort) || 1
})
createPermission(permission)
}
const savedMenu = customMenus.find(menu => menu.key === key)
const savedMenu = customMenus.find((menu) => menu.key === key)
if (savedMenu?.type !== '按钮') registerCustomMenu(savedMenu)
saveCustomMenus()
selectedKey.value = key
ElMessage.success(isNew ? '菜单已添加' : '菜单已保存')
}
async function deleteMenu() {
const menu = customMenus.find(item => item.key === selectedKey.value)
const menu = customMenus.find((item) => item.key === selectedKey.value)
if (!menu) return ElMessage.warning('请先在左侧选择一个自定义菜单')
try {
await ElMessageBox.confirm(`确认删除「${menu.title}」菜单吗?`, '删除确认', {
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
confirmButtonClass: 'el-button--danger'
})
customMenus.splice(customMenus.indexOf(menu), 1)
removePermission(menu.permission)
@@ -102,5 +182,15 @@ export function useMenuManagement() {
}
}
return { selectedKey, selectedParent, form, menuTree, parentOptions, resetForm, selectNode, saveMenu, deleteMenu }
return {
selectedKey,
selectedParent,
form,
menuTree,
parentOptions,
resetForm,
selectNode,
saveMenu,
deleteMenu
}
}
@@ -9,12 +9,14 @@ export function useRoleCategories() {
const categories = ref([
{ id: 1, name: '系统管理员', description: '负责账号、权限和菜单配置' },
{ id: 2, name: '内容管理', description: '负责短剧与剧集内容维护' },
{ id: 3, name: '数据分析', description: '负责查看运营数据' },
{ id: 3, name: '数据分析', description: '负责查看运营数据' }
])
const tableData = computed(() => categories.value.map(category => ({
...category,
roleCount: roleData.filter(role => role.category === category.name).length,
})))
const tableData = computed(() =>
categories.value.map((category) => ({
...category,
roleCount: roleData.filter((role) => role.category === category.name).length
}))
)
function openCreateDialog() {
editingId.value = null
@@ -29,14 +31,20 @@ export function useRoleCategories() {
function saveCategory() {
const value = name.value.trim()
if (!value) return ElMessage.warning('请输入分类名称')
if (categories.value.some(category => category.name === value && category.id !== editingId.value)) {
if (
categories.value.some(
(category) => category.name === value && category.id !== editingId.value
)
) {
return ElMessage.warning('分类名称已存在')
}
if (editingId.value) {
const category = categories.value.find(item => item.id === editingId.value)
const category = categories.value.find((item) => item.id === editingId.value)
const oldName = category?.name
if (category) category.name = value
roleData.forEach(role => { if (role.category === oldName) role.category = value })
roleData.forEach((role) => {
if (role.category === oldName) role.category = value
})
} else {
categories.value.push({ id: Date.now(), name: value, description: '自定义角色分类' })
}
@@ -49,14 +57,23 @@ export function useRoleCategories() {
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
confirmButtonClass: 'el-button--danger'
})
categories.value = categories.value.filter(item => item.id !== category.id)
categories.value = categories.value.filter((item) => item.id !== category.id)
ElMessage.success('分类已删除')
} catch {
// 用户取消时不删除分类。
}
}
return { dialogVisible, name, editingId, tableData, openCreateDialog, editCategory, saveCategory, deleteCategory }
return {
dialogVisible,
name,
editingId,
tableData,
openCreateDialog,
editCategory,
saveCategory,
deleteCategory
}
}
@@ -15,33 +15,49 @@ export function useRolePermissionEditor() {
const treeRef = ref()
const syncingTree = ref(false)
const permissionTree = computed(() => {
const groups = permissionGroups.map(group => ({
const groups = permissionGroups.map((group) => ({
id: `group-${group.name}`,
label: group.name,
children: group.items.map(permission => ({ id: permission, label: permission, permission })),
children: group.items.map((permission) => ({ id: permission, label: permission, permission }))
}))
if (customMenus.length) {
groups.push({
id: 'group-custom-menus',
label: '自定义菜单',
children: customMenus.map(menu => ({ id: menu.permission, label: menu.title, permission: menu.permission })),
children: customMenus.map((menu) => ({
id: menu.permission,
label: menu.title,
permission: menu.permission
}))
})
}
return groups
})
const currentLevel = computed(() => roleData.find(role => role.name === currentRole.value)?.level || 1)
const selectedRoleRecord = computed(() => roleData.find(role => role.name === selectedRole.value))
const canEditSelectedRole = computed(() => (selectedRoleRecord.value?.level || 1) >= currentLevel.value)
const currentLevel = computed(
() => roleData.find((role) => role.name === currentRole.value)?.level || 1
)
const selectedRoleRecord = computed(() =>
roleData.find((role) => role.name === selectedRole.value)
)
const canEditSelectedRole = computed(
() => (selectedRoleRecord.value?.level || 1) >= currentLevel.value
)
watch(selectedRole, async role => {
syncingTree.value = true
await nextTick()
treeRef.value?.setCheckedKeys(rolePermissions[role] || [])
await nextTick()
syncingTree.value = false
}, { immediate: true })
watch(
selectedRole,
async (role) => {
syncingTree.value = true
await nextTick()
treeRef.value?.setCheckedKeys(rolePermissions[role] || [])
await nextTick()
syncingTree.value = false
},
{ immediate: true }
)
function selectRole(row) { selectedRole.value = row.name }
function selectRole(row) {
selectedRole.value = row.name
}
function handleCheck(data, checked) {
if (!syncingTree.value && data.permission && canEditSelectedRole.value) {
updateRolePermission(selectedRole.value, data.permission, checked)
@@ -50,10 +66,15 @@ export function useRolePermissionEditor() {
function addRole() {
const name = newRoleName.value.trim()
if (!name) return ElMessage.warning('请输入角色名称')
if (roleData.some(role => role.name === name)) return ElMessage.warning('角色名称已存在')
if (roleData.some((role) => role.name === name)) return ElMessage.warning('角色名称已存在')
roleData.push({
id: Date.now(), name, description: '自定义权限角色', memberCount: 0,
category: '自定义角色', level: currentLevel.value + 1, status: '启用',
id: Date.now(),
name,
description: '自定义权限角色',
memberCount: 0,
category: '自定义角色',
level: currentLevel.value + 1,
status: '启用'
})
createRole(name)
selectedRole.value = name
@@ -63,7 +84,17 @@ export function useRolePermissionEditor() {
}
return {
roleData, rolePermissions, selectedRole, selectedRoleRecord, canEditSelectedRole,
permissionTree, treeRef, dialogVisible, newRoleName, selectRole, handleCheck, addRole,
roleData,
rolePermissions,
selectedRole,
selectedRoleRecord,
canEditSelectedRole,
permissionTree,
treeRef,
dialogVisible,
newRoleName,
selectRole,
handleCheck,
addRole
}
}