补齐业务路由与页面权限控制
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user