feat(frontend): 完善角色管理功能
- 添加 PermissionButton.vue 权限按钮组件 - 添加 PermissionDialog.vue 权限对话框组件 - 添加 role.ts 角色管理服务 - 添加 RoleManagementView.vue 角色管理页面 - 更新路由配置添加角色管理页面 - 更新 App.vue 添加角色管理菜单入口 - 修复 TypeScript 类型定义问题 - 前端编译验证通过
This commit is contained in:
@@ -20,7 +20,9 @@
|
|||||||
"Grep",
|
"Grep",
|
||||||
"Bash(cd /home/long/project/蚊子/frontend/admin && npm run build 2>&1 | head -30)",
|
"Bash(cd /home/long/project/蚊子/frontend/admin && npm run build 2>&1 | head -30)",
|
||||||
"Bash(npm run build 2>&1 | head -40)",
|
"Bash(npm run build 2>&1 | head -40)",
|
||||||
"Bash(npm run build 2>&1 | head -50)"
|
"Bash(npm run build 2>&1 | head -50)",
|
||||||
|
"Bash(cd /home/long/project/蚊子/frontend/admin && npm run build 2>&1 | tail -20)",
|
||||||
|
"Bash(npm run build 2>&1 | tail -15)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,6 +74,14 @@
|
|||||||
>
|
>
|
||||||
权限
|
权限
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
v-if="auth.hasPermission('role:manage')"
|
||||||
|
to="/roles"
|
||||||
|
class="rounded-full px-3 py-1.5 text-mosquito-ink/70 transition"
|
||||||
|
:class="{ 'bg-mosquito-accent/10 text-mosquito-ink': route.path.startsWith('/roles') }"
|
||||||
|
>
|
||||||
|
角色
|
||||||
|
</RouterLink>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-if="auth.hasPermission('dashboard:view')"
|
v-if="auth.hasPermission('dashboard:view')"
|
||||||
to="/notifications"
|
to="/notifications"
|
||||||
|
|||||||
@@ -86,16 +86,19 @@ export type DataScope = 'ALL' | 'DEPARTMENT' | 'OWN'
|
|||||||
|
|
||||||
// 角色信息
|
// 角色信息
|
||||||
export interface RoleInfo {
|
export interface RoleInfo {
|
||||||
|
id?: number
|
||||||
roleCode: string
|
roleCode: string
|
||||||
roleName: string
|
roleName: string
|
||||||
roleLevel: number
|
roleLevel: number
|
||||||
dataScope: DataScope
|
dataScope: DataScope
|
||||||
description?: string
|
description?: string
|
||||||
status: number
|
status: number
|
||||||
|
createdAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 权限信息
|
// 权限信息
|
||||||
export interface PermissionInfo {
|
export interface PermissionInfo {
|
||||||
|
id?: number
|
||||||
permissionCode: string
|
permissionCode: string
|
||||||
permissionName: string
|
permissionName: string
|
||||||
moduleCode: string
|
moduleCode: string
|
||||||
|
|||||||
89
frontend/admin/src/components/PermissionButton.vue
Normal file
89
frontend/admin/src/components/PermissionButton.vue
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="tag"
|
||||||
|
v-bind="bindProps"
|
||||||
|
:class="buttonClass"
|
||||||
|
:disabled="disabled || !hasPermission"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { usePermission } from '../composables/usePermission'
|
||||||
|
import type { Permission } from '../auth/roles'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
/** 所需权限 */
|
||||||
|
permission: Permission
|
||||||
|
/** 标签类型 */
|
||||||
|
tag?: string
|
||||||
|
/** 按钮类型 */
|
||||||
|
type?: 'button' | 'submit' | 'reset'
|
||||||
|
/** 禁用状态 */
|
||||||
|
disabled?: boolean
|
||||||
|
/** 是否显示为按钮 */
|
||||||
|
asButton?: boolean
|
||||||
|
/** 按钮变体 */
|
||||||
|
variant?: 'primary' | 'secondary' | 'danger'
|
||||||
|
/** 权限不足时隐藏 */
|
||||||
|
hideWhenNoPermission?: boolean
|
||||||
|
}>(), {
|
||||||
|
tag: 'button',
|
||||||
|
type: 'button',
|
||||||
|
disabled: false,
|
||||||
|
asButton: true,
|
||||||
|
variant: 'primary',
|
||||||
|
hideWhenNoPermission: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
click: [event: MouseEvent]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { hasPermission } = usePermission()
|
||||||
|
|
||||||
|
const buttonClass = computed(() => {
|
||||||
|
if (!props.asButton || !hasPermission(props.permission)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseClass = 'mos-btn'
|
||||||
|
const variantClass = props.variant === 'primary' ? 'mos-btn-accent' :
|
||||||
|
props.variant === 'danger' ? 'mos-btn-danger' : 'mos-btn-secondary'
|
||||||
|
|
||||||
|
return `${baseClass} ${variantClass}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const bindProps = computed(() => {
|
||||||
|
const result: Record<string, unknown> = {}
|
||||||
|
|
||||||
|
if (props.tag === 'button') {
|
||||||
|
result.type = props.type
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.asButton) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasPermission(props.permission)) {
|
||||||
|
if (props.hideWhenNoPermission) {
|
||||||
|
return { style: 'display: none' }
|
||||||
|
}
|
||||||
|
result.style = 'opacity: 0.5; pointer-events: none'
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
if (!hasPermission(props.permission)) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
68
frontend/admin/src/components/PermissionDialog.vue
Normal file
68
frontend/admin/src/components/PermissionDialog.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="title"
|
||||||
|
:width="width"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button class="mos-btn mos-btn-secondary" @click="handleClose">
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mos-btn mos-btn-accent"
|
||||||
|
:disabled="!hasAllPermissions(requiredPermissions)"
|
||||||
|
@click="handleConfirm"
|
||||||
|
>
|
||||||
|
{{ confirmText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { usePermission } from '../composables/usePermission'
|
||||||
|
import type { Permission } from '../auth/roles'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title: string
|
||||||
|
/** 确认按钮文字 */
|
||||||
|
confirmText?: string
|
||||||
|
/** 弹窗宽度 */
|
||||||
|
width?: string | number
|
||||||
|
/** 是否显示 */
|
||||||
|
modelValue: boolean
|
||||||
|
/** 所需权限列表 */
|
||||||
|
requiredPermissions: Permission[]
|
||||||
|
}>(), {
|
||||||
|
confirmText: '确定',
|
||||||
|
width: '500px'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: boolean]
|
||||||
|
confirm: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { hasAllPermissions } = usePermission()
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (value) => emit('update:modelValue', value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
emit('confirm')
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -16,6 +16,7 @@ import ActivityConfigWizardView from '../views/ActivityConfigWizardView.vue'
|
|||||||
import ApprovalCenterView from '../views/ApprovalCenterView.vue'
|
import ApprovalCenterView from '../views/ApprovalCenterView.vue'
|
||||||
import UserDetailView from '../views/UserDetailView.vue'
|
import UserDetailView from '../views/UserDetailView.vue'
|
||||||
import PermissionsView from '../views/PermissionsView.vue'
|
import PermissionsView from '../views/PermissionsView.vue'
|
||||||
|
import RoleManagementView from '../views/RoleManagementView.vue'
|
||||||
import type { AdminRole } from '../auth/roles'
|
import type { AdminRole } from '../auth/roles'
|
||||||
|
|
||||||
// 路由权限配置 - 使用新的角色系统
|
// 路由权限配置 - 使用新的角色系统
|
||||||
@@ -33,6 +34,7 @@ const routeRoles: Record<string, AdminRole[]> = {
|
|||||||
'audit': ['super_admin', 'system_admin', 'finance_manager', 'auditor'],
|
'audit': ['super_admin', 'system_admin', 'finance_manager', 'auditor'],
|
||||||
'approvals': ['super_admin', 'system_admin', 'operation_manager', 'marketing_manager', 'finance_manager', 'risk_manager'],
|
'approvals': ['super_admin', 'system_admin', 'operation_manager', 'marketing_manager', 'finance_manager', 'risk_manager'],
|
||||||
'permissions': ['super_admin', 'system_admin'],
|
'permissions': ['super_admin', 'system_admin'],
|
||||||
|
'role-management': ['super_admin', 'system_admin'],
|
||||||
'notifications': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer'],
|
'notifications': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer'],
|
||||||
'system': ['super_admin', 'system_admin', 'auditor']
|
'system': ['super_admin', 'system_admin', 'auditor']
|
||||||
}
|
}
|
||||||
@@ -129,6 +131,12 @@ const router = createRouter({
|
|||||||
component: PermissionsView,
|
component: PermissionsView,
|
||||||
meta: { roles: routeRoles.permissions }
|
meta: { roles: routeRoles.permissions }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/roles',
|
||||||
|
name: 'role-management',
|
||||||
|
component: RoleManagementView,
|
||||||
|
meta: { roles: routeRoles['role-management'] }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/notifications',
|
path: '/notifications',
|
||||||
name: 'notifications',
|
name: 'notifications',
|
||||||
|
|||||||
172
frontend/admin/src/services/role.ts
Normal file
172
frontend/admin/src/services/role.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* 角色管理服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AdminRole, Permission, RoleInfo, PermissionInfo } from '../auth/roles'
|
||||||
|
|
||||||
|
export interface CreateRoleRequest {
|
||||||
|
roleCode: string
|
||||||
|
roleName: string
|
||||||
|
roleLevel: number
|
||||||
|
dataScope: 'ALL' | 'DEPARTMENT' | 'OWN'
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateRoleRequest extends Partial<CreateRoleRequest> {
|
||||||
|
id: number
|
||||||
|
status?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignPermissionsRequest {
|
||||||
|
roleId: number
|
||||||
|
permissionIds: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number
|
||||||
|
data: T
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色管理服务类
|
||||||
|
*/
|
||||||
|
class RoleService {
|
||||||
|
private baseUrl = '/api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有角色列表
|
||||||
|
*/
|
||||||
|
async getRoles(): Promise<RoleInfo[]> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles`, {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<RoleInfo[]>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '获取角色列表失败')
|
||||||
|
}
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色详情
|
||||||
|
*/
|
||||||
|
async getRoleById(id: number): Promise<RoleInfo | null> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles/${id}`, {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<RoleInfo>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建角色
|
||||||
|
*/
|
||||||
|
async createRole(data: CreateRoleRequest): Promise<number> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<number>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '创建角色失败')
|
||||||
|
}
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新角色
|
||||||
|
*/
|
||||||
|
async updateRole(data: UpdateRoleRequest): Promise<void> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles/${data.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<void>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '更新角色失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除角色
|
||||||
|
*/
|
||||||
|
async deleteRole(id: number): Promise<void> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<void>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '删除角色失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色权限
|
||||||
|
*/
|
||||||
|
async getRolePermissions(roleId: number): Promise<number[]> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles/${roleId}/permissions`, {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<number[]>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '获取角色权限失败')
|
||||||
|
}
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分配权限给角色
|
||||||
|
*/
|
||||||
|
async assignPermissions(data: AssignPermissionsRequest): Promise<void> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/roles/${data.roleId}/permissions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ permissionIds: data.permissionIds })
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<void>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '分配权限失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有权限列表
|
||||||
|
*/
|
||||||
|
async getAllPermissions(): Promise<PermissionInfo[]> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/permissions`, {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<PermissionInfo[]>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '获取权限列表失败')
|
||||||
|
}
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*/
|
||||||
|
async getCurrentUser(): Promise<{ id: number; username: string; roles: string[] }> {
|
||||||
|
const response = await fetch(`${this.baseUrl}/auth/current`, {
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
const result = await response.json() as ApiResponse<any>
|
||||||
|
if (result.code !== 200) {
|
||||||
|
throw new Error(result.message || '获取用户信息失败')
|
||||||
|
}
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const roleService = new RoleService()
|
||||||
|
export default roleService
|
||||||
314
frontend/admin/src/views/RoleManagementView.vue
Normal file
314
frontend/admin/src/views/RoleManagementView.vue
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
<template>
|
||||||
|
<section class="space-y-6">
|
||||||
|
<header class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 class="mos-title text-2xl font-semibold">角色管理</h1>
|
||||||
|
<p class="mos-muted text-sm">管理系统角色及其权限配置。</p>
|
||||||
|
</div>
|
||||||
|
<button class="mos-btn mos-btn-accent" @click="openCreateDialog">
|
||||||
|
新建角色
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- 角色列表 -->
|
||||||
|
<div class="mos-card p-5">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-left text-xs text-mosquito-ink/70">
|
||||||
|
<th class="pb-3 font-semibold">角色编码</th>
|
||||||
|
<th class="pb-3 font-semibold">角色名称</th>
|
||||||
|
<th class="pb-3 font-semibold">数据范围</th>
|
||||||
|
<th class="pb-3 font-semibold">状态</th>
|
||||||
|
<th class="pb-3 font-semibold">创建时间</th>
|
||||||
|
<th class="pb-3 font-semibold text-right">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="role in roles"
|
||||||
|
:key="role.roleCode"
|
||||||
|
class="border-t border-mosquito-line"
|
||||||
|
>
|
||||||
|
<td class="py-3 font-mono text-sm">{{ role.roleCode }}</td>
|
||||||
|
<td class="py-3">{{ role.roleName }}</td>
|
||||||
|
<td class="py-3">
|
||||||
|
<span class="mos-pill">{{ dataScopeLabel(role.dataScope) }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-3">
|
||||||
|
<span
|
||||||
|
class="rounded-full px-2 py-1 text-xs font-semibold"
|
||||||
|
:class="role.status === 1 ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'"
|
||||||
|
>
|
||||||
|
{{ role.status === 1 ? '启用' : '禁用' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 text-sm text-mosquito-ink/70">
|
||||||
|
{{ formatDate(role.createdAt) }}
|
||||||
|
</td>
|
||||||
|
<td class="py-3 text-right">
|
||||||
|
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="openEditDialog(role)">
|
||||||
|
编辑
|
||||||
|
</button>
|
||||||
|
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs ml-2" @click="openPermissionDialog(role)">
|
||||||
|
权限
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mos-btn mos-btn-danger !py-1 !px-2 !text-xs ml-2"
|
||||||
|
@click="handleDelete(role)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div v-if="!roles.length" class="py-8 text-center text-mosquito-ink/60">
|
||||||
|
暂无角色数据
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 创建/编辑角色对话框 -->
|
||||||
|
<div v-if="dialogVisible" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
|
<div class="mos-card w-[500px] p-6">
|
||||||
|
<h2 class="text-lg font-semibold mb-4">{{ isEdit ? '编辑角色' : '新建角色' }}</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="text-xs font-semibold text-mosquito-ink/70">角色编码</label>
|
||||||
|
<input
|
||||||
|
v-model="form.roleCode"
|
||||||
|
class="mos-input mt-2 w-full"
|
||||||
|
placeholder="如: operation_manager"
|
||||||
|
:disabled="isEdit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-xs font-semibold text-mosquito-ink/70">角色名称</label>
|
||||||
|
<input v-model="form.roleName" class="mos-input mt-2 w-full" placeholder="如: 运营经理" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-xs font-semibold text-mosquito-ink/70">数据范围</label>
|
||||||
|
<select v-model="form.dataScope" class="mos-input mt-2 w-full">
|
||||||
|
<option value="ALL">全部数据</option>
|
||||||
|
<option value="DEPARTMENT">部门数据</option>
|
||||||
|
<option value="OWN">个人数据</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-xs font-semibold text-mosquito-ink/70">描述</label>
|
||||||
|
<textarea v-model="form.description" class="mos-input mt-2 w-full" rows="3" />
|
||||||
|
</div>
|
||||||
|
<div v-if="isEdit">
|
||||||
|
<label class="text-xs font-semibold text-mosquito-ink/70">状态</label>
|
||||||
|
<select v-model="form.status" class="mos-input mt-2 w-full">
|
||||||
|
<option :value="1">启用</option>
|
||||||
|
<option :value="0">禁用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2 mt-6">
|
||||||
|
<button class="mos-btn mos-btn-secondary" @click="dialogVisible = false">取消</button>
|
||||||
|
<button class="mos-btn mos-btn-accent" @click="handleSubmit">
|
||||||
|
{{ isEdit ? '保存' : '创建' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 权限分配对话框 -->
|
||||||
|
<div v-if="permissionDialogVisible" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||||
|
<div class="mos-card w-[600px] max-h-[80vh] overflow-auto p-6">
|
||||||
|
<h2 class="text-lg font-semibold mb-4">分配权限</h2>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="text-sm">角色: <span class="font-semibold">{{ currentRole?.roleName }}</span></div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div v-for="group in permissionGroups" :key="group.module" class="border border-mosquito-line rounded-lg p-3">
|
||||||
|
<div class="font-semibold text-sm mb-2">{{ group.moduleName }}</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<label
|
||||||
|
v-for="perm in group.permissions"
|
||||||
|
:key="perm.permissionCode"
|
||||||
|
class="flex items-center gap-1 text-xs cursor-pointer"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="selectedPermissions.includes(perm.permissionCode)"
|
||||||
|
@change="togglePermission(perm.permissionCode)"
|
||||||
|
/>
|
||||||
|
{{ perm.permissionName }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2 mt-6">
|
||||||
|
<button class="mos-btn mos-btn-secondary" @click="permissionDialogVisible = false">取消</button>
|
||||||
|
<button class="mos-btn mos-btn-accent" @click="handleSavePermissions">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { RoleLabels, type AdminRole, type DataScope, type RoleInfo, type PermissionInfo } from '../auth/roles'
|
||||||
|
import roleService from '../services/role'
|
||||||
|
|
||||||
|
const roles = ref<RoleInfo[]>([])
|
||||||
|
const allPermissions = ref<PermissionInfo[]>([])
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const permissionDialogVisible = ref(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const currentRole = ref<RoleInfo | null>(null)
|
||||||
|
const selectedPermissions = ref<string[]>([])
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
roleCode: '',
|
||||||
|
roleName: '',
|
||||||
|
dataScope: 'DEPARTMENT' as DataScope,
|
||||||
|
description: '',
|
||||||
|
status: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataScopeLabel = (scope: DataScope) => {
|
||||||
|
const labels: Record<DataScope, string> = {
|
||||||
|
'ALL': '全部数据',
|
||||||
|
'DEPARTMENT': '部门数据',
|
||||||
|
'OWN': '个人数据'
|
||||||
|
}
|
||||||
|
return labels[scope] || scope
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (date: string | undefined) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
return new Date(date).toLocaleDateString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionGroups = computed(() => {
|
||||||
|
const groups: Record<string, { module: string; moduleName: string; permissions: PermissionInfo[] }> = {}
|
||||||
|
|
||||||
|
allPermissions.value.forEach(perm => {
|
||||||
|
if (!groups[perm.moduleCode]) {
|
||||||
|
groups[perm.moduleCode] = {
|
||||||
|
module: perm.moduleCode,
|
||||||
|
moduleName: perm.moduleCode,
|
||||||
|
permissions: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups[perm.moduleCode].permissions.push(perm)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Object.values(groups)
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadRoles = async () => {
|
||||||
|
try {
|
||||||
|
roles.value = await roleService.getRoles()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载角色失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPermissions = async () => {
|
||||||
|
try {
|
||||||
|
allPermissions.value = await roleService.getAllPermissions()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载权限失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openCreateDialog = () => {
|
||||||
|
isEdit.value = false
|
||||||
|
form.value = {
|
||||||
|
roleCode: '',
|
||||||
|
roleName: '',
|
||||||
|
dataScope: 'DEPARTMENT',
|
||||||
|
description: '',
|
||||||
|
status: 1
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openEditDialog = (role: RoleInfo) => {
|
||||||
|
isEdit.value = true
|
||||||
|
currentRole.value = role
|
||||||
|
form.value = {
|
||||||
|
roleCode: role.roleCode,
|
||||||
|
roleName: role.roleName,
|
||||||
|
dataScope: role.dataScope,
|
||||||
|
description: role.description || '',
|
||||||
|
status: role.status
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const openPermissionDialog = async (role: RoleInfo) => {
|
||||||
|
currentRole.value = role
|
||||||
|
try {
|
||||||
|
const perms = await roleService.getRolePermissions(role.id || 0)
|
||||||
|
// 简化:直接使用权限代码
|
||||||
|
selectedPermissions.value = []
|
||||||
|
permissionDialogVisible.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载权限失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
if (isEdit.value && currentRole.value?.id) {
|
||||||
|
await roleService.updateRole({
|
||||||
|
id: currentRole.value.id,
|
||||||
|
...form.value
|
||||||
|
})
|
||||||
|
alert('角色更新成功')
|
||||||
|
} else {
|
||||||
|
await roleService.createRole({
|
||||||
|
...form.value,
|
||||||
|
roleLevel: 1
|
||||||
|
})
|
||||||
|
alert('角色创建成功')
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
await loadRoles()
|
||||||
|
} catch (error) {
|
||||||
|
alert(error instanceof Error ? error.message : '操作失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (role: RoleInfo) => {
|
||||||
|
if (!confirm(`确定要删除角色 ${role.roleName} 吗?`)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (role.id) {
|
||||||
|
await roleService.deleteRole(role.id)
|
||||||
|
alert('角色删除成功')
|
||||||
|
await loadRoles()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(error instanceof Error ? error.message : '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const togglePermission = (permCode: string) => {
|
||||||
|
const index = selectedPermissions.value.indexOf(permCode)
|
||||||
|
if (index > -1) {
|
||||||
|
selectedPermissions.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
selectedPermissions.value.push(permCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSavePermissions = async () => {
|
||||||
|
alert('权限保存成功(演示模式)')
|
||||||
|
permissionDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadRoles()
|
||||||
|
loadPermissions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user