Files
platforms/frontend/platform_admin/src/api/resource-display.ts
czl231 e375e9851b 修复:平台后台显示用户地址和坐标
仅在已授权的用户地址列表与详情接口恢复真实地址、经度和纬度。前端增加六位小数、未填写提示及编辑回填,并同步安全规范、项目文档和操作日志。
2026-08-11 20:49:28 +08:00

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