/** * 功能:统一资源列表与详情页面的字段名称、关系、金额、状态和时间展示。 * 版本:v1.1.0 */ import dayjs from 'dayjs'; import { staffRoleLabel } from './resource-staff-relation'; import type { ResourceField, ResourceUiDefinition } from './resources'; import type { RecordPageMode, ResourceRow } from './resource-page-rules'; const aliases: Record = { identity: '唯一标识', id: 'ID', created_at: '创建时间', updated_at: '更新时间', deleted_at: '删除时间', DeletedAt: '删除时间', status: '状态', version: '版本', items: '订单明细', assignments: '分配记录', statuses: '状态记录', tracks: '运行轨迹', confirmations: '确认记录', payments: '支付记录', revisions: '修订记录', products: '合同气瓶', order: '订单', contract: '合同', wallet: '钱包', is_system: '系统内置', }; const amountKeys = new Set([ 'amount', 'unit_price', 'balance', 'withdrawal_balance', 'default_delivery_fee', 'discount_amount', 'total_amount', 'delivery_fee', 'price_amount', 'sale_amount', 'difference_amount', 'fee', ]); /** 返回资源独立页面的模式名称。 */ export function recordPageModeLabel(mode: RecordPageMode) { return { create: '新建', detail: '详情', edit: '编辑' }[mode]; } /** 返回资源独立页面的错误标题。 */ export function recordPageErrorTitle(status: '403' | '404' | 'error') { return { '403': '无法执行此操作', '404': '记录不存在', error: '页面加载失败', }[status]; } /** 返回聚合详情中的主记录。 */ export function primaryRecord(detail: ResourceRow): ResourceRow { if (detail.order && typeof detail.order === 'object') return detail.order as ResourceRow; if (detail.contract && typeof detail.contract === 'object') return detail.contract as ResourceRow; return detail; } /** 返回字段中文名称,脱敏字段沿用原字段名称。 */ export function resourceFieldLabel( definition: ResourceUiDefinition, key: string, ) { const normalized = key.endsWith('_masked') ? key.slice(0, -7) : key; return ( definition.fields.find((field) => field.key === normalized)?.label ?? aliases[key] ?? key ); } /** 将标准实体状态转换为稳定的中文展示。 */ export function recordStatusLabel(status: number) { return ( { 0: '待审核', 1: '启用', 2: '停用', 3: '已归档', 4: '已冻结' }[status] ?? `未知(${status})` ); } export function recordStatusColor(status: number) { return ( { 0: 'orange', 1: 'green', 2: 'red', 3: 'gray', 4: 'purple' }[status] ?? 'gray' ); } /** 选择关系记录的首选可读名称。 */ export function optionLabel(option: ResourceRow) { return String( option.name ?? option.title ?? option.display_name ?? option.code ?? option.username ?? option.contract_no ?? option.order_no ?? option.identity ?? '-', ); } /** 工作人员关系额外展示角色,其他关系保持原有可读名称。 */ export function relationOptionLabel(field: ResourceField, option: ResourceRow) { const label = optionLabel(option); return field.relation === '/staff_account' ? `${label}(${staffRoleLabel(option.role_code)})` : label; } function relationLabel( field: ResourceField, identity: string, relationOptions: Record, ) { const match = (relationOptions[field.relation ?? ''] ?? []).find( (option) => String(option.identity) === identity, ); if (!match) return identity; if (field.relation === '/staff_account') { return relationOptionLabel(field, match); } return field.displayRelationLabel ? optionLabel(match) : `${optionLabel(match)} · ${identity}`; } /** 格式化不依赖字段定义的通用值。 */ export function displayRawValue(key: string, value: unknown) { if (value == null || value === '') return '-'; if (typeof value === 'boolean') return value ? '是' : '否'; if (key === 'status') return recordStatusLabel(Number(value)); if ( amountKeys.has(key) || key.endsWith('_amount') || key.endsWith('_balance_after') ) { const amount = Number(value); return Number.isFinite(amount) ? `¥${(amount / 100).toFixed(2)}` : String(value); } if (key.endsWith('_at') || ['created_at', 'updated_at'].includes(key)) { const date = dayjs(String(value)); return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : String(value); } if ( ['deleted_at', 'DeletedAt'].includes(key) && typeof value === 'object' && value && 'Time' in value ) { const date = dayjs(String((value as { Time?: unknown }).Time ?? '')); return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : '-'; } if (typeof value === 'object') return JSON.stringify(value, null, 2); return String(value); } /** 按字段选项和关系配置格式化值。 */ export function displayResourceField( field: ResourceField, row: ResourceRow, relationOptions: Record, fieldOptions: Record< string, Array<{ label: string; value: string | number }> > = {}, ) { const value = row[field.key] ?? row[`${field.key}_masked`]; if (value == null || value === '') return field.emptyText ?? '-'; const hasOptionSource = Object.prototype.hasOwnProperty.call(fieldOptions, field.key) || Boolean(field.options); const options = fieldOptions[field.key] ?? field.options; const option = options?.find((item) => String(item.value) === String(value)); if (option) return option.label; if (hasOptionSource && field.unknownValueLabel) { return `${field.unknownValueLabel}(${String(value)})`; } if (field.displayPrecision != null) { const numericValue = Number(value); return Number.isFinite(numericValue) ? numericValue.toFixed(field.displayPrecision) : String(value); } if (field.type === 'identity' && typeof value === 'string') { return relationLabel(field, value, relationOptions); } if (field.type === 'identity-list' && Array.isArray(value)) { return value .map((item) => relationLabel(field, String(item), relationOptions)) .join('、'); } return displayRawValue(field.key, value); } /** 判断软删除结构是否为空。 */ export function isEmptyDeletedAt(value: unknown) { if (value == null || value === '') return true; return Boolean( typeof value === 'object' && value && 'Valid' in value && !(value as { Valid?: boolean }).Valid, ); }