219 lines
6.5 KiB
TypeScript
219 lines
6.5 KiB
TypeScript
/**
|
||
* 功能:统一资源列表与详情页面的字段名称、关系、金额、状态和时间展示。
|
||
* 版本: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<string, string> = {
|
||
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<string, ResourceRow[]>,
|
||
) {
|
||
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<string, ResourceRow[]>,
|
||
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,
|
||
);
|
||
}
|