feat: 标准资源使用独立页面并优化详情布局
This commit is contained in:
191
frontend/platform_admin/src/api/resource-display.ts
Normal file
191
frontend/platform_admin/src/api/resource-display.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 功能:统一资源列表与详情页面的字段名称、关系、金额、状态和时间展示。
|
||||
* 版本:v1.1.0
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
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 ??
|
||||
'-',
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
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[]>,
|
||||
) {
|
||||
const value = row[field.key] ?? row[`${field.key}_masked`];
|
||||
if (value == null || value === '') return field.emptyText ?? '-';
|
||||
const option = field.options?.find(
|
||||
(item) => String(item.value) === String(value),
|
||||
);
|
||||
if (option) return option.label;
|
||||
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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user