统一业务页面权限与抽屉编辑

1、将 FAQ 分类和问题维护改为抽屉交互,并按分类、问题、回复的细粒度权限控制入口。

2、将注意事项、可行性和伦理记录接入详情页抽屉编辑,创建时支持先保存主记录再上传附件。

3、将参与者基础信息编辑改为抽屉,详情页按可读权限显示病史、访视、AE 和 PD 标签页。

4、补充业务页面权限一致性测试,覆盖停用中心、无权限入口和面包屑上下文。
This commit is contained in:
Cheng Zhou
2026-06-04 11:10:20 +08:00
parent 6a2e43f829
commit 6e8494abd5
39 changed files with 2334 additions and 337 deletions
@@ -207,7 +207,8 @@
v-model="formDialogVisible"
direction="rtl"
size="580px"
:close-on-click-modal="false"
:close-on-click-modal="true"
:before-close="formDirtyGuard.beforeClose"
:show-close="false"
class="issue-editor-drawer"
>
@@ -411,7 +412,7 @@
<template #footer>
<div class="editor-footer">
<el-button @click="formDialogVisible = false">{{ TEXT.common.actions.cancel }}</el-button>
<el-button type="primary" :loading="saving" :disabled="!canSaveIssue" @click="submitForm">{{ TEXT.common.actions.save }}</el-button>
<el-button v-if="canSaveIssue" type="primary" :loading="saving" @click="submitForm">{{ TEXT.common.actions.save }}</el-button>
</div>
</template>
</el-drawer>
@@ -474,6 +475,7 @@ import type { Site } from "../../types/api";
import { isApiPermissionAllowed } from "../../utils/apiPermissionValue";
import { displayDateTime } from "../../utils/display";
import { getProjectRole, isSystemAdmin } from "../../utils/roles";
import { useDrawerDirtyGuard } from "../../utils/drawerDirtyGuard";
interface MonitoringIssueRow {
id: string;
@@ -534,7 +536,7 @@ const canUseApiPermission = (operationKey: string) => {
if (!role) return false;
return isApiPermissionAllowed(study.currentPermissions?.[role]?.[operationKey]);
};
const canListIssues = computed(() => canUseApiPermission("monitoring_issues:list"));
const canListIssues = computed(() => canUseApiPermission("monitoring_issues:read"));
const canReadIssue = computed(() => canUseApiPermission("monitoring_issues:read"));
const canCreateIssue = computed(() => canUseApiPermission("monitoring_issues:create"));
const canUpdateIssue = computed(() => canUseApiPermission("monitoring_issues:update"));
@@ -605,6 +607,7 @@ const createForm = reactive({
closed_date: "",
responsible_name: "",
});
const formDirtyGuard = useDrawerDirtyGuard(() => createForm);
const createRules: FormRules = {
source: [{ required: true, message: "请选择问题来源", trigger: "change" }],
@@ -831,6 +834,7 @@ const openCreateDialog = () => {
resetCreateForm();
formMode.value = "create";
editingIssueId.value = "";
formDirtyGuard.syncBaseline();
formDialogVisible.value = true;
};
@@ -843,6 +847,7 @@ const openEditDialog = async (row: MonitoringIssueRow) => {
formMode.value = "edit";
editingIssueId.value = row.id;
fillFormFromIssue(issue);
formDirtyGuard.syncBaseline();
formDialogVisible.value = true;
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || TEXT.common.messages.loadFailed);
@@ -863,7 +868,11 @@ const openViewDialog = async (row: MonitoringIssueRow) => {
const submitForm = async () => {
const studyId = study.currentStudy?.id;
if (!studyId || !canSaveIssue.value) return;
if (!studyId) return;
if (!canSaveIssue.value) {
ElMessage.warning("权限不足");
return;
}
const ok = await createFormRef.value?.validate().catch(() => false);
if (!ok) return;