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

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
+14 -9
View File
@@ -2,8 +2,8 @@
<div class="panel unified-shell">
<div class="header">
<span>{{ TEXT.common.labels.category }}</span>
<div v-if="canMaintain">
<PermissionAction action="faq.edit">
<div v-if="canCreateCategory">
<PermissionAction action="faq.category.create">
<el-button type="primary" size="small" @click="openForm()">{{ TEXT.modules.knowledgeMedicalConsult.newCategory }}</el-button>
</PermissionAction>
</div>
@@ -12,11 +12,11 @@
<el-menu-item index="">{{ TEXT.common.labels.all }}</el-menu-item>
<el-menu-item v-for="c in sortedCategories" :key="c.id" :index="c.id">
<span class="name" :title="c.name">{{ c.name }}</span>
<div v-if="canEdit(c) || canDelete" class="actions">
<PermissionAction v-if="canEdit(c)" action="faq.edit">
<div v-if="canEdit(c) || canDeleteCategory" class="actions">
<PermissionAction v-if="canEdit(c)" action="faq.category.update">
<el-button type="text" size="small" @click.stop="openForm(c)">{{ TEXT.common.actions.edit }}</el-button>
</PermissionAction>
<PermissionAction v-if="canDelete" action="faq.edit">
<PermissionAction v-if="canDeleteCategory" action="faq.category.delete">
<el-button type="text" size="small" class="danger" @click.stop="onDelete(c)">{{ TEXT.common.actions.delete }}</el-button>
</PermissionAction>
</div>
@@ -52,8 +52,9 @@ const editing = ref<FaqCategory | null>(null);
const { can } = usePermission();
const isAdmin = computed(() => !!auth.user?.is_admin);
const canMaintain = computed(() => can("faq.edit"));
const canDelete = computed(() => isAdmin.value);
const canCreateCategory = computed(() => can("faq.category.create"));
const canUpdateCategory = computed(() => can("faq.category.update"));
const canDeleteCategory = computed(() => can("faq.category.delete"));
const sortedCategories = computed(() =>
[...props.categories].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))
@@ -62,7 +63,7 @@ const sortedCategories = computed(() =>
const activeCategory = computed(() => props.modelValue || "");
const canEdit = (c: FaqCategory) => {
if (!canMaintain.value) return false;
if (!canUpdateCategory.value) return false;
if (isAdmin.value) return true;
return c.study_id === study.currentStudy?.id;
};
@@ -77,13 +78,17 @@ const openForm = (cat?: FaqCategory) => {
};
const onDelete = async (cat: FaqCategory) => {
if (!canDeleteCategory.value) {
ElMessage.warning("权限不足");
return;
}
const ok = await ElMessageBox.confirm(
TEXT.modules.knowledgeMedicalConsult.confirmDeleteCategory.replace("{name}", cat.name),
TEXT.common.labels.tips
).catch(() => null);
if (!ok) return;
try {
await deleteFaqCategory(cat.id);
await deleteFaqCategory(cat.id, study.currentStudy?.id);
ElMessage.success(TEXT.common.messages.deleteSuccess);
emit("refresh");
} catch (e: any) {