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

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
+21 -5
View File
@@ -24,7 +24,7 @@
<el-button @click="resetFilters">{{ TEXT.common.actions.reset }}</el-button>
</div>
<div class="filter-spacer"></div>
<el-button v-if="canWritePrecautions" type="primary" @click="goNew">
<el-button v-if="canCreatePrecaution" type="primary" @click="goNew">
{{ TEXT.modules.knowledgeNotes.newTitle }}
</el-button>
</el-form>
@@ -45,10 +45,10 @@
<el-table-column prop="updated_at" :label="TEXT.common.labels.updatedAt" show-overflow-tooltip>
<template #default="scope">{{ displayDateTime(scope.row.updated_at) }}</template>
</el-table-column>
<el-table-column :label="TEXT.common.labels.actions" width="120">
<el-table-column v-if="canDeletePrecaution" :label="TEXT.common.labels.actions" width="120">
<template #default="scope">
<el-button
v-if="canWritePrecautions"
v-if="canDeletePrecaution"
link
type="danger"
size="small"
@@ -65,6 +65,10 @@
</el-table>
</div>
</div>
<PrecautionEditorDrawer
v-model="precautionDrawerVisible"
@success="handlePrecautionEditorSuccess"
/>
</div>
</template>
@@ -78,11 +82,13 @@ import { fetchSites } from "../../api/sites";
import { displayDateTime } from "../../utils/display";
import { usePermission } from "../../utils/permission";
import { TEXT } from "../../locales";
import PrecautionEditorDrawer from "../knowledge/PrecautionEditorDrawer.vue";
const router = useRouter();
const study = useStudyStore();
const { can } = usePermission();
const loading = ref(false);
const precautionDrawerVisible = ref(false);
const items = ref<any[]>([]);
const sites = ref<any[]>([]);
const filters = reactive({
@@ -112,7 +118,8 @@ const filteredItems = computed(() => {
const sortedItems = computed(() =>
[...filteredItems.value].sort((a, b) => Number(isInactiveSite(a?.site_name)) - Number(isInactiveSite(b?.site_name)))
);
const canWritePrecautions = computed(() => can("precautions.write"));
const canCreatePrecaution = computed(() => can("precautions.create"));
const canDeletePrecaution = computed(() => can("precautions.delete"));
const loadSites = async () => {
const studyId = study.currentStudy?.id;
@@ -144,8 +151,13 @@ const resetFilters = () => {
filters.keyword = "";
};
const goNew = () => router.push("/knowledge/precautions/new");
const goNew = () => {
precautionDrawerVisible.value = true;
};
const goDetail = (id: string) => router.push(`/knowledge/precautions/${id}`);
const handlePrecautionEditorSuccess = () => {
load();
};
const onRowClick = (row: any) => {
if (!row?.id) return;
goDetail(row.id);
@@ -154,6 +166,10 @@ const onRowClick = (row: any) => {
const remove = async (row: any) => {
const studyId = study.currentStudy?.id;
if (!studyId) return;
if (!canDeletePrecaution.value) {
ElMessage.warning("权限不足");
return;
}
if (isInactiveSite(row?.site_name)) {
ElMessage.warning("中心已停用");
return;