feat(management): 接入文档用户权限与报表
This commit is contained in:
+381
@@ -0,0 +1,381 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch } from 'vue'
|
||||
|
||||
import type { DocumentRecord, TemplatePermissionRecord } from '@/api/management/types'
|
||||
|
||||
import type { PermissionSubjectOption, TemplatePermissionForm } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
templates: DocumentRecord[]
|
||||
selectedTemplateId: string
|
||||
permissions: TemplatePermissionRecord[]
|
||||
subjects: PermissionSubjectOption[]
|
||||
loading: boolean
|
||||
saving: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:selectedTemplateId': [value: string]
|
||||
add: [form: TemplatePermissionForm]
|
||||
delete: [permissionId: string]
|
||||
}>()
|
||||
|
||||
const form = reactive<TemplatePermissionForm>({
|
||||
subjectType: 'ROLE',
|
||||
subjectId: '',
|
||||
permissionLevel: 'VIEW',
|
||||
effect: 'ALLOW',
|
||||
})
|
||||
|
||||
const subjectTypeLabels: Record<PermissionSubjectOption['type'], string> = {
|
||||
ROLE: '角色',
|
||||
DEPARTMENT: '科室',
|
||||
USER: '用户',
|
||||
}
|
||||
|
||||
const permissionLabels: Record<TemplatePermissionForm['permissionLevel'], string> = {
|
||||
VIEW: '可见(查看)',
|
||||
USE: '可用(发起签署)',
|
||||
MAINTAIN: '可维护(编辑/停用)',
|
||||
}
|
||||
|
||||
const effectLabels: Record<TemplatePermissionForm['effect'], string> = {
|
||||
ALLOW: '允许',
|
||||
DENY: '拒绝',
|
||||
}
|
||||
|
||||
const availableSubjects = computed(() =>
|
||||
props.subjects.filter((subject) => subject.type === form.subjectType),
|
||||
)
|
||||
|
||||
const selectedTemplate = computed(() =>
|
||||
props.templates.find((template) => template.id === props.selectedTemplateId),
|
||||
)
|
||||
|
||||
function handleTemplateChange(event: Event) {
|
||||
emit('update:selectedTemplateId', (event.target as HTMLSelectElement).value)
|
||||
}
|
||||
|
||||
function handleSubjectTypeChange() {
|
||||
form.subjectId = availableSubjects.value[0]?.id ?? ''
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (!form.subjectId) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('add', { ...form })
|
||||
}
|
||||
|
||||
function formatSubject(subjectType: PermissionSubjectOption['type'], subjectId: string) {
|
||||
const subject = props.subjects.find((item) => item.type === subjectType && item.id === subjectId)
|
||||
return subject?.label ?? subjectId
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.subjects,
|
||||
() => {
|
||||
if (!availableSubjects.value.some((subject) => subject.id === form.subjectId)) {
|
||||
form.subjectId = availableSubjects.value[0]?.id ?? ''
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="permission-panel">
|
||||
<div class="panel-toolbar">
|
||||
<label class="template-select">
|
||||
<span>选择模板</span>
|
||||
<select :value="selectedTemplateId" @change="handleTemplateChange">
|
||||
<option value="" disabled>请选择模板</option>
|
||||
<option v-for="template in templates" :key="template.id" :value="template.id">
|
||||
{{ template.name }} · {{ template.version }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<span v-if="selectedTemplate" class="template-meta">
|
||||
{{ selectedTemplate.code || selectedTemplate.id }} · {{ selectedTemplate.category }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="!selectedTemplateId" class="empty-state">暂无模板,请先选择一个模板。</div>
|
||||
<div v-else-if="loading" class="loading-state">正在加载模板权限…</div>
|
||||
<template v-else>
|
||||
<div class="permission-list">
|
||||
<div class="list-heading">
|
||||
<span>已配置权限</span>
|
||||
<span>{{ permissions.length }} 条</span>
|
||||
</div>
|
||||
<div v-if="permissions.length" class="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>授权对象</th>
|
||||
<th>权限</th>
|
||||
<th>效果</th>
|
||||
<th>来源</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="permission in permissions" :key="permission.id">
|
||||
<td>
|
||||
<span class="subject-type">{{ subjectTypeLabels[permission.subjectType] }}</span>
|
||||
{{ formatSubject(permission.subjectType, permission.subjectId) }}
|
||||
</td>
|
||||
<td>{{ permissionLabels[permission.permissionLevel] }}</td>
|
||||
<td>{{ effectLabels[permission.effect] }}</td>
|
||||
<td>{{ permission.inherited ? '继承' : '直接' }}</td>
|
||||
<td>{{ permission.createdAt }}</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="delete-button"
|
||||
:disabled="permission.inherited || saving"
|
||||
:title="permission.inherited ? '继承权限不可直接删除' : '删除权限'"
|
||||
@click="emit('delete', permission.id)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p v-else class="empty-state compact">当前模板暂无直接权限配置。</p>
|
||||
</div>
|
||||
|
||||
<form class="add-form" @submit.prevent="submit">
|
||||
<div class="list-heading">
|
||||
<span>新增权限</span>
|
||||
<span>服务端会再次校验授权范围</span>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<label>
|
||||
<span>对象类型</span>
|
||||
<select v-model="form.subjectType" @change="handleSubjectTypeChange">
|
||||
<option v-for="(label, value) in subjectTypeLabels" :key="value" :value="value">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>授权对象</span>
|
||||
<select v-model="form.subjectId" :disabled="!availableSubjects.length">
|
||||
<option value="" disabled>请选择对象</option>
|
||||
<option v-for="subject in availableSubjects" :key="subject.id" :value="subject.id">
|
||||
{{ subject.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>权限级别</span>
|
||||
<select v-model="form.permissionLevel">
|
||||
<option v-for="(label, value) in permissionLabels" :key="value" :value="value">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>授权效果</span>
|
||||
<select v-model="form.effect">
|
||||
<option v-for="(label, value) in effectLabels" :key="value" :value="value">
|
||||
{{ label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit" class="add-button" :disabled="saving || !form.subjectId">
|
||||
{{ saving ? '保存中…' : '添加权限' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.permission-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.panel-toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.template-select,
|
||||
.form-grid label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
min-width: min(420px, 100%);
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 0;
|
||||
padding: 7px 10px;
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
background: #f5f8f9;
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
padding-bottom: 8px;
|
||||
color: var(--mut);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.list-heading {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
color: var(--ink);
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.list-heading span:last-child {
|
||||
color: var(--mut);
|
||||
font-size: 11.5px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
min-width: 760px;
|
||||
font-size: 12px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 8px 10px;
|
||||
color: var(--mut);
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
background: #f5f9fa;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 10px;
|
||||
color: var(--ink);
|
||||
border-bottom: 1px solid #eef4f6;
|
||||
}
|
||||
|
||||
.subject-type {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
margin-right: 5px;
|
||||
color: var(--brand);
|
||||
font-size: 10.5px;
|
||||
background: var(--brand-l);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
padding: 3px 8px;
|
||||
color: var(--err);
|
||||
font-size: 11.5px;
|
||||
background: #fff;
|
||||
border: 1px solid rgb(194 73 73 / 32%);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.delete-button:disabled {
|
||||
color: #aebbc0;
|
||||
border-color: #dce5e8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.add-form {
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
|
||||
gap: 10px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
padding: 7px 13px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
background: var(--brand);
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.add-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.empty-state,
|
||||
.loading-state {
|
||||
padding: 32px 16px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state.compact {
|
||||
padding: 18px 12px;
|
||||
margin: 0;
|
||||
background: #f8fbfc;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.form-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.add-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user