立项配置页初步优化

This commit is contained in:
Cheng Zhou
2026-02-24 16:53:22 +08:00
parent 0693f09b1d
commit 8f3f717e48
124 changed files with 12519 additions and 2954 deletions
+101 -25
View File
@@ -1,7 +1,31 @@
<template>
<div class="page">
<div class="page-header unified-action-bar">
<div>
<h1 class="page-title">{{ detail.subject_no || TEXT.modules.subjectManagement.screeningNo }}</h1>
<p class="page-subtitle">{{ TEXT.modules.subjectManagement.screeningNo }}</p>
</div>
<div class="actions">
<el-button
v-if="subjectEditing"
type="primary"
:loading="subjectSaving"
:disabled="isReadOnly"
@click="saveSubjectEdit"
>
{{ TEXT.common.actions.save }}
</el-button>
<el-button v-if="subjectEditing" :disabled="isReadOnly" @click="cancelSubjectEdit">
{{ TEXT.common.actions.cancel }}
</el-button>
<el-button v-else type="primary" :disabled="isReadOnly" @click="startSubjectEdit">
{{ TEXT.common.actions.edit }}
</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.back }}</el-button>
</div>
</div>
<el-card v-loading="loading">
<el-card class="unified-shell" v-loading="loading">
<el-descriptions :column="2" border>
<el-descriptions-item :label="TEXT.modules.subjectManagement.screeningNo">{{ detail.subject_no || TEXT.common.fallback }}</el-descriptions-item>
<el-descriptions-item :label="TEXT.common.fields.site">{{ siteMap[detail.site_id] || TEXT.common.fallback }}</el-descriptions-item>
@@ -52,7 +76,7 @@
</el-descriptions>
</el-card>
<el-card>
<el-card class="unified-shell">
<el-tabs v-model="activeTab">
<el-tab-pane :label="TEXT.modules.subjectDetail.tabs.history" name="history">
<div class="tab-actions">
@@ -182,17 +206,17 @@
<el-table-column prop="onset_date" :label="TEXT.common.fields.occurDate" width="140">
<template #default="scope">{{ displayDate(scope.row.onset_date) }}</template>
</el-table-column>
<el-table-column :label="TEXT.common.fields.aeType" width="120">
<template #default="scope">
<el-tag size="small" effect="light" :class="getAeTypeClass(scope.row)">
{{ getAeTypeLabel(scope.row) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="term" :label="TEXT.common.fields.title" min-width="140" />
<el-table-column prop="seriousness" :label="TEXT.common.fields.seriousness" width="120">
<template #default="scope">
<el-tag
:type="getAeSeriousnessType(normalizeSeriousness(scope.row.seriousness))"
size="small"
effect="light"
class="seriousness-tag"
>
{{ displayEnum(TEXT.enums.aeSeriousness, normalizeSeriousness(scope.row.seriousness)) }}
</el-tag>
{{ displayEnum(TEXT.enums.aeSeriousness, normalizeSeriousness(scope.row.seriousness)) }}
</template>
</el-table-column>
<el-table-column prop="causality" :label="TEXT.common.fields.causality" width="120">
@@ -227,6 +251,10 @@
</el-table>
<StateEmpty v-if="!loadingAes && aeItems.length === 0" :description="TEXT.modules.subjectDetail.emptyAe" />
</el-tab-pane>
<el-tab-pane :label="TEXT.modules.subjectDetail.tabs.pd" name="pd">
<StateEmpty :description="TEXT.modules.subjectDetail.emptyPd" />
</el-tab-pane>
</el-tabs>
</el-card>
@@ -289,6 +317,12 @@
<el-option v-for="option in aeSeriousnessOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
<el-form-item :label="TEXT.common.fields.isSae">
<el-switch v-model="aeForm.is_sae" @change="onSaeChanged" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.isSusar">
<el-switch v-model="aeForm.is_susar" @change="onSusarChanged" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.causality">
<el-select v-model="aeForm.causality" clearable>
<el-option v-for="option in aeCausalityOptions" :key="option.value" :label="option.label" :value="option.value" />
@@ -312,7 +346,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import { useStudyStore } from "../../store/study";
@@ -330,6 +364,10 @@ const router = useRouter();
const study = useStudyStore();
const subjectId = route.params.subjectId as string;
const studyId = study.currentStudy?.id || "";
const resolveTabFromQuery = () => {
const tab = typeof route.query.tab === "string" ? route.query.tab : "";
return ["history", "visits", "ae", "pd"].includes(tab) ? tab : "history";
};
const loading = ref(false);
const detail = reactive<any>({
@@ -356,7 +394,7 @@ const subjectForm = reactive({
const siteMap = ref<Record<string, string>>({});
const siteActiveMap = ref<Record<string, boolean>>({});
const isReadOnly = computed(() => !!detail.site_id && siteActiveMap.value[detail.site_id] === false);
const activeTab = ref("history");
const activeTab = ref(resolveTabFromQuery());
const historyItems = ref<any[]>([]);
const loadingHistory = ref(false);
@@ -410,6 +448,8 @@ const aeForm = reactive({
term: "",
onset_date: "",
seriousness: "I",
is_sae: false,
is_susar: false,
causality: "",
outcome: "",
description: "",
@@ -714,19 +754,25 @@ const getVisitStatusTagType = (status: string) => {
}
};
const getAeSeriousnessType = (seriousness: string) => {
switch (seriousness) {
case "I":
return "info";
case "II":
return "success";
case "III":
return "warning";
case "IV":
case "V":
return "danger";
default:
return "info";
const getAeTypeKey = (row: any) => {
if (row?.is_susar) return "susar";
if (row?.is_sae) return "sae";
return "ae";
};
const getAeTypeLabel = (row: any) => TEXT.modules.subjectDetail.aeType[getAeTypeKey(row)];
const getAeTypeClass = (row: any) => `ae-type-tag ae-type-${getAeTypeKey(row)}`;
const onSaeChanged = (value: boolean) => {
if (!value) {
aeForm.is_susar = false;
}
};
const onSusarChanged = (value: boolean) => {
if (value) {
aeForm.is_sae = true;
}
};
@@ -803,6 +849,8 @@ const openAeDialog = (row?: any) => {
term: row?.term || "",
onset_date: row?.onset_date || "",
seriousness: normalizeSeriousness(row?.seriousness),
is_sae: !!row?.is_sae || !!row?.is_susar,
is_susar: !!row?.is_susar,
causality: row?.causality || "",
outcome: row?.outcome || "",
description: row?.description || "",
@@ -823,6 +871,8 @@ const saveAe = async () => {
term: aeForm.term,
onset_date: aeForm.onset_date || null,
seriousness: aeForm.seriousness,
is_sae: !!aeForm.is_sae || !!aeForm.is_susar,
is_susar: !!aeForm.is_susar,
causality: aeForm.causality || null,
outcome: aeForm.outcome || null,
description: aeForm.description || null,
@@ -857,6 +907,13 @@ const removeAe = async (row: any) => {
const goBack = () => router.push("/subjects");
watch(
() => route.query.tab,
() => {
activeTab.value = resolveTabFromQuery();
}
);
onMounted(async () => {
await loadSites();
await loadSubject();
@@ -901,4 +958,23 @@ onMounted(async () => {
justify-content: flex-end;
margin-bottom: 12px;
}
.ae-type-tag {
border: none;
}
.ae-type-ae {
color: #ffffff;
background-color: #3b82f6;
}
.ae-type-sae {
color: #ffffff;
background-color: #ef4444;
}
.ae-type-susar {
color: #ffffff;
background-color: #8b5cf6;
}
</style>