197 lines
6.8 KiB
Vue
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>
|