全局中文化

This commit is contained in:
Cheng Zhou
2026-01-12 11:14:04 +08:00
parent 9021c7fe2b
commit 1338fa7e2b
103 changed files with 2443 additions and 1533 deletions
+99 -90
View File
@@ -2,187 +2,195 @@
<div class="page">
<div class="page-header">
<div>
<h1 class="page-title">受试者详情</h1>
<p class="page-subtitle">查看病史访视与 AE 信息</p>
<h1 class="page-title">{{ TEXT.modules.subjectDetail.title }}</h1>
<p class="page-subtitle">{{ TEXT.modules.subjectDetail.subtitle }}</p>
</div>
<div class="actions">
<el-button type="primary" @click="goEdit">编辑</el-button>
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="goEdit">{{ TEXT.common.actions.edit }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.back }}</el-button>
</div>
</div>
<el-card v-loading="loading">
<el-descriptions :column="2" border>
<el-descriptions-item label="受试者编号">{{ detail.subject_no || "—" }}</el-descriptions-item>
<el-descriptions-item label="分中心">{{ siteMap[detail.site_id] || "—" }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detail.status || "—" }}</el-descriptions-item>
<el-descriptions-item label="筛选日期">{{ displayDate(detail.screening_date) }}</el-descriptions-item>
<el-descriptions-item label="入组日期">{{ displayDate(detail.enrollment_date) }}</el-descriptions-item>
<el-descriptions-item label="完成日期">{{ displayDate(detail.completion_date) }}</el-descriptions-item>
<el-descriptions-item label="退出原因" :span="2">{{ detail.drop_reason || "—" }}</el-descriptions-item>
<el-descriptions-item :label="TEXT.common.fields.subjectNo">{{ 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>
<el-descriptions-item :label="TEXT.common.fields.status">{{ displayEnum(TEXT.enums.subjectStatus, detail.status) }}</el-descriptions-item>
<el-descriptions-item :label="TEXT.common.fields.screeningDate">{{ displayDate(detail.screening_date) }}</el-descriptions-item>
<el-descriptions-item :label="TEXT.common.fields.enrollmentDate">{{ displayDate(detail.enrollment_date) }}</el-descriptions-item>
<el-descriptions-item :label="TEXT.common.fields.completionDate">{{ displayDate(detail.completion_date) }}</el-descriptions-item>
<el-descriptions-item :label="TEXT.common.fields.dropReason" :span="2">{{ detail.drop_reason || TEXT.common.fallback }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card>
<el-tabs v-model="activeTab">
<el-tab-pane label="病史" name="history">
<el-tab-pane :label="TEXT.modules.subjectDetail.tabs.history" name="history">
<div class="tab-actions">
<el-button type="primary" @click="openHistoryDialog()">新增病史</el-button>
<el-button type="primary" @click="openHistoryDialog()">{{ TEXT.common.actions.newHistory }}</el-button>
</div>
<el-table :data="historyItems" v-loading="loadingHistory" style="width: 100%">
<el-table-column prop="record_date" label="日期" width="140">
<el-table-column prop="record_date" :label="TEXT.common.fields.recordDate" width="140">
<template #default="scope">{{ displayDate(scope.row.record_date) }}</template>
</el-table-column>
<el-table-column prop="content" label="内容" min-width="240" />
<el-table-column label="操作" width="200">
<el-table-column prop="content" :label="TEXT.common.fields.content" min-width="240" />
<el-table-column :label="TEXT.common.labels.actions" width="200">
<template #default="scope">
<el-button link type="primary" size="small" @click="openHistoryDialog(scope.row)">编辑</el-button>
<el-button link type="danger" size="small" @click="removeHistory(scope.row)">删除</el-button>
<el-button link type="primary" size="small" @click="openHistoryDialog(scope.row)">{{ TEXT.common.actions.edit }}</el-button>
<el-button link type="danger" size="small" @click="removeHistory(scope.row)">{{ TEXT.common.actions.delete }}</el-button>
</template>
</el-table-column>
</el-table>
<StateEmpty v-if="!loadingHistory && historyItems.length === 0" description="暂无病史记录" />
<StateEmpty v-if="!loadingHistory && historyItems.length === 0" :description="TEXT.modules.subjectDetail.emptyHistory" />
</el-tab-pane>
<el-tab-pane label="访视" name="visits">
<el-tab-pane :label="TEXT.modules.subjectDetail.tabs.visits" name="visits">
<div class="tab-actions">
<el-button type="primary" @click="openVisitDialog()">新增访视</el-button>
<el-button type="primary" @click="openVisitDialog()">{{ TEXT.common.actions.newVisit }}</el-button>
</div>
<el-table :data="visitItems" v-loading="loadingVisits" style="width: 100%">
<el-table-column prop="visit_code" label="访视编号" width="120" />
<el-table-column prop="visit_name" label="访视名称" min-width="140" />
<el-table-column prop="planned_date" label="计划日期" width="140">
<el-table-column prop="visit_code" :label="TEXT.common.fields.visitCode" width="120" />
<el-table-column prop="visit_name" :label="TEXT.common.fields.visitName" min-width="140">
<template #default="scope">
{{ displayText(scope.row.visit_name, TEXT.enums.visitName) }}
</template>
</el-table-column>
<el-table-column prop="planned_date" :label="TEXT.common.fields.plannedDate" width="140">
<template #default="scope">{{ displayDate(scope.row.planned_date) }}</template>
</el-table-column>
<el-table-column prop="actual_date" label="实际日期" width="140">
<el-table-column prop="actual_date" :label="TEXT.common.fields.actualDate" width="140">
<template #default="scope">{{ displayDate(scope.row.actual_date) }}</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="120" />
<el-table-column label="操作" width="200">
<el-table-column prop="status" :label="TEXT.common.fields.status" width="120">
<template #default="scope">{{ displayEnum(TEXT.enums.visitStatus, scope.row.status) }}</template>
</el-table-column>
<el-table-column :label="TEXT.common.labels.actions" width="200">
<template #default="scope">
<el-button link type="primary" size="small" @click="openVisitDialog(scope.row)">编辑</el-button>
<el-button link type="danger" size="small" @click="removeVisit(scope.row)">删除</el-button>
<el-button link type="primary" size="small" @click="openVisitDialog(scope.row)">{{ TEXT.common.actions.edit }}</el-button>
<el-button link type="danger" size="small" @click="removeVisit(scope.row)">{{ TEXT.common.actions.delete }}</el-button>
</template>
</el-table-column>
</el-table>
<StateEmpty v-if="!loadingVisits && visitItems.length === 0" description="暂无访视记录" />
<StateEmpty v-if="!loadingVisits && visitItems.length === 0" :description="TEXT.modules.subjectDetail.emptyVisits" />
</el-tab-pane>
<el-tab-pane label="AE 信息" name="ae">
<el-tab-pane :label="TEXT.modules.subjectDetail.tabs.ae" name="ae">
<div class="tab-actions">
<el-button type="primary" @click="openAeDialog()">新增 AE</el-button>
<el-button type="primary" @click="openAeDialog()">{{ TEXT.common.actions.newAe }}</el-button>
</div>
<el-table :data="aeItems" v-loading="loadingAes" style="width: 100%">
<el-table-column prop="onset_date" label="发生日期" width="140">
<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 prop="term" label="事件" min-width="140" />
<el-table-column prop="seriousness" label="严重程度" width="120" />
<el-table-column prop="causality" label="关联性" width="120" />
<el-table-column prop="outcome" label="结局" width="120" />
<el-table-column prop="description" label="备注" min-width="200" />
<el-table-column label="操作" width="200">
<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">{{ displayEnum(TEXT.enums.aeSeriousness, scope.row.seriousness) }}</template>
</el-table-column>
<el-table-column prop="causality" :label="TEXT.common.fields.causality" width="120" />
<el-table-column prop="outcome" :label="TEXT.common.fields.outcome" width="120" />
<el-table-column prop="description" :label="TEXT.common.fields.remark" min-width="200" />
<el-table-column :label="TEXT.common.labels.actions" width="200">
<template #default="scope">
<el-button link type="primary" size="small" @click="openAeDialog(scope.row)">编辑</el-button>
<el-button link type="danger" size="small" @click="removeAe(scope.row)">删除</el-button>
<el-button link type="primary" size="small" @click="openAeDialog(scope.row)">{{ TEXT.common.actions.edit }}</el-button>
<el-button link type="danger" size="small" @click="removeAe(scope.row)">{{ TEXT.common.actions.delete }}</el-button>
</template>
</el-table-column>
</el-table>
<StateEmpty v-if="!loadingAes && aeItems.length === 0" description="暂无 AE 记录" />
<StateEmpty v-if="!loadingAes && aeItems.length === 0" :description="TEXT.modules.subjectDetail.emptyAe" />
</el-tab-pane>
</el-tabs>
</el-card>
<el-dialog v-model="historyDialogVisible" title="病史记录" width="520px">
<el-dialog v-model="historyDialogVisible" :title="TEXT.modules.subjectDetail.dialogHistory" width="520px">
<el-form :model="historyForm" label-width="90px">
<el-form-item label="日期">
<el-form-item :label="TEXT.common.fields.recordDate">
<el-date-picker v-model="historyForm.record_date" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="内容" required>
<el-form-item :label="TEXT.common.fields.content" required>
<el-input v-model="historyForm.content" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="historyDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveHistory">保存</el-button>
<el-button @click="historyDialogVisible = false">{{ TEXT.common.actions.cancel }}</el-button>
<el-button type="primary" @click="saveHistory">{{ TEXT.common.actions.save }}</el-button>
</template>
</el-dialog>
<el-dialog v-model="visitDialogVisible" title="访视记录" width="560px">
<el-dialog v-model="visitDialogVisible" :title="TEXT.modules.subjectDetail.dialogVisit" width="560px">
<el-form :model="visitForm" label-width="100px">
<template v-if="!visitEditingId">
<el-form-item label="访视编号" required>
<el-form-item :label="TEXT.common.fields.visitCode" required>
<el-input v-model="visitForm.visit_code" />
</el-form-item>
<el-form-item label="访视名称" required>
<el-form-item :label="TEXT.common.fields.visitName" required>
<el-input v-model="visitForm.visit_name" />
</el-form-item>
<el-form-item label="计划日期">
<el-form-item :label="TEXT.common.fields.plannedDate">
<el-date-picker v-model="visitForm.planned_date" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="窗口开始">
<el-form-item :label="TEXT.common.fields.windowStart">
<el-date-picker v-model="visitForm.window_start" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="窗口结束">
<el-form-item :label="TEXT.common.fields.windowEnd">
<el-date-picker v-model="visitForm.window_end" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
</template>
<template v-else>
<el-form-item label="实际日期">
<el-form-item :label="TEXT.common.fields.actualDate">
<el-date-picker v-model="visitForm.actual_date" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="状态">
<el-form-item :label="TEXT.common.fields.status">
<el-select v-model="visitForm.status">
<el-option label="计划中" value="PLANNED" />
<el-option label="已完成" value="DONE" />
<el-option label="未完成" value="MISSED" />
<el-option :label="TEXT.enums.visitStatus.PLANNED" value="PLANNED" />
<el-option :label="TEXT.enums.visitStatus.DONE" value="DONE" />
<el-option :label="TEXT.enums.visitStatus.MISSED" value="MISSED" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-form-item :label="TEXT.common.fields.remark">
<el-input v-model="visitForm.notes" type="textarea" :rows="3" />
</el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="visitDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveVisit">保存</el-button>
<el-button @click="visitDialogVisible = false">{{ TEXT.common.actions.cancel }}</el-button>
<el-button type="primary" @click="saveVisit">{{ TEXT.common.actions.save }}</el-button>
</template>
</el-dialog>
<el-dialog v-model="aeDialogVisible" title="AE 记录" width="560px">
<el-dialog v-model="aeDialogVisible" :title="TEXT.modules.subjectDetail.dialogAe" width="560px">
<el-form :model="aeForm" label-width="100px">
<el-form-item label="事件" required>
<el-form-item :label="TEXT.common.fields.title" required>
<el-input v-model="aeForm.term" />
</el-form-item>
<el-form-item label="发生日期">
<el-form-item :label="TEXT.common.fields.occurDate">
<el-date-picker v-model="aeForm.onset_date" type="date" value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="严重程度" required>
<el-form-item :label="TEXT.common.fields.seriousness" required>
<el-select v-model="aeForm.seriousness">
<el-option label="严重" value="SERIOUS" />
<el-option label="一般" value="NON_SERIOUS" />
<el-option :label="TEXT.enums.aeSeriousness.SERIOUS" value="SERIOUS" />
<el-option :label="TEXT.enums.aeSeriousness.NON_SERIOUS" value="NON_SERIOUS" />
</el-select>
</el-form-item>
<el-form-item label="严重等级" required>
<el-form-item :label="TEXT.common.fields.severity" required>
<el-select v-model="aeForm.severity">
<el-option label="轻度" value="MILD" />
<el-option label="中度" value="MODERATE" />
<el-option label="重度" value="SEVERE" />
<el-option :label="TEXT.enums.aeSeverity.MILD" value="MILD" />
<el-option :label="TEXT.enums.aeSeverity.MODERATE" value="MODERATE" />
<el-option :label="TEXT.enums.aeSeverity.SEVERE" value="SEVERE" />
</el-select>
</el-form-item>
<el-form-item label="关联性">
<el-form-item :label="TEXT.common.fields.causality">
<el-input v-model="aeForm.causality" />
</el-form-item>
<el-form-item label="结局">
<el-form-item :label="TEXT.common.fields.outcome">
<el-input v-model="aeForm.outcome" />
</el-form-item>
<el-form-item label="备注">
<el-form-item :label="TEXT.common.fields.remark">
<el-input v-model="aeForm.description" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="aeDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveAe">保存</el-button>
<el-button @click="aeDialogVisible = false">{{ TEXT.common.actions.cancel }}</el-button>
<el-button type="primary" @click="saveAe">{{ TEXT.common.actions.save }}</el-button>
</template>
</el-dialog>
</div>
@@ -198,8 +206,9 @@ import { fetchSites } from "../../api/sites";
import { listSubjectHistories, createSubjectHistory, updateSubjectHistory, deleteSubjectHistory } from "../../api/subjectHistories";
import { fetchVisits, createVisit, updateVisit, deleteVisit } from "../../api/visits";
import { fetchAes, createAe, updateAe, deleteAe } from "../../api/aes";
import { displayDate } from "../../utils/display";
import { displayDate, displayEnum, displayText } from "../../utils/display";
import StateEmpty from "../../components/StateEmpty.vue";
import { TEXT } from "../../locales";
const route = useRoute();
const router = useRouter();
@@ -280,7 +289,7 @@ const loadSubject = async () => {
const { data } = await getSubject(studyId, subjectId);
Object.assign(detail, data);
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "加载失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
} finally {
loading.value = false;
}
@@ -293,7 +302,7 @@ const loadHistories = async () => {
const { data } = await listSubjectHistories(studyId, subjectId);
historyItems.value = Array.isArray(data) ? data : data.items || [];
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "病史加载失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
} finally {
loadingHistory.value = false;
}
@@ -322,19 +331,19 @@ const saveHistory = async () => {
historyDialogVisible.value = false;
loadHistories();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "保存失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.saveFailed);
}
};
const removeHistory = async (row: any) => {
if (!studyId || !subjectId) return;
const ok = await ElMessageBox.confirm("确认删除该病史记录?", "提示").catch(() => null);
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
if (!ok) return;
try {
await deleteSubjectHistory(studyId, subjectId, row.id);
loadHistories();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "删除失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.deleteFailed);
}
};
@@ -345,7 +354,7 @@ const loadVisits = async () => {
const { data } = await fetchVisits(studyId, subjectId);
visitItems.value = Array.isArray(data) ? data : data.items || [];
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "访视加载失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
} finally {
loadingVisits.value = false;
}
@@ -394,19 +403,19 @@ const saveVisit = async () => {
visitDialogVisible.value = false;
loadVisits();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "保存失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.saveFailed);
}
};
const removeVisit = async (row: any) => {
if (!studyId || !subjectId) return;
const ok = await ElMessageBox.confirm("确认删除该访视记录?", "提示").catch(() => null);
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
if (!ok) return;
try {
await deleteVisit(studyId, subjectId, row.id);
loadVisits();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "删除失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.deleteFailed);
}
};
@@ -417,7 +426,7 @@ const loadAes = async () => {
const { data } = await fetchAes(studyId, { subject_id: subjectId });
aeItems.value = Array.isArray(data) ? data : data.items || [];
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "AE 加载失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
} finally {
loadingAes.value = false;
}
@@ -459,19 +468,19 @@ const saveAe = async () => {
aeDialogVisible.value = false;
loadAes();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "保存失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.saveFailed);
}
};
const removeAe = async (row: any) => {
if (!studyId) return;
const ok = await ElMessageBox.confirm("确认删除该 AE 记录?", "提示").catch(() => null);
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
if (!ok) return;
try {
await deleteAe(studyId, row.id);
loadAes();
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "删除失败");
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.deleteFailed);
}
};