补齐业务路由与页面权限控制

This commit is contained in:
Cheng Zhou
2026-05-28 10:56:28 +08:00
parent fa961f1391
commit 8611bd66f8
39 changed files with 458 additions and 250 deletions
@@ -105,7 +105,7 @@
</div>
<div class="template-toolbar">
<div class="toolbar-left">
<el-button class="toolbar-button toolbar-button-primary" type="primary" @click="openCreateDialog">
<el-button v-if="canCreateIssue" class="toolbar-button toolbar-button-primary" type="primary" @click="openCreateDialog">
<el-icon><Plus /></el-icon>
<span>新增</span>
</el-button>
@@ -117,7 +117,14 @@
<el-icon><CircleCheck /></el-icon>
<span>一键完成</span>
</el-button>
<el-button class="toolbar-button toolbar-button-success" type="success" plain :loading="exporting" @click="handleExportExcel">
<el-button
v-if="canListIssues"
class="toolbar-button toolbar-button-success"
type="success"
plain
:loading="exporting"
@click="handleExportExcel"
>
<el-icon><Download /></el-icon>
<span>导出</span>
</el-button>
@@ -126,6 +133,7 @@
<span>问题导入模板</span>
</el-button>
<el-upload
v-if="canCreateIssue"
ref="importUploadRef"
:auto-upload="false"
:show-file-list="false"
@@ -166,12 +174,12 @@
<el-table-column prop="recommendation" label="建议" min-width="190" show-overflow-tooltip />
<el-table-column prop="center_query" label="中心质疑" min-width="180" show-overflow-tooltip />
<el-table-column prop="center_latest_reply" label="中心最新回复" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" width="140" align="center">
<el-table-column v-if="canReadIssue || canUpdateIssue || canDeleteIssue" label="操作" width="140" align="center">
<template #default="{ row }">
<div class="action-cell">
<el-button class="action-btn" link type="primary" size="small" @click="openViewDialog(row)">查看</el-button>
<el-button class="action-btn" link type="primary" size="small" @click="openEditDialog(row)">编辑</el-button>
<el-button class="action-btn" link type="danger" size="small" @click="removeIssue(row)">删除</el-button>
<el-button v-if="canReadIssue" class="action-btn" link type="primary" size="small" @click="openViewDialog(row)">查看</el-button>
<el-button v-if="canUpdateIssue" class="action-btn" link type="primary" size="small" @click="openEditDialog(row)">编辑</el-button>
<el-button v-if="canDeleteIssue" class="action-btn" link type="danger" size="small" @click="removeIssue(row)">删除</el-button>
</div>
</template>
</el-table-column>
@@ -403,7 +411,7 @@
<template #footer>
<div class="editor-footer">
<el-button @click="formDialogVisible = false">{{ TEXT.common.actions.cancel }}</el-button>
<el-button type="primary" :loading="saving" @click="submitForm">{{ TEXT.common.actions.save }}</el-button>
<el-button type="primary" :loading="saving" :disabled="!canSaveIssue" @click="submitForm">{{ TEXT.common.actions.save }}</el-button>
</div>
</template>
</el-drawer>
@@ -460,9 +468,12 @@ import {
import { fetchSites } from "../../api/sites";
import StateEmpty from "../../components/StateEmpty.vue";
import { TEXT } from "../../locales";
import { useAuthStore } from "../../store/auth";
import { useStudyStore } from "../../store/study";
import type { Site } from "../../types/api";
import { isApiPermissionAllowed } from "../../utils/apiPermissionValue";
import { displayDateTime } from "../../utils/display";
import { getProjectRole, isSystemAdmin } from "../../utils/roles";
interface MonitoringIssueRow {
id: string;
@@ -499,6 +510,7 @@ interface MonitoringIssueRow {
}
type DateRange = [string, string] | [];
const auth = useAuthStore();
const study = useStudyStore();
const loading = ref(false);
const saving = ref(false);
@@ -515,6 +527,19 @@ const viewIssue = ref<MonitoringIssueRow | null>(null);
const sitesLoading = ref(false);
const siteOptions = ref<Array<Pick<Site, "id" | "name" | "is_active">>>([]);
const siteMap = ref<Record<string, string>>({});
const projectRole = computed(() => getProjectRole(study.currentStudy, study.currentStudyRole));
const canUseApiPermission = (operationKey: string) => {
if (isSystemAdmin(auth.user)) return true;
const role = projectRole.value;
if (!role) return false;
return isApiPermissionAllowed(study.currentPermissions?.[role]?.[operationKey]);
};
const canListIssues = computed(() => canUseApiPermission("monitoring_issues:list"));
const canReadIssue = computed(() => canUseApiPermission("monitoring_issues:read"));
const canCreateIssue = computed(() => canUseApiPermission("monitoring_issues:create"));
const canUpdateIssue = computed(() => canUseApiPermission("monitoring_issues:update"));
const canDeleteIssue = computed(() => canUseApiPermission("monitoring_issues:delete"));
const canSaveIssue = computed(() => (formMode.value === "edit" ? canUpdateIssue.value : canCreateIssue.value));
const currentProjectCenter = computed(() => study.currentStudy?.name || study.currentStudy?.project_full_name || "");
@@ -802,6 +827,7 @@ const resetFiltersForStudy = () => {
};
const openCreateDialog = () => {
if (!canCreateIssue.value) return;
resetCreateForm();
formMode.value = "create";
editingIssueId.value = "";
@@ -810,7 +836,7 @@ const openCreateDialog = () => {
const openEditDialog = async (row: MonitoringIssueRow) => {
const studyId = study.currentStudy?.id;
if (!studyId || !row?.id) return;
if (!studyId || !row?.id || !canUpdateIssue.value) return;
try {
const { data } = await getMonitoringVisitIssue(studyId, row.id);
const issue = (data || row) as MonitoringIssueRow;
@@ -825,7 +851,7 @@ const openEditDialog = async (row: MonitoringIssueRow) => {
const openViewDialog = async (row: MonitoringIssueRow) => {
const studyId = study.currentStudy?.id;
if (!studyId || !row?.id) return;
if (!studyId || !row?.id || !canReadIssue.value) return;
try {
const { data } = await getMonitoringVisitIssue(studyId, row.id);
viewIssue.value = (data || row) as MonitoringIssueRow;
@@ -837,7 +863,7 @@ const openViewDialog = async (row: MonitoringIssueRow) => {
const submitForm = async () => {
const studyId = study.currentStudy?.id;
if (!studyId) return;
if (!studyId || !canSaveIssue.value) return;
const ok = await createFormRef.value?.validate().catch(() => false);
if (!ok) return;
@@ -891,7 +917,7 @@ const submitForm = async () => {
const removeIssue = async (row: MonitoringIssueRow) => {
const studyId = study.currentStudy?.id;
if (!studyId || !row?.id) return;
if (!studyId || !row?.id || !canDeleteIssue.value) return;
const confirmed = await ElMessageBox.confirm(`确认删除问题「${row.issue_no}」?`, TEXT.common.labels.tips).catch(() => null);
if (!confirmed) return;
@@ -914,7 +940,7 @@ const getFilename = (header?: string | null) => {
const handleExportExcel = async () => {
const studyId = study.currentStudy?.id;
if (!studyId) return;
if (!studyId || !canListIssues.value) return;
if (filteredItems.value.length === 0) {
ElMessage.warning("暂无可导出数据");
return;
@@ -964,7 +990,7 @@ const handleExportExcel = async () => {
const onImportChange = async (uploadFile: UploadFile) => {
const studyId = study.currentStudy?.id;
if (!studyId) return;
if (!studyId || !canCreateIssue.value) return;
const file = uploadFile.raw;
if (!file) return;