fix(web-admin): 补齐捐赠查看操作日志接线并保留审核动作语义

修复 DonationViewDialog 缺失的操作日志状态与加载调用,清理误插入的重置语句,并避免显式 audit 动作被状态映射覆盖。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-11-05 12:15:10 +08:00
1 parent 4a12d92631
commit ac9db561a4
4 files changed
+92 -7

No files matched your search

+5 -2
View File
@@ -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:
@@ -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
)
@@ -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
}
@@ -636,9 +636,72 @@
const loading = ref(false)
const auditLoading = ref(false)
const statusChangeLoading = ref(false)
const operationLogLoading = ref(false)
const bundle = ref<Record<string, any> | null>(null)
const auditRecords = ref<Array<Record<string, unknown>>>([])
const statusChangeRecords = ref<Array<Record<string, unknown>>>([])
const operationLogRecords = ref<Array<Record<string, unknown>>>([])
const OPERATION_ACTION_LABEL: Record<string, string> = {
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<string, unknown>) {
const before = (row.before || {}) as Record<string, any>
const after = (row.after || {}) as Record<string, any>
const beforeProto = (before.donation || before.protocol || {}) as Record<string, any>
const afterProto = (after.donation || after.protocol || {}) as Record<string, any>
const beforeParty = (before.party || before.partyCustomer || {}) as Record<string, any>
const afterParty = (after.party || after.partyCustomer || {}) as Record<string, any>
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<string, string> = {
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<string, any>
await loadLookups(data as Record<string, any>)
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(