2026-02-12 17:41:27 +08:00

197 lines
6.8 KiB
Vue

<template>
<div>
<div class="page-header">
<h2>角色管理</h2>
<el-button type="primary" @click="openCreate"><el-icon><Plus /></el-icon> 新建角色</el-button>
</div>
<el-table :data="roles" v-loading="loading" stripe>
<el-table-column prop="name" label="角色名称" width="140">
<template #default="{row}">
<span class="role-name">{{ row.name }}</span>
<el-tag v-if="row.is_system" size="small" type="info" style="margin-left:6px">内置</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
<el-table-column label="权限数" width="90" align="center">
<template #default="{row}">
<span class="perm-count">{{ row.permissions.length }}</span>
</template>
</el-table-column>
<el-table-column label="用户数" width="90" align="center">
<template #default="{row}">{{ row.user_count }}</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="{row}">
<el-button text size="small" @click="openEdit(row)">编辑权限</el-button>
<el-button v-if="!row.is_system" text type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新建/编辑角色弹窗 -->
<el-dialog v-model="showDialog" :title="editingId ? '编辑角色' : '新建角色'" width="640px" destroy-on-close>
<el-form :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.name" :disabled="editingSystem" placeholder="如:制片人、实习生等" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" placeholder="(选填)角色职责说明" />
</el-form-item>
</el-form>
<div class="perm-panel">
<div class="perm-panel-header">
<span class="perm-panel-title">权限配置</span>
<el-button text size="small" @click="toggleAll">{{ isAllSelected ? '取消全选' : '全选' }}</el-button>
</div>
<div v-for="group in permGroups" :key="group.group" class="perm-group">
<div class="perm-group-header">
<el-checkbox
:model-value="isGroupAllSelected(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroup(group, $event)"
>{{ group.group }}</el-checkbox>
</div>
<div class="perm-items">
<el-checkbox
v-for="p in group.permissions"
:key="p.key"
:model-value="form.permissions.includes(p.key)"
@change="togglePerm(p.key, $event)"
>{{ p.label }}</el-checkbox>
</div>
</div>
</div>
<template #footer>
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { roleApi } from '../api'
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
const saving = ref(false)
const showDialog = ref(false)
const editingId = ref(null)
const editingSystem = ref(false)
const roles = ref([])
const permGroups = ref([])
const form = ref({ name: '', description: '', permissions: [] })
const allPermKeys = computed(() => permGroups.value.flatMap(g => g.permissions.map(p => p.key)))
const isAllSelected = computed(() => allPermKeys.value.length > 0 && allPermKeys.value.every(k => form.value.permissions.includes(k)))
function isGroupAllSelected(group) {
return group.permissions.every(p => form.value.permissions.includes(p.key))
}
function isGroupIndeterminate(group) {
const selected = group.permissions.filter(p => form.value.permissions.includes(p.key)).length
return selected > 0 && selected < group.permissions.length
}
function toggleGroup(group, checked) {
const keys = group.permissions.map(p => p.key)
if (checked) {
form.value.permissions = [...new Set([...form.value.permissions, ...keys])]
} else {
form.value.permissions = form.value.permissions.filter(k => !keys.includes(k))
}
}
function togglePerm(key, checked) {
if (checked) {
form.value.permissions.push(key)
} else {
form.value.permissions = form.value.permissions.filter(k => k !== key)
}
}
function toggleAll() {
if (isAllSelected.value) {
form.value.permissions = []
} else {
form.value.permissions = [...allPermKeys.value]
}
}
function openCreate() {
editingId.value = null
editingSystem.value = false
form.value = { name: '', description: '', permissions: [] }
showDialog.value = true
}
function openEdit(role) {
editingId.value = role.id
editingSystem.value = role.is_system
form.value = {
name: role.name,
description: role.description || '',
permissions: [...role.permissions],
}
showDialog.value = true
}
async function handleSave() {
if (!form.value.name.trim()) { ElMessage.warning('请输入角色名称'); return }
saving.value = true
try {
if (editingId.value) {
await roleApi.update(editingId.value, form.value)
ElMessage.success('角色已更新')
} else {
await roleApi.create(form.value)
ElMessage.success('角色已创建')
}
showDialog.value = false
load()
} finally { saving.value = false }
}
async function handleDelete(role) {
try {
await ElMessageBox.confirm(`确认删除角色「${role.name}」?`, '删除角色', { type: 'warning' })
await roleApi.delete(role.id)
ElMessage.success('角色已删除')
load()
} catch {}
}
async function load() {
loading.value = true
try { roles.value = await roleApi.list() } finally { loading.value = false }
}
onMounted(async () => {
load()
try { permGroups.value = await roleApi.permissions() } catch {}
})
</script>
<style scoped>
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.page-header h2 { font-size: 18px; font-weight: 600; }
.role-name { font-weight: 600; color: var(--text-primary); }
.perm-count { font-weight: 600; color: var(--primary); }
/* 权限勾选面板 */
.perm-panel { margin-top: 8px; }
.perm-panel-header {
display: flex; justify-content: space-between; align-items: center;
padding: 8px 0; border-bottom: 1px solid var(--border-light); margin-bottom: 8px;
}
.perm-panel-title { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.perm-group { margin-bottom: 12px; }
.perm-group-header {
padding: 6px 12px; background: #F7F8FA; border-radius: 6px;
margin-bottom: 6px; font-weight: 500;
}
.perm-items { padding: 4px 12px 4px 28px; display: flex; flex-wrap: wrap; gap: 8px 20px; }
</style>