feat(frontend): 完善角色管理功能

- 添加 PermissionButton.vue 权限按钮组件
- 添加 PermissionDialog.vue 权限对话框组件
- 添加 role.ts 角色管理服务
- 添加 RoleManagementView.vue 角色管理页面
- 更新路由配置添加角色管理页面
- 更新 App.vue 添加角色管理菜单入口
- 修复 TypeScript 类型定义问题
- 前端编译验证通过
This commit is contained in:
Your Name
2026-03-05 09:32:11 +08:00
parent ddae0432f4
commit c621af044c
8 changed files with 665 additions and 1 deletions

View 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>

View 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>