diff --git a/clinical-web/.env.example b/clinical-web/.env.example index 65734d5..8ebc4b1 100644 --- a/clinical-web/.env.example +++ b/clinical-web/.env.example @@ -1,3 +1,4 @@ VITE_APP_NAME=medical-sign-clinical VITE_API_BASE_URL=/api -VITE_USE_MOCK=true +# 联调默认走真实后端;仅在演示原型时改为 true。 +VITE_USE_MOCK=false diff --git a/clinical-web/README.md b/clinical-web/README.md index f57fe84..d3c11a6 100644 --- a/clinical-web/README.md +++ b/clinical-web/README.md @@ -178,6 +178,6 @@ API 请求和响应类型按业务域放在 `api/workbench/types.ts`、`api/mana 开发环境默认使用 `/api` 作为同源接口前缀,Vite 会将 `/api` 转发到 `https://ipad.shenynet.com`,因此浏览器不会直接跨域请求后端。代理配置位于 `vite.config.ts`,不改写 `/api/v1/...` 路径。修改代理或环境变量后需要重启 Vite 开发服务。 -生产环境不会使用 Vite 的开发代理,需要在 Nginx 或其他网关中配置同样的 `/api` 反向代理。除登录外,当前业务 API 模块默认返回 Mock 数据,设置 `VITE_USE_MOCK=false` 后切换已实现的真实接口。使用本地开发代理时,`VITE_API_BASE_URL` 应填写 `/api`;如果改为直连后端,则需要后端配置允许当前前端源的 CORS。 +生产环境不会使用 Vite 的开发代理,需要在 Nginx 或其他网关中配置同样的 `/api` 反向代理。文档权限和用户权限相关管理接口默认调用真实后端,仅在显式设置 `VITE_USE_MOCK=true` 时使用 Mock 数据。使用本地开发代理时,`VITE_API_BASE_URL` 应填写 `/api`;如果改为直连后端,则需要后端配置允许当前前端源的 CORS。 -当前尚未接入的签署能力包括手写板设备桥接、线上签署页面/签名回调和打印服务。签署投递、一次性 Token 消费和真实 PNG 上传 API 已完成封装,但页面不能用 Canvas 演示数据冒充真实签名;需要接入设备适配器或患者 H5 回调后再启用。短信初次发送接口需要完整手机号,而患者查询只返回脱敏手机号,因此真实模式会要求当前操作人员确认完整投递地址;生产环境也可以改为由后端根据患者 ID 解析投递地址。默认 Mock 模式仍可用于演示完整交互,联调时设置 `VITE_USE_MOCK=false`。 +当前尚未接入的签署能力包括手写板设备桥接、线上签署页面/签名回调和打印服务。签署投递、一次性 Token 消费和真实 PNG 上传 API 已完成封装,但页面不能用 Canvas 演示数据冒充真实签名;需要接入设备适配器或患者 H5 回调后再启用。短信初次发送接口需要完整手机号,而患者查询只返回脱敏手机号,因此真实模式会要求当前操作人员确认完整投递地址;生产环境也可以改为由后端根据患者 ID 解析投递地址。需要演示完整交互时再设置 `VITE_USE_MOCK=true`。 diff --git a/clinical-web/docs/document-permissions-api.md b/clinical-web/docs/document-permissions-api.md index 0e436e0..1516b4c 100644 --- a/clinical-web/docs/document-permissions-api.md +++ b/clinical-web/docs/document-permissions-api.md @@ -80,6 +80,8 @@ ## 当前核验结论 +文档权限页面仅在显式设置 `VITE_USE_MOCK=true` 时使用演示数据;未设置该变量或设置为其他值时,页面调用真实权限接口。修改环境变量后需要重启 Vite。 + 本轮新增接口已覆盖当前文档权限页面的后端缺口,暂无阻断前端接入的缺失接口。跨模板矩阵查询最多支持 100 个模板,批量保存最多支持 200 项;页面当前采用“模板选择 + 当前模板矩阵”的交互,没有臆造跨模板对比表。 联调验收至少包括: diff --git a/clinical-web/src/api/management/document-permissions.ts b/clinical-web/src/api/management/document-permissions.ts index cc55b73..b3861e1 100644 --- a/clinical-web/src/api/management/document-permissions.ts +++ b/clinical-web/src/api/management/document-permissions.ts @@ -6,9 +6,16 @@ import type { BackendCollection, BackendPage, CreateTemplatePermissionRequest, + DepartmentPermissionPolicyRecord, PermissionListResponse, + PermissionPolicyLevel, + PermissionPoliciesRecord, + PermissionPoliciesResponseDto, + PermissionPolicyDepartmentDto, + PermissionPolicyRoleDto, PermissionQuery, PermissionRecord, + RolePermissionPolicyRecord, TemplatePermissionBatchRequest, TemplatePermissionBatchResponse, TemplatePermissionBatchResponseDto, @@ -19,9 +26,12 @@ import type { TemplatePermissionMatrixResponse, TemplatePermissionRecord, TemplatePermissionResponseDto, + UpdateDepartmentPermissionPolicyRequest, + UpdateRolePermissionPolicyRequest, } from './types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示数据,避免联调环境因未注入变量而误入 Mock 分支。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' export const isPermissionMockEnabled = useMockData @@ -44,6 +54,473 @@ const mockRecords: PermissionRecord[] = [ }, ] +const mockPermissionPolicies: PermissionPoliciesRecord = { + campusId: 'mock-main', + roles: [ + { + roleId: 'system-admin', + code: 'SYSTEM_ADMIN', + name: '系统管理员', + description: '系统配置、用户与权限管理、审计日志', + userCount: 2, + campusId: 'mock-main', + view: true, + use: true, + maintain: true, + permissionLevel: 'MAINTAIN', + }, + { + roleId: 'medical-management', + code: 'MEDICAL_MANAGEMENT', + name: '医务管理', + description: '全院文档库维护、报表分析、任务监督', + userCount: 4, + campusId: 'mock-main', + view: true, + use: true, + maintain: true, + permissionLevel: 'MAINTAIN', + }, + { + roleId: 'department-signer', + code: 'DEPARTMENT_SIGNER', + name: '科室签署员', + description: '本科室文档发起签署、手写板 / 短信操作', + userCount: 23, + campusId: 'mock-main', + view: true, + use: true, + maintain: false, + permissionLevel: 'USE', + }, + { + roleId: 'audit-viewer', + code: 'AUDIT_VIEWER', + name: '查询审计员', + description: '只读查询报表与审计留痕,不可发起', + userCount: 3, + campusId: 'mock-main', + view: true, + use: false, + maintain: false, + permissionLevel: 'VIEW', + }, + ], + departments: [ + { + departmentId: 'mock-department-radiology', + code: 'RADIOLOGY', + name: '放射科', + campusId: 'mock-main', + own: true, + common: true, + other: false, + ownPermission: 'USE', + commonPermission: 'USE', + otherPermission: 'NONE', + }, + { + departmentId: 'mock-department-cardiology', + code: 'CARDIOLOGY', + name: '心内科', + campusId: 'mock-main', + own: true, + common: true, + other: false, + ownPermission: 'USE', + commonPermission: 'USE', + otherPermission: 'NONE', + }, + { + departmentId: 'mock-department-orthopedics', + code: 'ORTHOPEDICS', + name: '骨科', + campusId: 'mock-main', + own: true, + common: true, + other: false, + ownPermission: 'USE', + commonPermission: 'USE', + otherPermission: 'NONE', + }, + { + departmentId: 'mock-department-health', + code: 'HEALTH_MANAGEMENT', + name: '健康管理中心', + campusId: 'mock-main', + own: true, + common: true, + other: false, + ownPermission: 'USE', + commonPermission: 'USE', + otherPermission: 'NONE', + }, + ], + templateCount: 2, +} + +type PermissionPolicyObject = Record + +function asObject(value: unknown): PermissionPolicyObject { + return value !== null && typeof value === 'object' && !Array.isArray(value) + ? (value as PermissionPolicyObject) + : {} +} + +function asArray(value: unknown): unknown[] { + return Array.isArray(value) ? value : [] +} + +function readString(source: PermissionPolicyObject, keys: string[], fallback = '') { + for (const key of keys) { + const value = source[key] + if (typeof value === 'string' && value.trim()) { + return value.trim() + } + } + + return fallback +} + +function readNumber(source: PermissionPolicyObject, keys: string[]) { + for (const key of keys) { + const value = source[key] + if (typeof value === 'number' && Number.isFinite(value)) { + return value + } + if (typeof value === 'string' && value.trim() && Number.isFinite(Number(value))) { + return Number(value) + } + } + + return undefined +} + +function readBoolean(source: PermissionPolicyObject, keys: string[]) { + for (const key of keys) { + const value = source[key] + + if (typeof value === 'boolean') { + return value + } + + if (typeof value === 'number') { + return value !== 0 + } + + if (typeof value === 'string') { + const normalized = value.trim().toLowerCase() + if (['true', '1', 'yes', 'allow', 'allowed', 'enabled', 'on'].includes(normalized)) { + return true + } + if (['false', '0', 'no', 'deny', 'denied', 'disabled', 'off'].includes(normalized)) { + return false + } + } + + if (value !== null && typeof value === 'object') { + const nested = asObject(value) + const nestedValue = nested.enabled ?? nested.allowed ?? nested.value + if (typeof nestedValue === 'boolean') { + return nestedValue + } + } + } + + return false +} + +function readVersion(source: PermissionPolicyObject) { + return readNumber(source, ['version', 'policyVersion', 'revision']) +} + +const policyLevelRank: Record = { + NONE: 0, + VIEW: 1, + USE: 2, + MAINTAIN: 3, +} + +function normalizePolicyLevel(value: unknown): PermissionPolicyLevel | undefined { + if (typeof value !== 'string') { + return undefined + } + + const level = value.trim().toUpperCase() as PermissionPolicyLevel + return level in policyLevelRank ? level : undefined +} + +function policyLevelFromFlags(source: PermissionPolicyObject, keys: string[]) { + let level: PermissionPolicyLevel = 'NONE' + + keys.forEach((key, index) => { + if (readBoolean(source, [key])) { + const nextLevel = (['VIEW', 'USE', 'MAINTAIN'] as const)[index] + if (policyLevelRank[nextLevel] > policyLevelRank[level]) { + level = nextLevel + } + } + }) + + return level +} + +function readPolicyLevel( + source: PermissionPolicyObject, + levelKeys: string[], + flagKeys: string[], +): PermissionPolicyLevel { + for (const key of levelKeys) { + const level = normalizePolicyLevel(source[key]) + if (level) { + return level + } + } + + return policyLevelFromFlags(source, flagKeys) +} + +function flagsForPolicyLevel(level: PermissionPolicyLevel) { + const rank = policyLevelRank[level] + return { + view: rank >= policyLevelRank.VIEW, + use: rank >= policyLevelRank.USE, + maintain: rank >= policyLevelRank.MAINTAIN, + } +} + +function departmentFlagsForPolicyLevel(level: PermissionPolicyLevel) { + return level !== 'NONE' +} + +function mapRolePolicy( + dto: PermissionPolicyRoleDto | PermissionPolicyObject, + fallbackRoleId = '', + fallbackCampusId = '', +): RolePermissionPolicyRecord | null { + const source = asObject(dto) + const roleId = readString(source, ['roleId', 'roleID', 'id'], fallbackRoleId) + + if (!roleId) { + return null + } + + const permissionLevel = readPolicyLevel( + source, + ['permissionLevel', 'maxPermissionLevel', 'level'], + ['view', 'use', 'maintain'], + ) + + return { + roleId, + campusId: readString(source, ['campusId', 'campusID'], fallbackCampusId), + code: readString(source, ['code', 'roleCode'], roleId), + name: readString(source, ['name', 'roleName'], roleId), + description: readString(source, ['description'], '—'), + userCount: readNumber(source, ['userCount', 'users', 'memberCount']) ?? null, + ...flagsForPolicyLevel(permissionLevel), + permissionLevel, + version: readVersion(source), + } +} + +function mapDepartmentPolicy( + dto: PermissionPolicyDepartmentDto | PermissionPolicyObject, + fallbackCampusId = '', +): DepartmentPermissionPolicyRecord | null { + const source = asObject(dto) + const departmentId = readString(source, ['departmentId', 'departmentID', 'id']) + + if (!departmentId) { + return null + } + + const ownPermission = readPolicyLevel( + source, + ['ownPermission', 'ownLevel'], + ['own', 'ownDocuments', 'ownDocument', 'ownEnabled'], + ) + const commonPermission = readPolicyLevel( + source, + ['commonPermission', 'commonLevel'], + ['common', 'commonDocuments', 'commonDocument', 'commonEnabled'], + ) + const otherPermission = readPolicyLevel( + source, + ['otherPermission', 'otherLevel'], + ['other', 'otherDocuments', 'otherDocument', 'otherEnabled'], + ) + + return { + departmentId, + code: readString(source, ['code', 'departmentCode'], departmentId), + name: readString(source, ['name', 'departmentName'], departmentId), + campusId: readString(source, ['campusId', 'campusID'], fallbackCampusId), + own: departmentFlagsForPolicyLevel(ownPermission), + common: departmentFlagsForPolicyLevel(commonPermission), + other: departmentFlagsForPolicyLevel(otherPermission), + ownPermission, + commonPermission, + otherPermission, + version: readVersion(source), + } +} + +function normalizePermissionPolicies( + data: PermissionPoliciesResponseDto | PermissionPolicyObject, + requestedCampusId = '', +): PermissionPoliciesRecord { + const source = asObject(data) + const campusId = readString(source, ['campusId', 'campusID'], requestedCampusId) + const roleDtos = asArray(source.roles ?? source.rolePolicies ?? source.rolePermissionPolicies) + const departmentDtos = asArray( + source.departments ?? source.departmentPolicies ?? source.departmentPermissionPolicies, + ) + const roles = roleDtos + .map((role) => mapRolePolicy(role as PermissionPolicyObject, '', campusId)) + .filter((role): role is RolePermissionPolicyRecord => Boolean(role)) + const departments = departmentDtos + .map((department) => mapDepartmentPolicy(department as PermissionPolicyObject, campusId)) + .filter((department): department is DepartmentPermissionPolicyRecord => Boolean(department)) + + return { + campusId, + roles, + departments, + templateCount: readNumber(source, ['templateCount', 'templatesCount', 'count']) ?? 0, + } +} + +function mapUpdatedRolePolicy( + data: PermissionPolicyRoleDto | PermissionPolicyObject, + roleId: string, + previous: RolePermissionPolicyRecord, +) { + return ( + mapRolePolicy(data, roleId, previous.campusId) ?? { + ...previous, + roleId, + } + ) +} + +function mapUpdatedDepartmentPolicy( + data: PermissionPolicyDepartmentDto | PermissionPolicyObject, + departmentId: string, + previous: DepartmentPermissionPolicyRecord, +) { + return ( + mapDepartmentPolicy(data, previous.campusId) ?? { + ...previous, + departmentId, + } + ) +} + +export async function getPermissionPolicies(campusId?: string): Promise { + if (useMockData) { + return { + ...mockPermissionPolicies, + campusId: campusId ?? mockPermissionPolicies.campusId, + roles: mockPermissionPolicies.roles.map((role) => ({ ...role })), + departments: mockPermissionPolicies.departments.map((department) => ({ ...department })), + } + } + + const response = await request.get>( + '/v1/templates/permission-policies', + { params: campusId ? { campusId } : undefined }, + ) + return normalizePermissionPolicies(unwrapApiResponse(response), campusId) +} + +export async function updateRolePermissionPolicy( + roleId: string, + payload: UpdateRolePermissionPolicyRequest, + previous?: RolePermissionPolicyRecord, +): Promise { + if (useMockData) { + const role = mockPermissionPolicies.roles.find((item) => item.roleId === roleId) + if (!role) { + throw new Error('角色默认策略不存在') + } + Object.assign(role, flagsForPolicyLevel(payload.permissionLevel), { + permissionLevel: payload.permissionLevel, + campusId: payload.campusId, + version: (role.version ?? 0) + 1, + }) + return { ...role } + } + + const response = await request.put>( + `/v1/templates/permission-policies/roles/${encodeURIComponent(roleId)}`, + payload, + ) + return mapUpdatedRolePolicy( + unwrapApiResponse(response), + roleId, + previous ?? { + roleId, + campusId: payload.campusId, + code: roleId, + name: roleId, + description: '—', + userCount: null, + ...flagsForPolicyLevel(payload.permissionLevel), + permissionLevel: payload.permissionLevel, + version: payload.expectedVersion, + }, + ) +} + +export async function updateDepartmentPermissionPolicy( + departmentId: string, + payload: UpdateDepartmentPermissionPolicyRequest, + previous?: DepartmentPermissionPolicyRecord, +): Promise { + if (useMockData) { + const department = mockPermissionPolicies.departments.find( + (item) => item.departmentId === departmentId, + ) + if (!department) { + throw new Error('科室默认策略不存在') + } + Object.assign(department, { + own: departmentFlagsForPolicyLevel(payload.ownPermission), + common: departmentFlagsForPolicyLevel(payload.commonPermission), + other: departmentFlagsForPolicyLevel(payload.otherPermission), + ownPermission: payload.ownPermission, + commonPermission: payload.commonPermission, + otherPermission: payload.otherPermission, + campusId: payload.campusId, + version: (department.version ?? 0) + 1, + }) + return { ...department } + } + + const response = await request.put>( + `/v1/templates/permission-policies/departments/${encodeURIComponent(departmentId)}`, + payload, + ) + return mapUpdatedDepartmentPolicy( + unwrapApiResponse(response), + departmentId, + previous ?? { + departmentId, + code: departmentId, + name: departmentId, + campusId: payload.campusId, + own: departmentFlagsForPolicyLevel(payload.ownPermission), + common: departmentFlagsForPolicyLevel(payload.commonPermission), + other: departmentFlagsForPolicyLevel(payload.otherPermission), + ownPermission: payload.ownPermission, + commonPermission: payload.commonPermission, + otherPermission: payload.otherPermission, + version: payload.expectedVersion, + }, + ) +} + export function getDocumentPermissions(query: PermissionQuery): Promise { if (!useMockData) { return Promise.reject( diff --git a/clinical-web/src/api/management/documents.ts b/clinical-web/src/api/management/documents.ts index 784f7cf..5cedc04 100644 --- a/clinical-web/src/api/management/documents.ts +++ b/clinical-web/src/api/management/documents.ts @@ -17,7 +17,8 @@ import type { TemplateVersionResponseDto, } from './types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示数据,避免联调环境因未注入变量而误入 Mock 分支。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' export const isDocumentMockEnabled = useMockData diff --git a/clinical-web/src/api/management/organization.ts b/clinical-web/src/api/management/organization.ts index 6a1b13b..3160ee0 100644 --- a/clinical-web/src/api/management/organization.ts +++ b/clinical-web/src/api/management/organization.ts @@ -13,7 +13,8 @@ import type { UpdateDepartmentRequest, } from './types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示数据,避免联调环境因未注入变量而误入 Mock 分支。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' function getRecords(data: BackendCollection) { return Array.isArray(data) ? data : (data.records ?? data.items ?? data.content ?? []) diff --git a/clinical-web/src/api/management/permissions.ts b/clinical-web/src/api/management/permissions.ts index 10358f6..0e82336 100644 --- a/clinical-web/src/api/management/permissions.ts +++ b/clinical-web/src/api/management/permissions.ts @@ -14,7 +14,8 @@ import type { UpdateApiPermissionRequest, } from './types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示权限目录。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' export const isApiPermissionMockEnabled = useMockData diff --git a/clinical-web/src/api/management/roles.ts b/clinical-web/src/api/management/roles.ts index 8544163..a25e9e7 100644 --- a/clinical-web/src/api/management/roles.ts +++ b/clinical-web/src/api/management/roles.ts @@ -14,7 +14,8 @@ import type { UpdateRoleRequest, } from './types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示数据,避免联调环境因未注入变量而误入 Mock 分支。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' const fallbackColors = ['#6b5bd2', '#0e6e8c', '#0f9d6c', '#d9821f', '#c65d5d'] diff --git a/clinical-web/src/api/management/types.ts b/clinical-web/src/api/management/types.ts index f785ed1..00c7448 100644 --- a/clinical-web/src/api/management/types.ts +++ b/clinical-web/src/api/management/types.ts @@ -538,6 +538,118 @@ export interface RolePermissionsUpdateRequest { expectedVersion?: number } +export type PermissionPolicyLevel = 'NONE' | 'VIEW' | 'USE' | 'MAINTAIN' + +/** 默认文档权限策略的后端 DTO。字段保留常见别名,由 API 边界统一为页面模型。 */ +export interface PermissionPolicyRoleDto { + roleId?: string | null + id?: string | null + campusId?: string | null + code?: string | null + roleCode?: string | null + name?: string | null + roleName?: string | null + description?: string | null + userCount?: number | null + view?: unknown + canView?: unknown + viewEnabled?: unknown + use?: unknown + canUse?: unknown + useEnabled?: unknown + maintain?: unknown + canMaintain?: unknown + maintainEnabled?: unknown + permissionLevel?: PermissionPolicyLevel | string | null + maxPermissionLevel?: PermissionPolicyLevel | string | null + version?: number | null +} + +export interface PermissionPolicyDepartmentDto { + departmentId?: string | null + id?: string | null + code?: string | null + departmentCode?: string | null + name?: string | null + departmentName?: string | null + campusId?: string | null + ownPermission?: PermissionPolicyLevel | string | null + commonPermission?: PermissionPolicyLevel | string | null + otherPermission?: PermissionPolicyLevel | string | null + own?: unknown + ownDocuments?: unknown + ownDocument?: unknown + ownEnabled?: unknown + common?: unknown + commonDocuments?: unknown + commonDocument?: unknown + commonEnabled?: unknown + other?: unknown + otherDocuments?: unknown + otherDocument?: unknown + otherEnabled?: unknown + version?: number | null +} + +export interface PermissionPoliciesResponseDto { + campusId?: string | null + roles?: PermissionPolicyRoleDto[] | null + rolePolicies?: PermissionPolicyRoleDto[] | null + departments?: PermissionPolicyDepartmentDto[] | null + departmentPolicies?: PermissionPolicyDepartmentDto[] | null + templateCount?: number | null + templatesCount?: number | null +} + +export interface RolePermissionPolicyRecord { + roleId: string + campusId: string + code: string + name: string + description: string + userCount: number | null + view: boolean + use: boolean + maintain: boolean + permissionLevel: PermissionPolicyLevel + version?: number +} + +export interface DepartmentPermissionPolicyRecord { + departmentId: string + version?: number + code: string + name: string + campusId: string + own: boolean + common: boolean + other: boolean + ownPermission: PermissionPolicyLevel + commonPermission: PermissionPolicyLevel + otherPermission: PermissionPolicyLevel +} + +export interface PermissionPoliciesRecord { + campusId: string + roles: RolePermissionPolicyRecord[] + departments: DepartmentPermissionPolicyRecord[] + templateCount: number +} + +export interface UpdateRolePermissionPolicyRequest { + campusId: string + permissionLevel: PermissionPolicyLevel + expectedVersion?: number +} + +export interface UpdateDepartmentPermissionPolicyRequest { + campusId: string + ownPermission: PermissionPolicyLevel + commonPermission: PermissionPolicyLevel + otherPermission: PermissionPolicyLevel + expectedVersion?: number +} + export interface PasswordResetRequest { newPassword: string forceChangeOnNextLogin?: boolean diff --git a/clinical-web/src/api/management/users.ts b/clinical-web/src/api/management/users.ts index 630073f..ac2198e 100644 --- a/clinical-web/src/api/management/users.ts +++ b/clinical-web/src/api/management/users.ts @@ -22,7 +22,8 @@ import type { UserRolesUpdateRequest, } from './types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示数据,避免联调环境因未注入变量而误入 Mock 分支。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' export const isUserMockEnabled = useMockData diff --git a/clinical-web/src/components/layout/Menu.vue b/clinical-web/src/components/layout/Menu.vue index d772bf8..8e73091 100644 --- a/clinical-web/src/components/layout/Menu.vue +++ b/clinical-web/src/components/layout/Menu.vue @@ -32,7 +32,11 @@ const menuGroups: MenuGroup[] = [ { path: '/management/documents', label: '文档库管理' }, { path: '/management/reports', label: '报表分析' }, { path: '/management/users', label: '用户与权限', permission: 'system:user:query' }, - { path: '/management/document-permissions', label: '文档权限' }, + { + path: '/management/document-permissions', + label: '文档权限', + permission: 'sign:template:permission:query', + }, { path: '/management/settings', label: '系统设置' }, ], }, diff --git a/clinical-web/src/router/index.ts b/clinical-web/src/router/index.ts index a1539ad..889bfff 100644 --- a/clinical-web/src/router/index.ts +++ b/clinical-web/src/router/index.ts @@ -76,7 +76,11 @@ const router = createRouter({ path: 'document-permissions', name: 'management-document-permissions', component: DocumentPermissionsView, - meta: { title: '文档权限', group: '管理' }, + meta: { + title: '文档权限', + group: '管理', + permission: 'sign:template:permission:query', + }, }, { path: 'settings', diff --git a/clinical-web/src/stores/app.ts b/clinical-web/src/stores/app.ts index 5c8a939..2622867 100644 --- a/clinical-web/src/stores/app.ts +++ b/clinical-web/src/stores/app.ts @@ -5,7 +5,9 @@ import { getCampuses } from '@/api/management/organization' import type { CampusRecord } from '@/api/management/types' import type { WorkbenchCampus } from '@/api/workbench/types' -const useMockData = import.meta.env.VITE_USE_MOCK !== 'false' +// 仅显式开启 VITE_USE_MOCK=true 时使用演示院区,避免真实接口联调时把 mock UUID +// 作为 campusId 发给后端。 +const useMockData = import.meta.env.VITE_USE_MOCK === 'true' const mockCampusRecords: CampusRecord[] = [ { id: 'mock-campus-main', code: 'MAIN', name: '本部院区', status: 'ENABLED', sortNo: 1 }, diff --git a/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionDrawer.vue b/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionDrawer.vue new file mode 100644 index 0000000..4b6a502 --- /dev/null +++ b/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionDrawer.vue @@ -0,0 +1,475 @@ + + + + + diff --git a/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionTable.vue b/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionTable.vue index f13cd0e..0339512 100644 --- a/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionTable.vue +++ b/clinical-web/src/views/management/document-permissions/components/DepartmentPermissionTable.vue @@ -1,109 +1,71 @@ diff --git a/clinical-web/src/views/management/document-permissions/mock.ts b/clinical-web/src/views/management/document-permissions/mock.ts index d6db1a1..b2362ed 100644 --- a/clinical-web/src/views/management/document-permissions/mock.ts +++ b/clinical-web/src/views/management/document-permissions/mock.ts @@ -73,26 +73,36 @@ export const departmentPermissionRows: DepartmentPermissionRow[] = [ { id: 'radiology', department: '放射科', - permissions: { VIEW: true, USE: true, MAINTAIN: false }, + own: true, + common: true, + other: false, }, { id: 'cardiology', department: '心内科', - permissions: { VIEW: true, USE: true, MAINTAIN: false }, + own: true, + common: true, + other: false, }, { id: 'orthopedics', department: '骨科', - permissions: { VIEW: true, USE: true, MAINTAIN: false }, + own: true, + common: true, + other: false, }, { id: 'health-management', department: '健康管理中心', - permissions: { VIEW: true, USE: true, MAINTAIN: false }, + own: true, + common: true, + other: false, }, { id: 'general', department: '全院通用', - permissions: { VIEW: true, USE: true, MAINTAIN: false }, + own: false, + common: true, + other: false, }, ] diff --git a/clinical-web/src/views/management/document-permissions/types.ts b/clinical-web/src/views/management/document-permissions/types.ts index e9d4423..b135c9c 100644 --- a/clinical-web/src/views/management/document-permissions/types.ts +++ b/clinical-web/src/views/management/document-permissions/types.ts @@ -17,14 +17,19 @@ export interface RolePermissionRow { userCount: number permissions: Record permissionStates?: Record + version?: number } export interface DepartmentPermissionRow { id: string + code?: string department: string campus?: string - permissions: Record - permissionStates?: Record + campusId?: string + own: boolean + common: boolean + other: boolean + version?: number } export interface PermissionSubjectOption { diff --git a/clinical-web/src/views/management/users/components/RoleApiPermissionPanel.vue b/clinical-web/src/views/management/users/components/RoleApiPermissionPanel.vue index f4f0863..8c7a9dd 100644 --- a/clinical-web/src/views/management/users/components/RoleApiPermissionPanel.vue +++ b/clinical-web/src/views/management/users/components/RoleApiPermissionPanel.vue @@ -11,12 +11,15 @@ const props = defineProps<{ loading: boolean saving: boolean error: string + canEdit?: boolean + drawer?: boolean }>() const emit = defineEmits<{ selectRole: [roleId: string] togglePermission: [permissionId: string] save: [] + close: [] }>() const groupedPermissions = computed(() => { @@ -37,14 +40,26 @@ function isSelected(permissionId: string) {