feat(document-permissions): 重构权限页面按模板接入真实接口
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# 文档权限页面接口接入说明
|
||||
|
||||
依据 default_OpenAPI (1).json,文档权限页面当前按“文档模板”维度接入权限。页面保留原型的三部分:
|
||||
|
||||
1. **文档库权限矩阵**:说明可见、可用、可维护和继承规则。
|
||||
2. **按角色授权**:选择一个文档模板,调整角色的 VIEW、USE、MAINTAIN。
|
||||
3. **按科室授权**:沿用当前模板,调整科室的 VIEW、USE、MAINTAIN。
|
||||
|
||||
## 已接入接口
|
||||
|
||||
| 用途 | 方法 | 路径 |
|
||||
| ---------------------- | ------ | --------------------------------------------------------- |
|
||||
| 查询可配置模板 | GET | /api/v1/templates |
|
||||
| 查询角色 | GET | /api/v1/roles |
|
||||
| 查询科室 | GET | /api/v1/departments |
|
||||
| 查询模板权限及继承结果 | GET | /api/v1/templates/{templateId}/permissions |
|
||||
| 绑定模板权限 | POST | /api/v1/templates/{templateId}/permissions |
|
||||
| 撤销模板权限 | DELETE | /api/v1/templates/{templateId}/permissions/{permissionId} |
|
||||
|
||||
权限绑定请求使用 OpenAPI 中的 PermissionBindingRequest:
|
||||
|
||||
```json
|
||||
{
|
||||
"subjectType": "ROLE",
|
||||
"subjectId": "<role-or-department-uuid>",
|
||||
"permissionLevel": "USE",
|
||||
"effect": "ALLOW"
|
||||
}
|
||||
```
|
||||
|
||||
权限等级映射:
|
||||
|
||||
- VIEW:可浏览模板
|
||||
- USE:可发起签署
|
||||
- MAINTAIN:可维护模板
|
||||
|
||||
## 页面保存行为
|
||||
|
||||
当前契约没有更新权限绑定的 PUT 接口,因此开关按以下规则保存:
|
||||
|
||||
- 开启:删除同主体、同等级的直接 DENY;没有有效允许权限时 POST ALLOW。
|
||||
- 关闭:撤销直接 ALLOW;如果仍有继承的 ALLOW,POST 直接 DENY 覆盖继承。
|
||||
- 继承 DENY 没有持久化绑定 ID,且契约约定拒绝优先,页面将其显示为不可直接覆盖。
|
||||
|
||||
页面不会把继承结果当成可删除的直接绑定,也不会伪造全局权限矩阵数据。真实环境接口没有数据时会显示空态。
|
||||
|
||||
## 当前 OpenAPI 未提供的能力
|
||||
|
||||
如果产品要求“跨全部文档模板”的全局角色/科室权限矩阵,后端还需要提供以下能力之一:
|
||||
|
||||
- 全局角色权限查询与批量保存接口;
|
||||
- 全局科室权限查询与批量保存接口;
|
||||
- 或提供带模板维度的批量权限查询/保存接口,避免前端逐个模板请求。
|
||||
|
||||
另外,当前 GET /api/v1/templates 的语义是查询当前用户有权限查看的模板。若权限管理员需要配置自己当前不可见的模板,请后端提供管理员可见的模板列表语义或专用接口。
|
||||
@@ -95,6 +95,7 @@ function mapTemplatePermission(dto: TemplatePermissionResponseDto): TemplatePerm
|
||||
effect: dto.effect,
|
||||
inherited: dto.inherited,
|
||||
createdAt: formatDateTime(dto.createdAt),
|
||||
createdBy: dto.createdBy,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -511,7 +511,7 @@ export type ApiPermissionLevel = 'VIEW' | 'USE' | 'MAINTAIN'
|
||||
export type ApiPermissionEffect = 'ALLOW' | 'DENY'
|
||||
|
||||
export interface TemplatePermissionResponseDto {
|
||||
id: string
|
||||
id?: string
|
||||
templateId: string
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
@@ -519,10 +519,11 @@ export interface TemplatePermissionResponseDto {
|
||||
effect: ApiPermissionEffect
|
||||
inherited: boolean
|
||||
createdAt: string
|
||||
createdBy?: string
|
||||
}
|
||||
|
||||
export interface TemplatePermissionRecord {
|
||||
id: string
|
||||
id?: string
|
||||
templateId: string
|
||||
subjectType: ApiPermissionSubjectType
|
||||
subjectId: string
|
||||
@@ -531,6 +532,7 @@ export interface TemplatePermissionRecord {
|
||||
effect: ApiPermissionEffect
|
||||
inherited: boolean
|
||||
createdAt: string
|
||||
createdBy?: string
|
||||
}
|
||||
|
||||
export interface CreateTemplatePermissionRequest {
|
||||
|
||||
+196
-70
@@ -1,59 +1,109 @@
|
||||
<script setup lang="ts">
|
||||
import type { DepartmentDocumentAccess, DepartmentPermissionRow } from '../types'
|
||||
import type {
|
||||
PermissionCellState,
|
||||
PermissionColumn,
|
||||
PermissionKey,
|
||||
DepartmentPermissionRow,
|
||||
} from '../types'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
rows: DepartmentPermissionRow[]
|
||||
columns: PermissionColumn[]
|
||||
saving?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
adjust: [row: DepartmentPermissionRow]
|
||||
toggle: [departmentId: string, permission: PermissionKey]
|
||||
}>()
|
||||
|
||||
const accessLabels: Record<DepartmentDocumentAccess, string> = {
|
||||
available: '可用',
|
||||
invisible: '不可见',
|
||||
function getState(row: DepartmentPermissionRow, permission: PermissionKey): PermissionCellState {
|
||||
return (
|
||||
row.permissionStates?.[permission] ?? (row.permissions[permission] ? 'direct-allow' : 'none')
|
||||
)
|
||||
}
|
||||
|
||||
function stateLabel(state: PermissionCellState) {
|
||||
const labels: Record<PermissionCellState, string> = {
|
||||
none: '未授权',
|
||||
'direct-allow': '直接允许',
|
||||
'direct-deny': '直接拒绝',
|
||||
'inherited-allow': '继承允许',
|
||||
'inherited-deny': '继承拒绝',
|
||||
}
|
||||
return labels[state]
|
||||
}
|
||||
|
||||
function isDisabled(row: DepartmentPermissionRow, permission: PermissionKey) {
|
||||
return props.saving || getState(row, permission) === 'inherited-deny'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="table-scroll">
|
||||
<table class="permission-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科室</th>
|
||||
<th>本科室文档</th>
|
||||
<th>全院通用文档</th>
|
||||
<th>其他科室文档</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.id">
|
||||
<td>
|
||||
<strong>{{ row.department }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="access-tag" :class="'is-' + row.ownDocuments">
|
||||
{{ accessLabels[row.ownDocuments] }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="access-tag" :class="'is-' + row.commonDocuments">
|
||||
{{ accessLabels[row.commonDocuments] }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="access-tag" :class="'is-' + row.otherDocuments">
|
||||
{{ accessLabels[row.otherDocuments] }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="adjust-button" @click="emit('adjust', row)">调整</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="!rows.length" class="empty-state">暂无科室数据</div>
|
||||
<template v-else>
|
||||
<div class="table-scroll">
|
||||
<table class="permission-table" aria-label="按科室授权矩阵">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科室</th>
|
||||
<th
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
class="permission-heading"
|
||||
:title="column.description"
|
||||
>
|
||||
{{ column.label }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.id">
|
||||
<td>
|
||||
<div class="department-name">
|
||||
<strong>{{ row.department }}</strong>
|
||||
<small v-if="row.campus">{{ row.campus }}</small>
|
||||
</div>
|
||||
</td>
|
||||
<td v-for="column in columns" :key="column.key" class="permission-cell">
|
||||
<button
|
||||
type="button"
|
||||
class="permission-toggle"
|
||||
:class="{
|
||||
active: row.permissions[column.key],
|
||||
inherited: getState(row, column.key).startsWith('inherited'),
|
||||
denied: getState(row, column.key).endsWith('deny'),
|
||||
}"
|
||||
:disabled="isDisabled(row, column.key)"
|
||||
:aria-pressed="row.permissions[column.key]"
|
||||
:aria-label="
|
||||
row.department +
|
||||
':' +
|
||||
column.label +
|
||||
',' +
|
||||
stateLabel(getState(row, column.key))
|
||||
"
|
||||
:title="
|
||||
column.label +
|
||||
':' +
|
||||
column.description +
|
||||
'(' +
|
||||
stateLabel(getState(row, column.key)) +
|
||||
')'
|
||||
"
|
||||
@click="emit('toggle', row.id, column.key)"
|
||||
>
|
||||
<span class="switch-track" aria-hidden="true" />
|
||||
</button>
|
||||
<span class="state-label">{{ stateLabel(getState(row, column.key)) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="legend">
|
||||
继承允许可通过开关新增直接拒绝;继承拒绝由上级规则控制,当前接口不支持直接覆盖。
|
||||
</p>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -64,15 +114,15 @@ const accessLabels: Record<DepartmentDocumentAccess, string> = {
|
||||
|
||||
.permission-table {
|
||||
width: 100%;
|
||||
min-width: 640px;
|
||||
font-size: 13px;
|
||||
min-width: 680px;
|
||||
font-size: 12.5px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.permission-table th {
|
||||
padding: 9px 12px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
@@ -80,6 +130,10 @@ const accessLabels: Record<DepartmentDocumentAccess, string> = {
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.permission-table th.permission-heading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.permission-table td {
|
||||
padding: 9px 12px;
|
||||
vertical-align: middle;
|
||||
@@ -90,34 +144,106 @@ const accessLabels: Record<DepartmentDocumentAccess, string> = {
|
||||
background: #f7fbfc;
|
||||
}
|
||||
|
||||
.access-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
.department-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.department-name small {
|
||||
color: var(--mut);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.permission-cell {
|
||||
width: 23%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.permission-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
background: transparent;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.permission-toggle:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.switch-track {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
background: #cbd8de;
|
||||
border-radius: 20px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.access-tag.is-available {
|
||||
color: var(--ok);
|
||||
background: var(--ok-l);
|
||||
}
|
||||
|
||||
.access-tag.is-invisible {
|
||||
color: #8a9aa3;
|
||||
background: #eceef0;
|
||||
}
|
||||
|
||||
.adjust-button {
|
||||
padding: 4px 10px;
|
||||
color: var(--brand);
|
||||
font-size: 12px;
|
||||
.switch-track::after {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: '';
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 5px;
|
||||
border-radius: 50%;
|
||||
transition: left 0.2s ease;
|
||||
}
|
||||
|
||||
.adjust-button:hover {
|
||||
background: var(--brand-l);
|
||||
.permission-toggle.active .switch-track {
|
||||
background: var(--ok);
|
||||
}
|
||||
|
||||
.permission-toggle.inherited.active .switch-track {
|
||||
background: #4eaa8a;
|
||||
box-shadow: inset 0 0 0 2px rgb(255 255 255 / 45%);
|
||||
}
|
||||
|
||||
.permission-toggle.denied .switch-track {
|
||||
background: #cbd8de;
|
||||
}
|
||||
|
||||
.permission-toggle.active .switch-track::after {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
.permission-toggle:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.state-label {
|
||||
display: inline-block;
|
||||
min-width: 52px;
|
||||
margin-left: 3px;
|
||||
color: var(--mut);
|
||||
font-size: 10.5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.legend {
|
||||
margin: 10px 0 0;
|
||||
color: var(--mut);
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 28px 12px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
background: #f8fbfc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
+96
-8
@@ -1,18 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import type { PermissionColumn, PermissionKey, RolePermissionRow } from '../types'
|
||||
import type {
|
||||
PermissionCellState,
|
||||
PermissionColumn,
|
||||
PermissionKey,
|
||||
RolePermissionRow,
|
||||
} from '../types'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
rows: RolePermissionRow[]
|
||||
columns: PermissionColumn[]
|
||||
saving?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggle: [roleId: string, permission: PermissionKey]
|
||||
}>()
|
||||
|
||||
function getState(row: RolePermissionRow, permission: PermissionKey): PermissionCellState {
|
||||
return (
|
||||
row.permissionStates?.[permission] ?? (row.permissions[permission] ? 'direct-allow' : 'none')
|
||||
)
|
||||
}
|
||||
|
||||
function stateLabel(state: PermissionCellState) {
|
||||
const labels: Record<PermissionCellState, string> = {
|
||||
none: '未授权',
|
||||
'direct-allow': '直接允许',
|
||||
'direct-deny': '直接拒绝',
|
||||
'inherited-allow': '继承允许',
|
||||
'inherited-deny': '继承拒绝',
|
||||
}
|
||||
return labels[state]
|
||||
}
|
||||
|
||||
function isDisabled(row: RolePermissionRow, permission: PermissionKey) {
|
||||
return props.saving || getState(row, permission) === 'inherited-deny'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="permission-grid" role="table" aria-label="按角色授权矩阵">
|
||||
<div v-if="!rows.length" class="empty-state">暂无角色数据</div>
|
||||
<div v-else class="permission-grid" role="table" aria-label="按角色授权矩阵">
|
||||
<div class="grid-cell grid-header role-header" role="columnheader">角色</div>
|
||||
<div
|
||||
v-for="column in columns"
|
||||
@@ -27,16 +55,34 @@ const emit = defineEmits<{
|
||||
<template v-for="row in rows" :key="row.id">
|
||||
<div class="grid-cell role-name" role="rowheader">
|
||||
<span class="role-color" :style="{ backgroundColor: row.color }" aria-hidden="true" />
|
||||
{{ row.name }}
|
||||
<span class="role-info">
|
||||
<strong>{{ row.name }}</strong>
|
||||
<small v-if="row.description">{{ row.description }}</small>
|
||||
<small v-if="row.userCount">{{ row.userCount }} 人</small>
|
||||
</span>
|
||||
</div>
|
||||
<div v-for="column in columns" :key="column.key" class="grid-cell permission-cell">
|
||||
<button
|
||||
type="button"
|
||||
class="permission-toggle"
|
||||
:class="{ active: row.permissions[column.key] }"
|
||||
:class="{
|
||||
active: row.permissions[column.key],
|
||||
inherited: getState(row, column.key).startsWith('inherited'),
|
||||
denied: getState(row, column.key).endsWith('deny'),
|
||||
}"
|
||||
:disabled="isDisabled(row, column.key)"
|
||||
:aria-pressed="row.permissions[column.key]"
|
||||
:aria-label="`${row.name}:${column.label}${row.permissions[column.key] ? ',已开启' : ',已关闭'}`"
|
||||
:title="`${column.label}:${column.description}`"
|
||||
:aria-label="
|
||||
row.name + ':' + column.label + ',' + stateLabel(getState(row, column.key))
|
||||
"
|
||||
:title="
|
||||
column.label +
|
||||
':' +
|
||||
column.description +
|
||||
'(' +
|
||||
stateLabel(getState(row, column.key)) +
|
||||
')'
|
||||
"
|
||||
@click="emit('toggle', row.id, column.key)"
|
||||
>
|
||||
<span class="switch-track" aria-hidden="true" />
|
||||
@@ -49,7 +95,7 @@ const emit = defineEmits<{
|
||||
<style scoped>
|
||||
.permission-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 140px repeat(3, minmax(100px, 1fr));
|
||||
grid-template-columns: minmax(190px, 1.5fr) repeat(3, minmax(100px, 1fr));
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 12.5px;
|
||||
@@ -75,16 +121,35 @@ const emit = defineEmits<{
|
||||
.role-name {
|
||||
gap: 7px;
|
||||
color: var(--ink);
|
||||
align-items: flex-start;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.role-color {
|
||||
margin-top: 3px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.role-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.role-info small {
|
||||
overflow: hidden;
|
||||
color: var(--mut);
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.permission-cell {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -131,10 +196,33 @@ const emit = defineEmits<{
|
||||
background: var(--ok);
|
||||
}
|
||||
|
||||
.permission-toggle.inherited.active .switch-track {
|
||||
background: #4eaa8a;
|
||||
box-shadow: inset 0 0 0 2px rgb(255 255 255 / 45%);
|
||||
}
|
||||
|
||||
.permission-toggle.denied .switch-track {
|
||||
background: #cbd8de;
|
||||
}
|
||||
|
||||
.permission-toggle.active .switch-track::after {
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
.permission-toggle:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 28px 12px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
background: #f8fbfc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.permission-grid {
|
||||
grid-template-columns: 120px repeat(3, minmax(76px, 1fr));
|
||||
|
||||
+7
-1
@@ -68,6 +68,12 @@ function submit() {
|
||||
emit('add', { ...form })
|
||||
}
|
||||
|
||||
function deletePermission(permissionId: string | undefined) {
|
||||
if (permissionId) {
|
||||
emit('delete', permissionId)
|
||||
}
|
||||
}
|
||||
|
||||
function formatSubject(subjectType: PermissionSubjectOption['type'], subjectId: string) {
|
||||
const subject = props.subjects.find((item) => item.type === subjectType && item.id === subjectId)
|
||||
return subject?.label ?? subjectId
|
||||
@@ -137,7 +143,7 @@ watch(
|
||||
class="delete-button"
|
||||
:disabled="permission.inherited || saving"
|
||||
:title="permission.inherited ? '继承权限不可直接删除' : '删除权限'"
|
||||
@click="emit('delete', permission.id)"
|
||||
@click="deletePermission(permission.id)"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { getDepartments } from '@/api/management/organization'
|
||||
@@ -11,87 +11,160 @@ import {
|
||||
isPermissionMockEnabled,
|
||||
} from '@/api/management/document-permissions'
|
||||
import { getRoles } from '@/api/management/roles'
|
||||
import { getUsers } from '@/api/management/users'
|
||||
import type { DocumentRecord, TemplatePermissionRecord } from '@/api/management/types'
|
||||
import type {
|
||||
DepartmentRecord,
|
||||
DocumentRecord,
|
||||
RoleRecord,
|
||||
TemplatePermissionRecord,
|
||||
} from '@/api/management/types'
|
||||
|
||||
import DepartmentPermissionTable from './components/DepartmentPermissionTable.vue'
|
||||
import RolePermissionMatrix from './components/RolePermissionMatrix.vue'
|
||||
import TemplatePermissionPanel from './components/TemplatePermissionPanel.vue'
|
||||
import { departmentPermissionRows, permissionColumns, rolePermissionRows } from './mock'
|
||||
import type {
|
||||
DepartmentPermissionRow,
|
||||
PermissionCellState,
|
||||
PermissionKey,
|
||||
PermissionSubjectOption,
|
||||
RolePermissionRow,
|
||||
TemplatePermissionForm,
|
||||
} from './types'
|
||||
|
||||
type PermissionSubjectType = 'ROLE' | 'DEPARTMENT'
|
||||
|
||||
interface PermissionResolution {
|
||||
enabled: boolean
|
||||
state: PermissionCellState
|
||||
}
|
||||
|
||||
const permissionKeys: PermissionKey[] = ['VIEW', 'USE', 'MAINTAIN']
|
||||
|
||||
const loading = ref(true)
|
||||
const roles = ref<RolePermissionRow[]>([])
|
||||
const departments = ref<DepartmentPermissionRow[]>([])
|
||||
const roleRecords = ref<RoleRecord[]>([])
|
||||
const departmentRecords = ref<DepartmentRecord[]>([])
|
||||
const roleRows = ref<RolePermissionRow[]>([])
|
||||
const departmentRows = ref<DepartmentPermissionRow[]>([])
|
||||
const templates = ref<DocumentRecord[]>([])
|
||||
const selectedTemplateId = ref('')
|
||||
const templatePermissions = ref<TemplatePermissionRecord[]>([])
|
||||
const permissionSubjects = ref<PermissionSubjectOption[]>([])
|
||||
const permissionLoading = ref(false)
|
||||
const permissionSaving = ref(false)
|
||||
const permissionError = ref(false)
|
||||
let permissionRequestId = 0
|
||||
|
||||
const selectedTemplate = computed(() =>
|
||||
templates.value.find((template) => template.id === selectedTemplateId.value),
|
||||
)
|
||||
|
||||
function resolvePermission(
|
||||
subjectType: PermissionSubjectType,
|
||||
subjectId: string,
|
||||
permission: PermissionKey,
|
||||
): PermissionResolution {
|
||||
const records = templatePermissions.value.filter(
|
||||
(record) =>
|
||||
record.subjectType === subjectType &&
|
||||
record.subjectId === subjectId &&
|
||||
record.permissionLevel === permission,
|
||||
)
|
||||
const hasDirectDeny = records.some((record) => !record.inherited && record.effect === 'DENY')
|
||||
const hasDirectAllow = records.some((record) => !record.inherited && record.effect === 'ALLOW')
|
||||
const hasInheritedDeny = records.some((record) => record.inherited && record.effect === 'DENY')
|
||||
const hasInheritedAllow = records.some((record) => record.inherited && record.effect === 'ALLOW')
|
||||
|
||||
if (hasDirectDeny) {
|
||||
return { enabled: false, state: 'direct-deny' }
|
||||
}
|
||||
|
||||
if (hasInheritedDeny) {
|
||||
return { enabled: false, state: 'inherited-deny' }
|
||||
}
|
||||
|
||||
if (hasDirectAllow) {
|
||||
return { enabled: true, state: 'direct-allow' }
|
||||
}
|
||||
|
||||
if (hasInheritedAllow) {
|
||||
return { enabled: true, state: 'inherited-allow' }
|
||||
}
|
||||
|
||||
return { enabled: false, state: 'none' }
|
||||
}
|
||||
|
||||
function buildPermissionMaps(subjectType: PermissionSubjectType, subjectId: string) {
|
||||
const permissions = {} as Record<PermissionKey, boolean>
|
||||
const permissionStates = {} as Record<PermissionKey, PermissionCellState>
|
||||
|
||||
for (const permission of permissionKeys) {
|
||||
const resolution = resolvePermission(subjectType, subjectId, permission)
|
||||
permissions[permission] = resolution.enabled
|
||||
permissionStates[permission] = resolution.state
|
||||
}
|
||||
|
||||
return { permissions, permissionStates }
|
||||
}
|
||||
|
||||
function syncPermissionRows() {
|
||||
roleRows.value = roleRecords.value.map((role, index) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
color: role.color ?? ['#6b5bd2', '#0e6e8c', '#0f9d6c', '#d9821f'][index % 4],
|
||||
description: role.description,
|
||||
userCount: role.userCount ?? 0,
|
||||
...buildPermissionMaps('ROLE', role.id),
|
||||
}))
|
||||
|
||||
departmentRows.value = departmentRecords.value.map((department) => ({
|
||||
id: department.id,
|
||||
department: department.name,
|
||||
...buildPermissionMaps('DEPARTMENT', department.id),
|
||||
}))
|
||||
}
|
||||
|
||||
async function loadPermissions() {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
if (isPermissionMockEnabled) {
|
||||
roles.value = rolePermissionRows.map((role) => ({
|
||||
roleRows.value = rolePermissionRows.map((role) => ({
|
||||
...role,
|
||||
permissions: { ...role.permissions },
|
||||
}))
|
||||
departments.value = departmentPermissionRows.map((row) => ({ ...row }))
|
||||
departmentRows.value = departmentPermissionRows.map((row) => ({
|
||||
...row,
|
||||
permissions: { ...row.permissions },
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
const [templatesResult, rolesResult, departmentsResult, usersResult] = await Promise.allSettled(
|
||||
[
|
||||
getDocuments({ page: 1, pageSize: 200, status: 'all' }),
|
||||
getRoles(),
|
||||
getDepartments(),
|
||||
getUsers({ page: 1, pageSize: 200, status: 'all' }),
|
||||
],
|
||||
)
|
||||
const [templatesResult, rolesResult, departmentsResult] = await Promise.allSettled([
|
||||
getDocuments({ page: 1, pageSize: 200, status: 'all' }),
|
||||
getRoles({ page: 1, pageSize: 200 }),
|
||||
getDepartments(),
|
||||
])
|
||||
|
||||
if (templatesResult.status === 'rejected') {
|
||||
throw templatesResult.reason
|
||||
}
|
||||
|
||||
templates.value = templatesResult.value.records
|
||||
roleRecords.value = rolesResult.status === 'fulfilled' ? rolesResult.value : []
|
||||
departmentRecords.value =
|
||||
departmentsResult.status === 'fulfilled' ? departmentsResult.value : []
|
||||
syncPermissionRows()
|
||||
|
||||
if (rolesResult.status === 'rejected' || departmentsResult.status === 'rejected') {
|
||||
ElMessage.warning('角色或科室列表加载不完整,部分授权对象暂不可配置')
|
||||
}
|
||||
|
||||
selectedTemplateId.value = templates.value[0]?.id ?? ''
|
||||
permissionSubjects.value = [
|
||||
...(rolesResult.status === 'fulfilled'
|
||||
? rolesResult.value.map((role) => ({
|
||||
type: 'ROLE' as const,
|
||||
id: role.id,
|
||||
label: role.name,
|
||||
}))
|
||||
: []),
|
||||
...(departmentsResult.status === 'fulfilled'
|
||||
? departmentsResult.value.map((department) => ({
|
||||
type: 'DEPARTMENT' as const,
|
||||
id: department.id,
|
||||
label: department.name,
|
||||
}))
|
||||
: []),
|
||||
...(usersResult.status === 'fulfilled'
|
||||
? usersResult.value.records.map((user) => ({
|
||||
type: 'USER' as const,
|
||||
id: user.id,
|
||||
label: `${user.name}${user.employeeNo ? `(${user.employeeNo})` : ''}`,
|
||||
}))
|
||||
: []),
|
||||
]
|
||||
await loadTemplatePermissions()
|
||||
} catch {
|
||||
roleRecords.value = []
|
||||
departmentRecords.value = []
|
||||
roleRows.value = []
|
||||
departmentRows.value = []
|
||||
templates.value = []
|
||||
permissionSubjects.value = []
|
||||
templatePermissions.value = []
|
||||
permissionError.value = false
|
||||
ElMessage.error('权限配置加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -99,76 +172,170 @@ async function loadPermissions() {
|
||||
}
|
||||
|
||||
async function loadTemplatePermissions() {
|
||||
if (!selectedTemplateId.value || isPermissionMockEnabled) {
|
||||
const templateId = selectedTemplateId.value
|
||||
const requestId = ++permissionRequestId
|
||||
|
||||
if (!templateId || isPermissionMockEnabled) {
|
||||
templatePermissions.value = []
|
||||
permissionError.value = false
|
||||
return
|
||||
}
|
||||
|
||||
permissionLoading.value = true
|
||||
permissionError.value = false
|
||||
|
||||
try {
|
||||
templatePermissions.value = await getTemplatePermissions(selectedTemplateId.value)
|
||||
const records = await getTemplatePermissions(templateId)
|
||||
|
||||
if (requestId !== permissionRequestId || templateId !== selectedTemplateId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
templatePermissions.value = records
|
||||
syncPermissionRows()
|
||||
} catch {
|
||||
if (requestId !== permissionRequestId) {
|
||||
return
|
||||
}
|
||||
|
||||
templatePermissions.value = []
|
||||
permissionError.value = true
|
||||
syncPermissionRows()
|
||||
ElMessage.error('模板权限加载失败,请稍后重试')
|
||||
} finally {
|
||||
permissionLoading.value = false
|
||||
if (requestId === permissionRequestId) {
|
||||
permissionLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRolePermission(roleId: string, permission: PermissionKey) {
|
||||
const role = roles.value.find((item) => item.id === roleId)
|
||||
|
||||
if (!role) {
|
||||
if (isPermissionMockEnabled) {
|
||||
const role = roleRows.value.find((item) => item.id === roleId)
|
||||
if (role) {
|
||||
role.permissions[permission] = !role.permissions[permission]
|
||||
ElMessage.info('演示数据已调整,真实环境将通过权限接口保存')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
role.permissions[permission] = !role.permissions[permission]
|
||||
ElMessage.info('权限已调整并记录审计日志')
|
||||
void setSubjectPermission('ROLE', roleId, permission)
|
||||
}
|
||||
|
||||
function adjustDepartmentPermission(row: DepartmentPermissionRow) {
|
||||
ElMessage.info(`原型演示:调整${row.department}文档权限`)
|
||||
}
|
||||
|
||||
async function addTemplatePermission(form: TemplatePermissionForm) {
|
||||
if (!selectedTemplateId.value) {
|
||||
function toggleDepartmentPermission(departmentId: string, permission: PermissionKey) {
|
||||
if (isPermissionMockEnabled) {
|
||||
const row = departmentRows.value.find((item) => item.id === departmentId)
|
||||
if (row) {
|
||||
row.permissions[permission] = !row.permissions[permission]
|
||||
ElMessage.info('演示数据已调整,真实环境将通过权限接口保存')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
void setSubjectPermission('DEPARTMENT', departmentId, permission)
|
||||
}
|
||||
|
||||
async function applyTemplatePermission(
|
||||
templateId: string,
|
||||
subjectType: PermissionSubjectType,
|
||||
subjectId: string,
|
||||
permission: PermissionKey,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const matchingRecords = templatePermissions.value.filter(
|
||||
(record) =>
|
||||
record.subjectType === subjectType &&
|
||||
record.subjectId === subjectId &&
|
||||
record.permissionLevel === permission,
|
||||
)
|
||||
const directAllows = matchingRecords.filter(
|
||||
(record) => !record.inherited && record.effect === 'ALLOW',
|
||||
)
|
||||
const directDenies = matchingRecords.filter(
|
||||
(record) => !record.inherited && record.effect === 'DENY',
|
||||
)
|
||||
const inheritedAllows = matchingRecords.some(
|
||||
(record) => record.inherited && record.effect === 'ALLOW',
|
||||
)
|
||||
|
||||
if (enabled) {
|
||||
for (const binding of directDenies) {
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接拒绝权限缺少绑定 ID,无法撤销')
|
||||
}
|
||||
await deleteTemplatePermission(templateId, binding.id)
|
||||
}
|
||||
|
||||
if (!directAllows.length && !inheritedAllows) {
|
||||
await createTemplatePermission(templateId, {
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'ALLOW',
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for (const binding of directAllows) {
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接允许权限缺少绑定 ID,无法撤销')
|
||||
}
|
||||
await deleteTemplatePermission(templateId, binding.id)
|
||||
}
|
||||
|
||||
if (!directDenies.length && inheritedAllows) {
|
||||
await createTemplatePermission(templateId, {
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'DENY',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function setSubjectPermission(
|
||||
subjectType: PermissionSubjectType,
|
||||
subjectId: string,
|
||||
permission: PermissionKey,
|
||||
) {
|
||||
if (!selectedTemplateId.value || permissionSaving.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const current = resolvePermission(subjectType, subjectId, permission)
|
||||
|
||||
if (current.state === 'inherited-deny') {
|
||||
ElMessage.warning('该权限由上级规则拒绝,当前接口不支持直接覆盖')
|
||||
return
|
||||
}
|
||||
|
||||
const nextEnabled = !current.enabled
|
||||
permissionSaving.value = true
|
||||
|
||||
try {
|
||||
await createTemplatePermission(selectedTemplateId.value, form)
|
||||
await applyTemplatePermission(
|
||||
selectedTemplateId.value,
|
||||
subjectType,
|
||||
subjectId,
|
||||
permission,
|
||||
nextEnabled,
|
||||
)
|
||||
await loadTemplatePermissions()
|
||||
ElMessage.success('模板权限已添加')
|
||||
} catch {
|
||||
ElMessage.error('模板权限添加失败,请稍后重试')
|
||||
ElMessage.success(nextEnabled ? '权限已开启' : '权限已关闭')
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '权限调整失败,请稍后重试')
|
||||
} finally {
|
||||
permissionSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTemplatePermissionRecord(permissionId: string) {
|
||||
if (!selectedTemplateId.value || !window.confirm('确定删除这条模板权限吗?')) {
|
||||
return
|
||||
}
|
||||
|
||||
permissionSaving.value = true
|
||||
|
||||
try {
|
||||
await deleteTemplatePermission(selectedTemplateId.value, permissionId)
|
||||
await loadTemplatePermissions()
|
||||
ElMessage.success('模板权限已删除')
|
||||
} catch {
|
||||
ElMessage.error('模板权限删除失败,请稍后重试')
|
||||
} finally {
|
||||
permissionSaving.value = false
|
||||
}
|
||||
function handleTemplateChange(event: Event) {
|
||||
selectedTemplateId.value = (event.target as HTMLSelectElement).value
|
||||
void loadTemplatePermissions()
|
||||
}
|
||||
|
||||
function selectTemplate(templateId: string) {
|
||||
selectedTemplateId.value = templateId
|
||||
function retryTemplatePermissions() {
|
||||
void loadTemplatePermissions()
|
||||
}
|
||||
|
||||
@@ -196,44 +363,79 @@ onMounted(loadPermissions)
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isPermissionMockEnabled">
|
||||
<template v-else>
|
||||
<section class="page-card">
|
||||
<div class="card-heading">
|
||||
<h2>按角色授权</h2>
|
||||
<div class="card-heading section-heading">
|
||||
<div class="heading-copy">
|
||||
<h2>按角色授权</h2>
|
||||
<p>选择文档模板后,调整各个角色的可见、可用和可维护权限。</p>
|
||||
</div>
|
||||
<label v-if="!isPermissionMockEnabled" 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>
|
||||
</div>
|
||||
|
||||
<div v-if="!isPermissionMockEnabled && !selectedTemplateId" class="empty-state">
|
||||
暂无可配置的文档模板
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionLoading" class="loading-state">
|
||||
正在加载模板权限…
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionError" class="error-state">
|
||||
<span>模板权限加载失败</span>
|
||||
<button type="button" class="retry-button" @click="retryTemplatePermissions">
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
<RolePermissionMatrix
|
||||
:rows="roles"
|
||||
v-else
|
||||
:rows="roleRows"
|
||||
:columns="permissionColumns"
|
||||
:saving="permissionSaving"
|
||||
@toggle="toggleRolePermission"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="page-card department-card">
|
||||
<div class="card-heading">
|
||||
<h2>按科室授权</h2>
|
||||
<span>科室级别的文档可用范围</span>
|
||||
<div class="card-heading section-heading">
|
||||
<div class="heading-copy">
|
||||
<h2>按科室授权</h2>
|
||||
<p>
|
||||
{{ selectedTemplate ? selectedTemplate.name : '当前文档模板' }}:调整各个科室的权限。
|
||||
</p>
|
||||
</div>
|
||||
<span v-if="selectedTemplate" class="template-meta">
|
||||
{{ selectedTemplate.code || selectedTemplate.id }} · {{ selectedTemplate.category }}
|
||||
</span>
|
||||
</div>
|
||||
<DepartmentPermissionTable :rows="departments" @adjust="adjustDepartmentPermission" />
|
||||
|
||||
<div v-if="!isPermissionMockEnabled && !selectedTemplateId" class="empty-state">
|
||||
暂无可配置的文档模板
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionLoading" class="loading-state">
|
||||
正在加载模板权限…
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionError" class="error-state">
|
||||
<span>模板权限加载失败</span>
|
||||
<button type="button" class="retry-button" @click="retryTemplatePermissions">
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
<DepartmentPermissionTable
|
||||
v-else
|
||||
:rows="departmentRows"
|
||||
:columns="permissionColumns"
|
||||
:saving="permissionSaving"
|
||||
@toggle="toggleDepartmentPermission"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<section v-else class="page-card">
|
||||
<div class="card-heading">
|
||||
<h2>模板权限</h2>
|
||||
<span>按模板配置角色、科室和用户的可见/可用/可维护权限</span>
|
||||
</div>
|
||||
<TemplatePermissionPanel
|
||||
:templates="templates"
|
||||
:selected-template-id="selectedTemplateId"
|
||||
:permissions="templatePermissions"
|
||||
:subjects="permissionSubjects"
|
||||
:loading="permissionLoading"
|
||||
:saving="permissionSaving"
|
||||
@update:selected-template-id="selectTemplate"
|
||||
@add="addTemplatePermission"
|
||||
@delete="deleteTemplatePermissionRecord"
|
||||
/>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -263,6 +465,15 @@ onMounted(loadPermissions)
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.heading-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
@@ -270,8 +481,14 @@ onMounted(loadPermissions)
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-heading span {
|
||||
margin-left: auto;
|
||||
.heading-copy p {
|
||||
margin: 5px 0 0;
|
||||
color: var(--mut);
|
||||
font-size: 11.5px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.card-heading > span {
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -283,8 +500,37 @@ onMounted(loadPermissions)
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 6px;
|
||||
min-width: min(360px, 100%);
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
padding-bottom: 8px;
|
||||
font-size: 11.5px !important;
|
||||
}
|
||||
|
||||
.department-card {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -317,6 +563,38 @@ onMounted(loadPermissions)
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state,
|
||||
.error-state {
|
||||
padding: 28px 16px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
background: #f8fbfc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--err);
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
padding: 4px 10px;
|
||||
color: var(--brand);
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.retry-button:hover {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
@@ -327,13 +605,28 @@ onMounted(loadPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.section-heading {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.card-heading {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-heading span {
|
||||
.card-heading > span {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ import type { DepartmentPermissionRow, PermissionColumn, RolePermissionRow } fro
|
||||
|
||||
export const permissionColumns: PermissionColumn[] = [
|
||||
{
|
||||
key: 'visible',
|
||||
key: 'VIEW',
|
||||
label: '可见',
|
||||
description: '可浏览模板',
|
||||
},
|
||||
{
|
||||
key: 'usable',
|
||||
key: 'USE',
|
||||
label: '可用',
|
||||
description: '可发起签署',
|
||||
},
|
||||
{
|
||||
key: 'maintainable',
|
||||
key: 'MAINTAIN',
|
||||
label: '可维护',
|
||||
description: '可编辑、停用模板',
|
||||
},
|
||||
@@ -26,9 +26,9 @@ export const rolePermissionRows: RolePermissionRow[] = [
|
||||
description: '系统配置、用户与权限管理、审计日志',
|
||||
userCount: 2,
|
||||
permissions: {
|
||||
visible: true,
|
||||
usable: true,
|
||||
maintainable: true,
|
||||
VIEW: true,
|
||||
USE: true,
|
||||
MAINTAIN: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -38,9 +38,9 @@ export const rolePermissionRows: RolePermissionRow[] = [
|
||||
description: '全院文档库维护、报表分析、任务监督',
|
||||
userCount: 4,
|
||||
permissions: {
|
||||
visible: true,
|
||||
usable: true,
|
||||
maintainable: false,
|
||||
VIEW: true,
|
||||
USE: true,
|
||||
MAINTAIN: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -50,9 +50,9 @@ export const rolePermissionRows: RolePermissionRow[] = [
|
||||
description: '本科室文档发起签署、手写板 / 短信操作',
|
||||
userCount: 23,
|
||||
permissions: {
|
||||
visible: false,
|
||||
usable: false,
|
||||
maintainable: false,
|
||||
VIEW: false,
|
||||
USE: false,
|
||||
MAINTAIN: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -62,9 +62,9 @@ export const rolePermissionRows: RolePermissionRow[] = [
|
||||
description: '只读查询报表与审计留痕,不可发起',
|
||||
userCount: 3,
|
||||
permissions: {
|
||||
visible: false,
|
||||
usable: false,
|
||||
maintainable: false,
|
||||
VIEW: false,
|
||||
USE: false,
|
||||
MAINTAIN: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -73,36 +73,26 @@ export const departmentPermissionRows: DepartmentPermissionRow[] = [
|
||||
{
|
||||
id: 'radiology',
|
||||
department: '放射科',
|
||||
ownDocuments: 'available',
|
||||
commonDocuments: 'available',
|
||||
otherDocuments: 'invisible',
|
||||
permissions: { VIEW: true, USE: true, MAINTAIN: false },
|
||||
},
|
||||
{
|
||||
id: 'cardiology',
|
||||
department: '心内科',
|
||||
ownDocuments: 'available',
|
||||
commonDocuments: 'available',
|
||||
otherDocuments: 'invisible',
|
||||
permissions: { VIEW: true, USE: true, MAINTAIN: false },
|
||||
},
|
||||
{
|
||||
id: 'orthopedics',
|
||||
department: '骨科',
|
||||
ownDocuments: 'available',
|
||||
commonDocuments: 'available',
|
||||
otherDocuments: 'invisible',
|
||||
permissions: { VIEW: true, USE: true, MAINTAIN: false },
|
||||
},
|
||||
{
|
||||
id: 'health-management',
|
||||
department: '健康管理中心',
|
||||
ownDocuments: 'available',
|
||||
commonDocuments: 'available',
|
||||
otherDocuments: 'invisible',
|
||||
permissions: { VIEW: true, USE: true, MAINTAIN: false },
|
||||
},
|
||||
{
|
||||
id: 'general',
|
||||
department: '全院通用',
|
||||
ownDocuments: 'available',
|
||||
commonDocuments: 'available',
|
||||
otherDocuments: 'invisible',
|
||||
permissions: { VIEW: true, USE: true, MAINTAIN: false },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export type PermissionKey = 'visible' | 'usable' | 'maintainable'
|
||||
export type PermissionKey = 'VIEW' | 'USE' | 'MAINTAIN'
|
||||
|
||||
export type PermissionCellState =
|
||||
'none' | 'direct-allow' | 'direct-deny' | 'inherited-allow' | 'inherited-deny'
|
||||
|
||||
export interface PermissionColumn {
|
||||
key: PermissionKey
|
||||
@@ -13,16 +16,15 @@ export interface RolePermissionRow {
|
||||
description: string
|
||||
userCount: number
|
||||
permissions: Record<PermissionKey, boolean>
|
||||
permissionStates?: Record<PermissionKey, PermissionCellState>
|
||||
}
|
||||
|
||||
export type DepartmentDocumentAccess = 'available' | 'invisible'
|
||||
|
||||
export interface DepartmentPermissionRow {
|
||||
id: string
|
||||
department: string
|
||||
ownDocuments: DepartmentDocumentAccess
|
||||
commonDocuments: DepartmentDocumentAccess
|
||||
otherDocuments: DepartmentDocumentAccess
|
||||
campus?: string
|
||||
permissions: Record<PermissionKey, boolean>
|
||||
permissionStates?: Record<PermissionKey, PermissionCellState>
|
||||
}
|
||||
|
||||
export interface PermissionSubjectOption {
|
||||
|
||||
Reference in New Issue
Block a user