From ac9db561a42ec2301f1ffdb6419f4054d32aa557 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Thu, 5 Nov 2026 12:15:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(web-admin):=20=E8=A1=A5=E9=BD=90=E6=8D=90?= =?UTF-8?q?=E8=B5=A0=E6=9F=A5=E7=9C=8B=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=8E=A5=E7=BA=BF=E5=B9=B6=E4=BF=9D=E7=95=99=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E8=AF=AD=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 DonationViewDialog 缺失的操作日志状态与加载调用,清理误插入的重置语句,并避免显式 audit 动作被状态映射覆盖。 Co-authored-by: Cursor --- backend/app/crud/protocol_operation_log.py | 7 +- backend/tests/test_protocol_operation_log.py | 18 +++++ .../protocol/ProtocolViewDialog.vue | 2 - .../list/DonationViewDialog.vue | 72 ++++++++++++++++++- 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/backend/app/crud/protocol_operation_log.py b/backend/app/crud/protocol_operation_log.py index 4bc51ca8..bb18edf4 100644 --- a/backend/app/crud/protocol_operation_log.py +++ b/backend/app/crud/protocol_operation_log.py @@ -61,12 +61,15 @@ def resolve_action_from_status( before_status: Optional[str] = None, after_status: Optional[str] = None, ) -> str: - """状态流转优先映射为更具体的动作;否则沿用 default_action。""" + """仅对 update 默认动作按状态流转细化;显式动作(create/delete/audit 等)保持原值。""" + action = (default_action or "").strip() or ACTION_UPDATE + if action != ACTION_UPDATE: + return action before = (before_status or "").strip() after = (after_status or "").strip() if after and after != before and after in _STATUS_ACTION_MAP: return _STATUS_ACTION_MAP[after] - return default_action + return action def _operator_name(db: Session, user_id: Optional[str]) -> str: diff --git a/backend/tests/test_protocol_operation_log.py b/backend/tests/test_protocol_operation_log.py index 163c0cb5..9f3e214a 100644 --- a/backend/tests/test_protocol_operation_log.py +++ b/backend/tests/test_protocol_operation_log.py @@ -116,7 +116,9 @@ def test_record_cell_update_appends_before_after_and_maps_void_action(): def test_resolve_action_from_status_keeps_default_when_unchanged(): from app.crud.protocol_operation_log import ( + ACTION_AUDIT, ACTION_UPDATE, + ACTION_VOID, resolve_action_from_status, ) @@ -128,3 +130,19 @@ def test_resolve_action_from_status_keeps_default_when_unchanged(): ) == ACTION_UPDATE ) + assert ( + resolve_action_from_status( + default_action=ACTION_UPDATE, + before_status="sxz", + after_status="yzf", + ) + == ACTION_VOID + ) + assert ( + resolve_action_from_status( + default_action=ACTION_AUDIT, + before_status="dsx", + after_status="sxz", + ) + == ACTION_AUDIT + ) diff --git a/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue b/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue index acfea86f..afd97889 100644 --- a/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue +++ b/web-admin/src/views/cell-storage/protocol/ProtocolViewDialog.vue @@ -2196,7 +2196,6 @@ async function loadStatusChangeRecords() { if (!props.protocolId) { statusChangeRecords.value = [] - operationLogRecords.value = [] return } statusChangeLoading.value = true @@ -2205,7 +2204,6 @@ statusChangeRecords.value = res.list || [] } catch { statusChangeRecords.value = [] - operationLogRecords.value = [] } finally { statusChangeLoading.value = false } diff --git a/web-admin/src/views/research-donation/list/DonationViewDialog.vue b/web-admin/src/views/research-donation/list/DonationViewDialog.vue index 79f5d827..c1ba55b6 100644 --- a/web-admin/src/views/research-donation/list/DonationViewDialog.vue +++ b/web-admin/src/views/research-donation/list/DonationViewDialog.vue @@ -636,9 +636,72 @@ const loading = ref(false) const auditLoading = ref(false) const statusChangeLoading = ref(false) + const operationLogLoading = ref(false) const bundle = ref | null>(null) const auditRecords = ref>>([]) const statusChangeRecords = ref>>([]) + const operationLogRecords = ref>>([]) + + const OPERATION_ACTION_LABEL: Record = { + create: '创建', + update: '修改', + delete: '删除', + void: '作废', + activate: '生效', + expire: '过期', + to_research: '转科研', + to_storage: '转细胞存储', + audit: '审核' + } + const OPERATION_ACTION_TAG: Record< + string, + 'success' | 'warning' | 'danger' | 'primary' | 'info' + > = { + create: 'success', + update: 'primary', + delete: 'danger', + void: 'danger', + activate: 'success', + expire: 'warning', + to_research: 'primary', + to_storage: 'primary', + audit: 'info' + } + + function operationActionLabel(action: string) { + return OPERATION_ACTION_LABEL[action] || action || '—' + } + function operationActionTagType(action: string) { + return OPERATION_ACTION_TAG[action] || 'info' + } + function operationChannelLabel(channel: string) { + if (channel === 'mini') return '小程序' + if (channel === 'web') return 'Web' + return channel || '—' + } + function operationSummary(row: Record) { + const before = (row.before || {}) as Record + const after = (row.after || {}) as Record + const beforeProto = (before.donation || before.protocol || {}) as Record + const afterProto = (after.donation || after.protocol || {}) as Record + const beforeParty = (before.party || before.partyCustomer || {}) as Record + const afterParty = (after.party || after.partyCustomer || {}) as Record + const parts: string[] = [] + const bs = String(beforeProto.donationStatus || beforeProto.protocolStatus || '') + const as = String(afterProto.donationStatus || afterProto.protocolStatus || '') + if (bs && as && bs !== as) parts.push(`状态 ${bs}→${as}`) + else if (as) parts.push(`状态 ${as}`) + for (const key of ['motherName', 'customerName', 'donorName', 'name']) { + const b = String(beforeParty[key] || '').trim() + const a = String(afterParty[key] || '').trim() + if (b || a) { + if (b && a && b !== a) parts.push(`姓名 ${b}→${a}`) + else if (a && !b) parts.push(`姓名 ${a}`) + break + } + } + return parts.join(';') || String(row.remark || '').trim() || '见前后快照' + } const STATUS_CHANGE_TYPE_LABEL: Record = { activate: '生效', @@ -982,7 +1045,6 @@ async function loadStatusChangeRecords() { if (!props.donationId) { statusChangeRecords.value = [] - operationLogRecords.value = [] return } statusChangeLoading.value = true @@ -991,7 +1053,6 @@ statusChangeRecords.value = res.list || [] } catch { statusChangeRecords.value = [] - operationLogRecords.value = [] } finally { statusChangeLoading.value = false } @@ -1045,7 +1106,11 @@ const data = await fetchResearchDonationDetail(props.donationId) bundle.value = data as Record await loadLookups(data as Record) - await Promise.all([loadAuditRecords(), loadStatusChangeRecords()]) + await Promise.all([ + loadAuditRecords(), + loadStatusChangeRecords(), + loadOperationLogRecords() + ]) if (props.highlightSampleCode) { nextTick(() => { document @@ -1077,6 +1142,7 @@ loading.value = false auditLoading.value = false statusChangeLoading.value = false + operationLogLoading.value = false } watch(