Files
zkxw/mobile-oa-h5/src/market/modules/protocol.js
T
zlwuandCursor a4defb2c94 fix(mobile): 协议单列表项目筛选下移并补齐科研捐赠筛选
将项目筛选并入搜索框下方的 van-dropdown 行,与协议状态样式一致;科研捐赠 Tab 同步展示两项筛选,并修复捐赠列表按 protocolStatus 过滤 donation_status。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-05 17:13:15 +08:00

842 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { groupLegacyPaymentPeriods } from '../protocol-v1-payment.js'
import {
formatProductionReceptionDate,
productionReceptionFieldValue,
resolveProductionReceptionFields
} from '../protocol-production-reception.js'
import {
resolveStatusTag,
protocolStatus,
donationStatus,
payMode,
payStatus,
paymentScheduleStatus,
auditStatus
} from '../status-tag-registry.js'
const SEX_LABELS = Object.freeze({ '1': '男', '0': '女' })
/** 科研捐赠客户信息只展示母亲姓名;这些父母字段仍参与提交,避免编辑时清空已有数据 */
const DONATION_HIDDEN_PARENT_KEYS = new Set([
'motherPhone', 'motherIdCard', 'motherEmail', 'motherBirthYm', 'motherEthnic',
'motherAddress', 'fatherName', 'fatherPhone', 'fatherIdCard'
])
const DONATION_PARENT_FIELD_HIDE = Object.freeze({ hiddenSources: ['research_donation'] })
const DELIVERY_LABELS = Object.freeze({ sc: '顺产', pgc: '剖宫产' })
const SAMPLE_STATUS_LABELS = Object.freeze({
ycj: '已采集', jcz: '检测中', yrk: '已入库', yzf: '已作废'
})
const VOID_TYPE_LABELS = Object.freeze({
khhc: '客户毁存', jcbhg: '检测不合格'
})
const STATUS_CHANGE_TYPE_LABELS = Object.freeze({
activate: '生效',
expire: '过期',
void: '作废',
to_research: '转科研',
to_storage: '转细胞存储'
})
const CELL_PROTOCOL_STATUS_OPTIONS = Object.freeze([
{ label: '待生效', value: 'dsx' },
{ label: '生效中', value: 'sxz' },
{ label: '已过期', value: 'ygq' },
{ label: '已作废', value: 'yzf' },
{ label: '属地废弃', value: 'sdfq' },
{ label: '转科研', value: 'zky' }
])
const DONATION_PROTOCOL_STATUS_OPTIONS = Object.freeze([
{ label: '待生效', value: 'dsx' },
{ label: '生效中', value: 'sxz' },
{ label: '已过期', value: 'ygq' },
{ label: '已作废', value: 'yzf' },
{ label: '属地废弃', value: 'sdfq' },
{ label: '转细胞存储', value: 'zckc' }
])
const TRANSFER_TYPE_LABELS = Object.freeze({
wxzz: '微信转账', wx: '微信转账', wechat: '微信转账',
zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账',
yhkzz: '银行卡转账', bank: '银行卡转账',
xj: '现金', cash: '现金', lszz: '乐刷'
})
function value(source, key) {
const result = source?.[key]
return result == null || result === '' ? '—' : String(result)
}
function label(map, source) {
const key = String(source || '').trim()
return map[key] || (key ? (/[㐀-鿿]/u.test(key) ? key : '未知') : '—')
}
/** 细胞存储走 protocolStatus;捐赠侧码(如 zckc)走 donationStatus */
function bizStatusTag(code, source) {
const key = String(code || '').trim()
if (!key) return { label: '—', tagType: 'info' }
if (source === 'research_donation') {
if (donationStatus[key]) return resolveStatusTag(donationStatus, key)
if (protocolStatus[key]) return resolveStatusTag(protocolStatus, key)
return resolveStatusTag(donationStatus, key)
}
if (protocolStatus[key]) return resolveStatusTag(protocolStatus, key)
if (donationStatus[key]) return resolveStatusTag(donationStatus, key)
return resolveStatusTag(protocolStatus, key)
}
function payModeTag(code) {
return resolveStatusTag(payMode, code)
}
function transferPayTag(code) {
return resolveStatusTag(payStatus, code)
}
function scheduleTag(code) {
return resolveStatusTag(paymentScheduleStatus, code)
}
function auditTag(code) {
return resolveStatusTag(auditStatus, code)
}
function statusChangeTypeTone(changeType) {
if (changeType === 'void') return 'danger'
if (changeType === 'expire') return 'warning'
if (changeType === 'activate') return 'success'
return 'primary'
}
function statusChangeFlowText(row, statusRegistry) {
const fromLabel = row?.fromStatus
? resolveStatusTag(statusRegistry, row.fromStatus).label
: ''
const toLabel = resolveStatusTag(statusRegistry, row?.toStatus).label
if (fromLabel) return `${fromLabel} → ${toLabel}`
return toLabel || '—'
}
function statusChangeDescText(row) {
const changeType = String(row?.changeType || '')
const desc = String(row?.changeDesc || '').trim()
if (changeType === 'void') {
const voidLabel =
VOID_TYPE_LABELS[String(row?.voidType || '')] ||
String(row?.voidTypeLabel || row?.voidType || '').trim()
if (voidLabel && desc) return `作废类型:${voidLabel};${desc}`
if (voidLabel) return `作废类型:${voidLabel}`
}
return desc || '—'
}
function operationActionLabel(action) {
const map = {
create: '创建',
update: '修改',
delete: '删除',
void: '作废',
activate: '生效',
expire: '过期',
to_research: '转科研',
to_storage: '转细胞存储',
audit: '审核'
}
return map[action] || action || '—'
}
function operationChannelLabel(channel) {
if (channel === 'mini') return '小程序'
if (channel === 'web') return 'Web'
return channel || '—'
}
function buildOperationLogCards(rows) {
return (Array.isArray(rows) ? rows : []).map((row, index) => {
const action = String(row?.action || '')
const before = row?.before || {}
const after = row?.after || {}
const beforeProto = before.protocol || before.donation || {}
const afterProto = after.protocol || after.donation || {}
const beforeParty = before.partyCustomer || before.party || {}
const afterParty = after.partyCustomer || after.party || {}
const beforeStatus = String(beforeProto.protocolStatus || beforeProto.donationStatus || '')
const afterStatus = String(afterProto.protocolStatus || afterProto.donationStatus || '')
let summary = ''
if (beforeStatus && afterStatus && beforeStatus !== afterStatus) summary = `状态 ${beforeStatus}→${afterStatus}`
else if (afterStatus) summary = `状态 ${afterStatus}`
for (const key of ['motherName', 'customerName', 'donorName', 'name']) {
const b = String(beforeParty[key] || '').trim()
const a = String(afterParty[key] || '').trim()
if (b || a) {
const namePart = b && a && b !== a ? `姓名 ${b}→${a}` : a ? `姓名 ${a}` : ''
summary = summary ? `${summary};${namePart}` : namePart
break
}
}
if (!summary) summary = String(row?.remark || '').trim() || '见前后快照'
return {
key: `operation-log-${row?.id || index}`,
title: `${index + 1}. ${operationActionLabel(action)}`,
fields: [
{ label: '来源端', value: operationChannelLabel(String(row?.clientChannel || '')) },
{ label: '操作时间', value: row?.operateTime || '—' },
{ label: '操作人', value: row?.operatorUserName || '—' },
{ label: '变更摘要', value: summary }
]
}
})
}
function buildStatusChangeCards(rows, statusRegistry) {
return (rows || []).map((row, index) => {
const changeType = String(row?.changeType || '')
return {
key: String(row?.id || `status-${index}`),
title: `变更 ${index + 1}`,
statusText: STATUS_CHANGE_TYPE_LABELS[changeType] || changeType || '变更',
statusTone: statusChangeTypeTone(changeType),
fields: [
{ label: '变更时间', value: value(row, 'changeTime') },
{ label: '状态流转', value: statusChangeFlowText(row, statusRegistry) },
{ label: '变更说明', value: statusChangeDescText(row) },
{ label: '操作人', value: person(row?.operatorUserName, row?.operatorUserId) }
]
}
})
}
/** 无 statusChanges 时,从主表快照字段合成卡片(对齐方案 A 单行展示) */
function synthesizeStatusChangesFromBasic(basic, source) {
const rows = []
if (basic?.effectiveTime) {
rows.push({
id: 'snap-activate',
changeType: 'activate',
toStatus: 'sxz',
changeTime: basic.effectiveTime,
changeDesc: basic.effectiveDesc || ''
})
}
if (basic?.expireTime) {
rows.push({
id: 'snap-expire',
changeType: 'expire',
toStatus: 'ygq',
changeTime: basic.expireTime,
changeDesc: basic.expireDesc || ''
})
}
if (basic?.voidTime) {
rows.push({
id: 'snap-void',
changeType: 'void',
toStatus: 'yzf',
changeTime: basic.voidTime,
changeDesc: basic.voidDesc || '',
voidType: basic.voidType || '',
voidTypeLabel: basic.voidTypeLabel || ''
})
}
if (source === 'research_donation') {
const convertTime = basic?.convertTime || basic?.toStorageTime
if (convertTime) {
rows.push({
id: 'snap-to-storage',
changeType: 'to_storage',
toStatus: 'zckc',
changeTime: convertTime,
changeDesc: basic?.convertDesc || basic?.toStorageDesc || ''
})
}
} else if (basic?.toResearchTime) {
rows.push({
id: 'snap-to-research',
changeType: 'to_research',
toStatus: 'zky',
changeTime: basic.toResearchTime,
changeDesc: basic.toResearchDesc || ''
})
}
return rows
}
function person(name, id) {
const displayName = String(name || '').trim()
const referenceId = String(id || '').trim()
if (displayName && displayName !== referenceId) return displayName
return referenceId ? '未知人员' : '—'
}
function referenceName(name, id) {
const displayName = String(name || '').trim()
const referenceId = String(id || '').trim()
if (displayName && displayName !== referenceId) return displayName
return referenceId ? '未知' : '—'
}
function fields(source, definitions) {
return definitions.map(([key, text, options = {}]) => ({
label: text,
value: options.map ? options.map(source?.[key]) : value(source, key),
...options,
map: undefined
}))
}
function checkedItems(items) {
const names = (items || [])
.filter((item) => item?.itemName)
.map((item) => `${item.itemName}:${item.checked ? '是' : '否'}`)
return names.length ? names.join('、') : '—'
}
function transferAttachmentValue(transfer) {
return transfer?.attachments || transfer?.transferAttachmentFiles || transfer?.transferAttachments || ''
}
function refundRecordAttachmentValue(row) {
if (Array.isArray(row?.refundAttachmentFiles) && row.refundAttachmentFiles.length) {
return row.refundAttachmentFiles
}
return row?.refundAttachments || ''
}
function refundRecordCards(records) {
return (records || []).map((row, index) => ({
key: String(row?.id || `refund-${index}`),
title: `退费登记 ${index + 1}`,
fields: [
{ label: '退费金额', value: value(row, 'refundAmount') },
{ label: '退费时间', value: value(row, 'refundTime') },
{ label: '备注', value: value(row, 'refundRemark') },
{ label: '附件', type: 'attachments', value: refundRecordAttachmentValue(row) }
]
}))
}
function transferTypeText(transfer) {
const backendLabel = String(transfer?.transferTypeLabel || '').trim()
if (backendLabel) return backendLabel
const code = String(transfer?.transferType || '').trim()
if (!code) return '—'
return TRANSFER_TYPE_LABELS[code] || (/[㐀-鿿]/u.test(code) ? code : code)
}
function sampleLifecycleCards(item, source) {
const cards = []
const reception = item?.sampleReception
if (reception) {
cards.push({
key: `${item?.id || item?.sampleCode}-reception`,
title: '质检接收',
statusText: value(reception, 'acceptConclusionLabel'),
fields: [
{ label: '接收时间', value: value(reception, 'receptionTime') },
{ label: '经手人', value: value(reception, 'handlerName') },
{ label: '接收温度(℃)', value: value(reception, 'receiveTemperature') },
{ label: '结论', value: value(reception, 'acceptConclusionLabel'), tag: true },
{ label: '实验室入库编码', value: value(reception, 'labStorageCode') },
{ label: '检查项', value: checkedItems(reception?.checkResults) },
{ label: '备注', value: value(reception, 'remark') },
{ label: '接收附件', value: reception?.receiveAttachmentFiles || reception?.receiveAttachments || '', type: 'attachments' }
]
})
}
const productionReception = item?.productionReception || reception?.productionReception
if (productionReception) {
const productionFields = resolveProductionReceptionFields(
source,
item?.sampleTypeName || ''
)
cards.push({
key: `${item?.id || item?.sampleCode}-production-reception`,
title: '生产接收',
fields: [
{
label: '处理时间',
value: formatProductionReceptionDate(productionReception?.processTime)
},
{ label: '处理人', value: value(productionReception, 'handlerName') },
...productionFields.map((field) => ({
label: field.label,
value: productionReceptionFieldValue(field, productionReception)
})),
{ label: '备注', value: value(productionReception, 'remark') }
]
})
}
const release = item?.storageReleaseInfo
if (release) {
cards.push({
key: `${item?.id || item?.sampleCode}-storage-release`,
title: '入库放行',
statusText: value(release, 'storageStatusLabel'),
fields: [
{ label: '生产结论', value: value(release, 'prodReviewConclusionLabel'), tag: true },
{ label: '质检结论', value: value(release, 'qcReviewConclusionLabel'), tag: true },
{ label: '入库状态', value: value(release, 'storageStatusLabel'), tag: true },
{ label: '生产审核人', value: value(release, 'prodReviewByName') },
{ label: '生产审核时间', value: value(release, 'prodReviewTime') },
{ label: '生产审核备注', value: value(release, 'prodReviewRemark') },
{ label: '质检审核人', value: value(release, 'qcReviewByName') },
{ label: '质检审核时间', value: value(release, 'qcReviewTime') },
{ label: '质检审核备注', value: value(release, 'qcReviewRemark') },
{ label: '质检附件', value: release?.qcReviewAttachments || '', type: 'attachments' },
{ label: '生产附件', value: release?.prodReviewAttachments || '', type: 'attachments' }
]
})
}
return cards
}
function paymentTransferCard(transfer, transferIndex) {
const pay = transferPayTag(transfer?.payStatus)
const transferType = transferTypeText(transfer)
const transferTime = value(transfer, 'transferTime')
return {
key: String(transfer?.id || `transfer-${transferIndex}`),
title: `转账 ${transferIndex + 1}`,
variant: 'transfer',
transferIndex: transferIndex + 1,
amount: value(transfer, 'transferAmount'),
meta: `${transferType} · ${transferTime}`,
statusText: pay.label,
statusTone: pay.tagType,
fields: [
{ label: '备注', value: value(transfer, 'remark') },
{ label: '财务备注', value: value(transfer, 'financeRemark') },
{ label: '转账凭证', value: transferAttachmentValue(transfer), type: 'attachments' }
]
}
}
function paymentScheduleCards(pay) {
return (pay?.schedules || []).map((schedule, scheduleIndex) => {
const status = scheduleTag(schedule?.scheduleStatus)
return {
key: String(schedule?.id || `schedule-${scheduleIndex}`),
title: `第 ${schedule?.scheduleNo || scheduleIndex + 1} 期`,
variant: 'schedule',
cardsLabel: '转账记录',
statusText: status.label,
statusTone: status.tagType,
fields: [
{ label: '应缴金额', value: value(schedule, 'dueAmount'), money: true },
{ label: '应缴日期', value: value(schedule, 'dueDate') },
{ label: '实缴金额', value: value(schedule, 'paidAmount'), money: true },
{ label: '欠缴金额', value: value(schedule, 'arrearsAmount'), money: true }
],
cards: (schedule?.transfers || []).map(paymentTransferCard)
}
})
}
function legacyPaymentDetailCards(payment, details) {
return groupLegacyPaymentPeriods({ payment, details }).map((period, index) => {
const status = scheduleTag(period.scheduleStatus)
return {
key: `legacy-payment-${period.scheduleNo || index + 1}`,
title: `第 ${period.scheduleNo || index + 1} 期`,
variant: 'schedule',
cardsLabel: '转账记录',
statusText: status.label,
statusTone: status.tagType,
fields: [
{ label: '应缴金额', value: period.dueAmount == null ? '—' : String(period.dueAmount), money: true },
{ label: '应缴日期', value: period.dueDate || '—' },
{ label: '确认实缴金额', value: String(period.paidAmount), money: true },
{ label: '欠缴金额', value: period.arrearsAmount == null ? '—' : String(period.arrearsAmount), money: true }
],
cards: period.transfers.map((detail, transferIndex) =>
paymentTransferCard(
{ ...detail, id: detail?.id || `legacy-transfer-${period.scheduleNo}-${transferIndex}` },
transferIndex
)
)
}
})
}
export default {
key: 'protocol',
resource: 'protocols',
actions: { specialist: ['view', 'create', 'edit'], manager: ['view', 'create', 'edit'] },
listParams: ({ status } = {}) => ({
source: ['cell_storage', 'research_donation'].includes(String(status || '').trim())
? String(status).trim()
: 'cell_storage'
}),
statusOptions: [
{ text: '细胞存储', value: 'cell_storage' },
{ text: '科研捐赠', value: 'research_donation' }
],
// 协议列表用搜索下方 van-dropdown-menu(对齐预签约),不走筛选弹层
filterFields: [],
inlineDropdownFilters: [
{
key: 'protocolStatus',
allText: '全部协议状态',
source: 'cell_storage',
options: CELL_PROTOCOL_STATUS_OPTIONS.map((item) => ({ text: item.label, value: item.value }))
},
{
key: 'protocolStatus',
allText: '全部协议状态',
source: 'research_donation',
options: DONATION_PROTOCOL_STATUS_OPTIONS.map((item) => ({ text: item.label, value: item.value }))
}
],
statusLabel(detail) {
const basic = detail?.protocol || detail?.basic || detail || {}
const status = detail?.source === 'research_donation'
? basic?.donationStatus
: basic?.protocolStatus
return bizStatusTag(status, detail?.source).label
},
statusTone(detail) {
const basic = detail?.protocol || detail?.basic || detail || {}
const status = detail?.source === 'research_donation'
? basic?.donationStatus
: basic?.protocolStatus
return bizStatusTag(status, detail?.source).tagType
},
detailSections(detail) {
if (Array.isArray(detail?.sections)) {
return detail.sections.map((item) => ({ ...item, fields: item?.fields || [], cards: item?.cards || [] }))
}
const source = detail?.source
const basic = detail?.protocol || detail?.basic || detail || {}
const statusKey = source === 'research_donation' ? 'donationStatus' : 'protocolStatus'
const dateKey = source === 'research_donation' ? 'donationDate' : 'registerDate'
const bizStatus = bizStatusTag(basic?.[statusKey], source)
const isDonation = source === 'research_donation'
const values = [
{
key: 'protocol',
title: isDonation ? '基本信息' : '协议信息',
fields: isDonation
? [
{ label: '状态', value: bizStatus.label, tag: true, tagType: bizStatus.tagType },
{ label: '母亲姓名', value: value(detail?.customer, 'motherName') },
{ label: '所属项目', value: basic.projectName || (basic.projectId ? '历史项目' : '未归属') },
{ label: '项目组', value: basic.teamName || '—' },
{ label: '捐赠日期', value: value(basic, dateKey) },
{ label: '创建时间', value: value(basic, 'createTime') },
{ label: '备注', value: value(basic, 'remark') }
]
: [
{ label: '协议状态', value: bizStatus.label, tag: true, tagType: bizStatus.tagType },
{ label: '母亲姓名', value: value(detail?.customer, 'motherName') },
{ label: '所属项目', value: basic.projectName || (basic.projectId ? '历史项目' : '未归属') },
{ label: '项目组', value: basic.teamName || '—' },
{ label: '登记日期', value: value(basic, dateKey) },
{ label: '协议总金额', value: value(basic, 'protocolTotalAmount') },
{ label: '备注', value: value(basic, 'remark') },
{ label: '创建时间', value: value(basic, 'createTime') },
{ label: '更新时间', value: value(basic, 'updateTime') }
],
cards: []
},
{
key: 'collection',
title: '采集信息',
fields: fields(detail?.collection, [
['salesUserName', '市场专员', { map: () => person(detail?.collection?.salesUserName, detail?.collection?.salesUserId) }],
['regionName', '采集地区', { map: () => referenceName(detail?.collection?.regionName, detail?.collection?.regionId) }],
['hospitalName', '采集医院', { map: () => referenceName(detail?.collection?.hospitalName, detail?.collection?.hospitalId) }],
['regionManagerUserName', '地区经理', { map: () => person(detail?.collection?.regionManagerUserName, detail?.collection?.regionManagerUserId) }],
['medicalStaff', '采集医护'],
['sampleCollectTime', '样本采集时间'],
['maternalBloodCollectTime', '母血采集时间']
]),
cards: []
},
{
key: 'customer',
title: '客户信息',
fields: fields(detail?.customer, [
['motherName', '母亲姓名'], ['motherPhone', '母亲联系电话'], ['motherIdCard', '母亲身份证号'],
['motherEmail', '母亲邮箱'], ['motherBirthYm', '母亲出生年月'], ['motherEthnic', '母亲民族'],
['motherAddress', '母亲家庭住址'], ['fatherName', '父亲姓名'], ['fatherPhone', '父亲电话'],
['fatherIdCard', '父亲身份证号'], ['newbornName', '新生儿姓名'],
['newbornSex', '新生儿性别', { map: (item) => label(SEX_LABELS, item) }],
['expectedDeliveryDate', '预产期'], ['newbornDeliveryTime', '新生儿分娩时间'],
['newbornIdCard', '新生儿身份证'],
['deliveryMode', '分娩方式', { map: (item) => label(DELIVERY_LABELS, item) }],
['birthOrder', '胎次'], ['newbornWeightGram', '新生儿体重(克)'],
['apgar1min', '新生儿评分(1分钟)']
].filter(([key]) => !isDonation || !DONATION_HIDDEN_PARENT_KEYS.has(key))),
cards: []
},
{
key: 'samples',
title: isDonation ? '样本及运输信息' : '样本信息',
fields: [],
cards: (detail?.samples || []).map((item, index) => ({
key: String(item?.id || `sample-${index}`),
title: item?.sampleCode || `样本 ${index + 1}`,
statusText: label(SAMPLE_STATUS_LABELS, item?.sampleStatusLabel || item?.sampleStatus),
fields: fields(item, [
['sampleTypeName', '样本类型', { map: () => referenceName(item?.sampleTypeName, item?.sampleTypeId) }],
['sampleCode', '样本编码'],
['sampleStatus', '样本状态', { map: () => label(SAMPLE_STATUS_LABELS, item?.sampleStatusLabel || item?.sampleStatus) }],
...(source === 'cell_storage'
? [['price', '优惠价'], ['originalPrice', '原价'], ['storageYears', '存储年限']]
: []),
[
'transportMode',
'运输方式',
{
map: () =>
item?.freight?.transportModeLabel ||
item?.freight?.transportMode ||
'—'
}
],
[
'freightAmount',
'运费金额',
{
map: () => {
const freight = item?.freight || {}
const shared = Number(freight.linkedSampleCount || 0) > 1
const amount = freight.freightAmount
if (String(freight.transportMode || '') === 'sfkd' && (amount == null || amount === '')) {
return `月结${shared ? '(共用)' : ''}`
}
if (amount == null) return '—'
return `${amount}${shared ? '(共用)' : ''}`
}
}
],
['etaTime', '预计到达时间', { map: () => item?.etaTime || item?.transport?.etaTime || '' }],
['isSpecial', '是否特殊样本', { map: (itemValue) => Number(itemValue) === 1 ? '是' : '否' }],
['specialRemark', '特殊说明']
]),
cards: sampleLifecycleCards(item, source)
}))
}
]
const depositTransfers = Array.isArray(detail?.depositTransfers) ? detail.depositTransfers : []
const depositHint = detail?.presignDepositHint
if (detail?.source === 'cell_storage' && (depositHint || depositTransfers.length)) {
const amt = Number(depositHint?.confirmedDepositAmount || 0)
const cnt = Number(depositHint?.confirmedTransferCount || 0)
values.push({
key: 'deposit',
title: '意向金(关联预签约)',
fields: depositHint
? [{
label: '已确认意向金',
value: `¥${amt.toFixed(2)}(${cnt} 笔)`
}]
: [],
cards: depositTransfers.map((tr, index) => {
const pay = transferPayTag(tr?.payStatus)
return {
key: String(tr?.id || `deposit-${index}`),
title: `意向金转账 ${index + 1}`,
statusText: pay.label,
statusTone: pay.tagType,
fields: [
{ label: '转账方式', value: transferTypeText(tr) },
{ label: '转账金额', value: value(tr, 'transferAmount') },
{ label: '转账时间', value: value(tr, 'transferTime') },
{ label: '转账凭证', value: transferAttachmentValue(tr), type: 'attachments' },
{ label: '备注', value: value(tr, 'remark') },
{ label: '财务备注', value: value(tr, 'financeRemark') }
]
}
})
})
}
if (detail?.source === 'cell_storage') {
const samplePays = Array.isArray(detail?.samplePayments) ? detail.samplePayments : []
if (samplePays.length) {
values.push({
key: 'payment',
title: '缴费信息',
fields: [],
cards: samplePays.map((pay, index) => {
const mode = payModeTag(pay?.payMode)
return {
key: String(pay?.id || pay?.sampleCode || `pay-${index}`),
title: pay?.sampleCode || `样本缴费 ${index + 1}`,
fields: [
{ label: '缴费模式', value: mode.label, tag: true, tagType: mode.tagType },
{ label: '合同金额', value: value(pay, 'contractAmount') },
...(String(pay?.payMode || '') === 'dj'
? [{ label: '趸交截止时间', value: value(pay, 'lumpSumDeadline') }]
: [
{ label: '分期期数', value: value(pay, 'installmentCount') },
{ label: '每期间隔(月)', value: value(pay, 'installmentIntervalMonths') },
{ label: '分期开始时间', value: value(pay, 'installmentStartDate') },
{ label: '提前催缴预警(天)', value: value(pay, 'installmentAdvanceDays') }
]),
...((pay?.schedules || []).map((sch, sIdx) => ({
label: `第 ${sch?.scheduleNo || sIdx + 1} 期`,
value: `应缴 ${sch?.dueAmount ?? '—'} / 实缴 ${sch?.paidAmount ?? '—'} / 欠缴 ${sch?.arrearsAmount ?? '—'} / ${sch?.dueDate || '—'}`
})))
],
cards: paymentScheduleCards(pay)
}
})
})
} else {
const legacyDetails = Array.isArray(detail?.paymentDetails) ? detail.paymentDetails : []
const legacyMode = payModeTag(detail?.payment?.payMode)
values.push({
key: 'payment',
title: '缴费信息',
fields: [
{ label: '缴费模式', value: legacyMode.label, tag: true, tagType: legacyMode.tagType },
...fields(detail?.payment, [
['contractAmount', '合同金额'], ['installmentCount', '分期期数'],
['installmentIntervalMonths', '每期间隔(月)'], ['lumpSumDeadline', '趸交截止时间'],
['installmentStartDate', '分期开始时间'], ['installmentAdvanceDays', '提前催缴预警(天)'],
['remark', '缴费备注']
])
],
cards: legacyPaymentDetailCards(detail?.payment || {}, legacyDetails)
})
}
const refundRecords = Array.isArray(detail?.refundRecords) ? detail.refundRecords : []
values.push({
key: 'refundRecords',
title: '退费登记',
fields: refundRecords.length ? [] : [{ label: '记录', value: '暂无退费登记' }],
cards: refundRecordCards(refundRecords)
})
}
const audits = Array.isArray(detail?.audits) ? detail.audits : []
values.push({
key: 'audits',
title: '审核记录',
fields: audits.length ? [] : [{ label: '记录', value: '暂无审核记录' }],
cards: audits.map((row, index) => {
const audit = auditTag(row?.auditStatus)
return {
key: String(row?.id || `audit-${index}`),
title: `审核 ${index + 1}`,
statusText: audit.label,
statusTone: audit.tagType,
fields: [
{ label: '审核人', value: person(row?.auditorUserName, row?.auditorUserId) },
{ label: '审核意见', value: value(row, 'auditRemark') },
{ label: '时间', value: value(row, 'createTime') }
]
}
})
})
values.push({
key: 'attachments',
title: '附件信息',
fields: isDonation
? [
{ label: '知情同意书', type: 'attachments', value: detail?.attachments?.consentFormUrls || '' },
{ label: '传染病检测报告', type: 'attachments', value: detail?.attachments?.infectiousDiseaseReportUrls || '' },
{ label: '其他附件', type: 'attachments', value: detail?.attachments?.attachmentUrls || '' }
]
: [
{ label: '协议附件', type: 'attachments', value: detail?.attachments?.protocolAttachments || '' },
{ label: '知情同意书', type: 'attachments', value: detail?.attachments?.consentFormUrls || '' },
{ label: '孕妇健康调查表', type: 'attachments', value: detail?.attachments?.healthSurveyFormUrls || '' }
],
cards: []
})
const statusRegistry = source === 'research_donation' ? donationStatus : protocolStatus
const statusChangeRows = Array.isArray(detail?.statusChanges) && detail.statusChanges.length
? detail.statusChanges
: synthesizeStatusChangesFromBasic(basic, source)
const statusChangeCards = buildStatusChangeCards(statusChangeRows, statusRegistry)
const operationLogRows = Array.isArray(detail?.operationLogs) ? detail.operationLogs : []
const operationLogCards = buildOperationLogCards(operationLogRows)
values.push({
key: 'statusChange',
title: '状态变更记录',
fields: statusChangeCards.length
? []
: [{ label: '记录', value: '暂无状态变更记录' }],
cards: statusChangeCards
},
{
key: 'operationLog',
title: '操作日志',
fields: operationLogCards.length
? []
: [{ label: '记录', value: '暂无操作日志' }],
cards: operationLogCards
})
if (isDonation) {
const order = ['protocol', 'collection', 'samples', 'customer', 'audits', 'attachments', 'statusChange', 'operationLog']
const byKey = new Map(values.map((section) => [section.key, section]))
return order.map((key) => byKey.get(key)).filter(Boolean)
}
return values
},
formSections: [
{ key: 'protocol', title: '协议信息', fields: [
{ key: 'source', label: '业务来源', type: 'tabs', required: true, createOnly: true, options: [
{ text: '细胞存储', value: 'cell_storage' }, { text: '科研捐赠', value: 'research_donation' }
] },
{ key: 'protocolStatus', label: '协议状态', type: 'select', required: true, source: 'cell_storage', options: CELL_PROTOCOL_STATUS_OPTIONS },
{ key: 'donationStatus', label: '协议状态', type: 'select', required: true, source: 'research_donation', options: DONATION_PROTOCOL_STATUS_OPTIONS },
// 服务端发号:表单不展示,仍参与 payload
{ key: 'protocolNo', label: '协议编号', source: 'cell_storage', hidden: true },
{ key: 'donationNo', label: '捐赠编号', source: 'research_donation', hidden: true },
{ key: 'registerDate', label: '登记日期', type: 'date', valueFormat: 'YYYYMMDD', source: 'cell_storage' },
{ key: 'donationDate', label: '捐赠日期', type: 'date', valueFormat: 'YYYYMMDD', source: 'research_donation' },
{ key: 'collectorUserId', label: '采集专员', type: 'select', optionKey: 'collectorStaff', hidden: true },
// 展示在缴费信息面板(ProtocolSamplePaymentPanel / ProtocolV1PaymentPanel)
{ key: 'protocolTotalAmount', label: '协议总金额', type: 'number', source: 'cell_storage', hidden: true },
{ key: 'remark', label: '备注', type: 'textarea' }
] },
{ key: 'collection', title: '采集信息', nested: true, fields: [
{ key: 'salesUserId', label: '市场专员', type: 'select', optionKey: 'salesStaff' },
{ key: 'regionId', label: '采集地区', type: 'select', optionKey: 'regions' },
{ key: 'hospitalId', label: '采集医院', type: 'select', optionKey: 'hospitals' },
{ key: 'regionManagerUserId', label: '地区经理', type: 'select', optionKey: 'managerStaff' },
{ key: 'medicalStaff', label: '采集医护' },
{ key: 'sampleCollectTime', label: '样本采集时间', type: 'datetime' },
{ key: 'maternalBloodCollectTime', label: '母血采集时间', type: 'datetime' }
] },
{ key: 'customer', title: '客户信息', nested: true, fields: [
{ key: 'motherName', label: '母亲姓名', type: 'customerSearch', role: 'mother', required: true },
{ key: 'motherPhone', label: '联系电话', type: 'tel', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'motherIdCard', label: '母亲身份证号', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'motherEmail', label: '母亲邮箱', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'motherBirthYm', label: '母亲出生年月', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'motherEthnic', label: '母亲民族', type: 'select', optionKey: 'ethnicities', searchable: true, ...DONATION_PARENT_FIELD_HIDE },
{ key: 'motherAddress', label: '母亲家庭住址', type: 'textarea', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'fatherName', label: '父亲姓名', type: 'customerSearch', role: 'father', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'fatherPhone', label: '父亲电话', type: 'tel', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'fatherIdCard', label: '父亲身份证号', ...DONATION_PARENT_FIELD_HIDE },
{ key: 'newbornName', label: '新生儿姓名' },
{ key: 'newbornSex', label: '新生儿性别', type: 'tabs', options: [
{ label: '男', value: '1' }, { label: '女', value: '0' }
] },
{ key: 'expectedDeliveryDate', label: '预产期', type: 'date', valueFormat: 'YYYYMMDD' },
{ key: 'newbornDeliveryTime', label: '新生儿分娩时间', type: 'datetime' },
{ key: 'newbornIdCard', label: '新生儿身份证' },
{ key: 'deliveryMode', label: '分娩方式', type: 'select', options: [
{ label: '顺产', value: 'sc' }, { label: '剖宫产', value: 'pgc' }
] },
{ key: 'birthOrder', label: '胎次', type: 'number', integer: true },
{ key: 'newbornWeightGram', label: '新生儿体重(克)', type: 'number', integer: true },
{ key: 'apgar1min', label: '新生儿评分(1分钟)', type: 'number' }
] },
// 样本及运输改由 ProtocolSampleTransportPanel 托管(对齐 Web)
{ key: 'attachments', title: '附件', nested: true, fields: [
{ key: 'protocolAttachments', label: '协议附件', type: 'upload', attachmentPolicy: 'protocol', source: 'cell_storage' },
{ key: 'consentFormUrls', label: '知情同意书', type: 'upload', attachmentPolicy: 'protocol' },
{ key: 'healthSurveyFormUrls', label: '健康调查表', type: 'upload', attachmentPolicy: 'protocol', source: 'cell_storage' },
{ key: 'infectiousDiseaseReportUrls', label: '传染病报告', type: 'upload', attachmentPolicy: 'protocol', source: 'research_donation' },
{ key: 'attachmentUrls', label: '其他附件', type: 'upload', attachmentPolicy: 'protocol', source: 'research_donation' }
] },
// 渲染时须置于样本运输 / 缴费面板之后(见 MarketModuleForm)
{ key: 'confirm', title: '提交确认', nested: true, uiOnly: true, renderLast: true, fields: [
{ key: 'confirmed', label: '我已核对以上信息', type: 'checkbox', required: true }
] }
]
}