{{ TEXT.menu.fileVersionManagement }}
diff --git a/frontend/src/locales/zh-CN.ts b/frontend/src/locales/zh-CN.ts
index 908ac142..d44cd693 100644
--- a/frontend/src/locales/zh-CN.ts
+++ b/frontend/src/locales/zh-CN.ts
@@ -151,8 +151,11 @@ export const TEXT = {
trainedDate: "培训日期",
authorizedDate: "授权日期",
direction: "类型",
- trackingNo: "运单号",
- shipDate: "日期",
+ trackingNo: "单号",
+ shipDate: "寄出/回收日期",
+ receiveDate: "接收日期",
+ quantity: "数量",
+ batchNo: "批号",
drugDesc: "物品描述",
carrier: "快递公司",
fromParty: "发出方",
@@ -245,7 +248,7 @@ export const TEXT = {
feeContracts: "合同管理",
feeSpecials: "特殊费用管理",
drug: "药品管理",
- drugShipments: "运输/流向",
+ drugShipments: "药品流向管理",
fileVersionManagement: "文件版本管理",
startupFeasibilityEthics: "立项与伦理",
startupMeetingAuth: "启动与授权",
@@ -419,7 +422,7 @@ export const TEXT = {
uploadHint: "保存后可上传凭证/发票/其他附件",
},
drugShipments: {
- title: "药品运输/流向",
+ title: "药品流向管理",
subtitle: "登记寄送与回收信息",
recordLabel: "运输记录",
empty: "暂无运输记录",
@@ -429,7 +432,7 @@ export const TEXT = {
formSubtitle: "记录寄送/回收流向",
uploadHint: "保存后可上传交接材料",
detailTitle: "运输记录详情",
- detailSubtitle: "查看运输/流向信息与附件",
+ detailSubtitle: "查看药品流向信息与附件",
},
fileVersionManagement: {
title: "文件版本管理",
diff --git a/frontend/src/views/drug/ShipmentDetail.vue b/frontend/src/views/drug/ShipmentDetail.vue
index 43bfd07a..dd06be3f 100644
--- a/frontend/src/views/drug/ShipmentDetail.vue
+++ b/frontend/src/views/drug/ShipmentDetail.vue
@@ -11,20 +11,38 @@
-
-
- {{ displayEnum(TEXT.enums.shipmentDirection, detail.direction) }}
- {{ displayEnum(TEXT.enums.shipmentStatus, detail.status) }}
- {{ detail.site_name || TEXT.common.fallback }}
- {{ displayDate(detail.ship_date) }}
- {{ detail.carrier || TEXT.common.fallback }}
- {{ detail.tracking_no || TEXT.common.fallback }}
- {{ detail.from_party || TEXT.common.fallback }}
- {{ detail.to_party || TEXT.common.fallback }}
- {{ detail.drug_desc || TEXT.common.fallback }}
- {{ detail.remark || TEXT.common.fallback }}
-
-
+
+
+
+
+
+
+ {{ detail.site_name || TEXT.common.fallback }}
+
+
+ {{ displayEnum(TEXT.enums.shipmentDirection, detail.direction) }}
+
+
+
+
+
+
+
+ {{ detail.tracking_no || TEXT.common.fallback }}
+ {{ detail.carrier || TEXT.common.fallback }}
+ {{ displayDate(detail.ship_date) }}
+ {{ displayDate(detail.receive_date) }}
+ {{ detail.quantity ?? TEXT.common.fallback }}
+ {{ detail.batch_no || TEXT.common.fallback }}
+ {{ detail.remark || TEXT.common.fallback }}
+
+
+
({
status: "",
site_name: "",
ship_date: "",
+ receive_date: "",
+ quantity: null,
+ batch_no: "",
carrier: "",
tracking_no: "",
- from_party: "",
- to_party: "",
- drug_desc: "",
remark: "",
});
@@ -109,6 +127,18 @@ onMounted(load);
color: var(--ctms-text-secondary);
}
+.content {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
.actions {
display: flex;
gap: 8px;
diff --git a/frontend/src/views/drug/ShipmentForm.vue b/frontend/src/views/drug/ShipmentForm.vue
index 9cdd9267..fa98835d 100644
--- a/frontend/src/views/drug/ShipmentForm.vue
+++ b/frontend/src/views/drug/ShipmentForm.vue
@@ -9,49 +9,85 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {{ TEXT.common.labels.basicInfo }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ TEXT.modules.drugShipments.recordLabel }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- {{ TEXT.common.actions.save }}
- {{ TEXT.common.actions.cancel }}
+
+ {{ TEXT.common.actions.save }}
+ {{ TEXT.common.actions.cancel }}
+
@@ -74,6 +110,7 @@ import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { useStudyStore } from "../../store/study";
import { createDrugShipment, getDrugShipment, updateDrugShipment } from "../../api/drugShipments";
+import { fetchSites } from "../../api/sites";
import AttachmentList from "../../components/attachments/AttachmentList.vue";
import StateEmpty from "../../components/StateEmpty.vue";
import { TEXT } from "../../locales";
@@ -82,37 +119,63 @@ const route = useRoute();
const router = useRouter();
const study = useStudyStore();
const saving = ref(false);
+const formRef = ref();
+const sites = ref([]);
const shipmentId = computed(() => route.params.shipmentId as string | undefined);
const isEdit = computed(() => !!shipmentId.value);
const studyId = computed(() => study.currentStudy?.id || "");
const form = reactive({
+ center_id: "",
direction: "SEND",
- site_name: "",
- drug_desc: "",
ship_date: "",
+ receive_date: "",
+ quantity: null as number | null,
+ batch_no: "",
carrier: "",
tracking_no: "",
- from_party: "",
- to_party: "",
status: "PENDING",
remark: "",
});
+const rules = {
+ center_id: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
+ direction: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
+ ship_date: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
+ receive_date: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
+ quantity: [{ required: true, type: "number", message: TEXT.common.messages.required, trigger: "change" }],
+ batch_no: [{ required: true, message: TEXT.common.messages.required, trigger: "blur" }],
+ carrier: [{ required: true, message: TEXT.common.messages.required, trigger: "blur" }],
+ tracking_no: [{ required: true, message: TEXT.common.messages.required, trigger: "blur" }],
+ status: [{ required: true, message: TEXT.common.messages.required, trigger: "change" }],
+ remark: [{ required: true, message: TEXT.common.messages.required, trigger: "blur" }],
+};
+
+const loadSites = async () => {
+ const studyId = study.currentStudy?.id;
+ if (!studyId) return;
+ try {
+ const { data } = await fetchSites(studyId, { limit: 500 });
+ sites.value = Array.isArray(data) ? data : data.items || [];
+ } catch {
+ sites.value = [];
+ }
+};
+
const load = async () => {
if (!isEdit.value || !studyId.value || !shipmentId.value) return;
try {
const { data } = await getDrugShipment(studyId.value, shipmentId.value);
Object.assign(form, {
direction: data.direction || "SEND",
- site_name: data.site_name || "",
- drug_desc: data.drug_desc || "",
+ center_id: data.center_id || "",
ship_date: data.ship_date || "",
+ receive_date: data.receive_date || "",
+ quantity: data.quantity ?? null,
+ batch_no: data.batch_no || "",
carrier: data.carrier || "",
tracking_no: data.tracking_no || "",
- from_party: data.from_party || "",
- to_party: data.to_party || "",
status: data.status || "PENDING",
remark: data.remark || "",
});
@@ -123,17 +186,19 @@ const load = async () => {
const submit = async () => {
if (!studyId.value) return;
+ const valid = await formRef.value?.validate().catch(() => false);
+ if (!valid) return;
saving.value = true;
try {
const payload = {
+ center_id: form.center_id,
direction: form.direction,
- site_name: form.site_name,
- drug_desc: form.drug_desc,
ship_date: form.ship_date || null,
+ receive_date: form.receive_date || null,
+ quantity: form.quantity,
+ batch_no: form.batch_no,
carrier: form.carrier || null,
tracking_no: form.tracking_no || null,
- from_party: form.from_party || null,
- to_party: form.to_party || null,
status: form.status,
remark: form.remark || null,
};
@@ -155,7 +220,10 @@ const submit = async () => {
const goBack = () => router.push("/drug/shipments");
-onMounted(load);
+onMounted(async () => {
+ await loadSites();
+ load();
+});
diff --git a/frontend/src/views/fees/ContractFees.vue b/frontend/src/views/fees/ContractFees.vue
index d66b8291..cef9e52c 100644
--- a/frontend/src/views/fees/ContractFees.vue
+++ b/frontend/src/views/fees/ContractFees.vue
@@ -157,7 +157,7 @@
{{ scope.row.remark || TEXT.common.fallback }}
-
+
-
-
+
-
+
+
+
-
-
+
+
+
+
+
-
+
@@ -33,23 +43,42 @@
-
-
- {{ displayEnum(TEXT.enums.shipmentDirection, scope.row.direction) }}
+
+
+ {{ scope.row.site_name || TEXT.common.fallback }}
+
+
+
+
+ {{ displayEnum(TEXT.enums.shipmentDirection, scope.row.direction) }}
+
+
-
-
{{ displayDate(scope.row.ship_date) }}
-
- {{ displayEnum(TEXT.enums.shipmentStatus, scope.row.status) }}
+
+ {{ displayDate(scope.row.receive_date) }}
-
+
+ {{ typeof scope.row.quantity === "number" ? scope.row.quantity : TEXT.common.fallback }}
+
+
+ {{ scope.row.batch_no || TEXT.common.fallback }}
+
+
- {{ TEXT.common.actions.view }}
- {{ TEXT.common.actions.edit }}
- {{ TEXT.common.actions.delete }}
+
+ {{ displayEnum(TEXT.enums.shipmentStatus, scope.row.status) }}
+
+
+
+
+ {{ scope.row.remark || TEXT.common.fallback }}
+
+
+
+ {{ TEXT.common.actions.delete }}
@@ -64,6 +93,7 @@ import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import { useStudyStore } from "../../store/study";
import { listDrugShipments, deleteDrugShipment } from "../../api/drugShipments";
+import { fetchSites } from "../../api/sites";
import { displayDate, displayEnum } from "../../utils/display";
import StateEmpty from "../../components/StateEmpty.vue";
import { TEXT } from "../../locales";
@@ -72,20 +102,32 @@ const router = useRouter();
const study = useStudyStore();
const loading = ref(false);
const items = ref([]);
+const sites = ref([]);
const filters = reactive({
- site_name: "",
- tracking_no: "",
+ center_id: "",
+ direction: "",
status: "",
});
+const loadSites = async () => {
+ const studyId = study.currentStudy?.id;
+ if (!studyId) return;
+ try {
+ const { data } = await fetchSites(studyId, { limit: 500 });
+ sites.value = Array.isArray(data) ? data : data.items || [];
+ } catch {
+ sites.value = [];
+ }
+};
+
const load = async () => {
const studyId = study.currentStudy?.id;
if (!studyId) return;
loading.value = true;
try {
const { data } = await listDrugShipments(studyId, {
- site_name: filters.site_name || undefined,
- tracking_no: filters.tracking_no || undefined,
+ center_id: filters.center_id || undefined,
+ direction: filters.direction || undefined,
status: filters.status || undefined,
});
items.value = Array.isArray(data) ? data : data.items || [];
@@ -97,15 +139,35 @@ const load = async () => {
};
const resetFilters = () => {
- filters.site_name = "";
- filters.tracking_no = "";
+ filters.center_id = "";
+ filters.direction = "";
filters.status = "";
load();
};
const goNew = () => router.push("/drug/shipments/new");
const goDetail = (id: string) => router.push(`/drug/shipments/${id}`);
-const goEdit = (id: string) => router.push(`/drug/shipments/${id}/edit`);
+const onRowClick = (row: any) => {
+ if (!row?.id) return;
+ goDetail(row.id);
+};
+
+const statusType = (status: string) => {
+ switch (status) {
+ case "PENDING":
+ return "info";
+ case "IN_TRANSIT":
+ return "primary";
+ case "SIGNED":
+ return "success";
+ case "RETURNED":
+ return "warning";
+ case "EXCEPTION":
+ return "danger";
+ default:
+ return "info";
+ }
+};
const remove = async (row: any) => {
const studyId = study.currentStudy?.id;
@@ -121,7 +183,10 @@ const remove = async (row: any) => {
}
};
-onMounted(load);
+onMounted(async () => {
+ await loadSites();
+ load();
+});