Files
tiku-backend.net/Tiku.PlatformAdmin.Web/src/components/BusinessTable.tsx
wangziqi 589ecd06f0 fix: 修复前端界面边界条件问题
- rowKey 使用 ?? 替代 ||,避免合法值 0 被当作 falsy
- parseJson 增加非字符串类型防御检查
- toTree 增加递归深度限制防止循环引用栈溢出
- saveBatchNodes 增加空名称数组检查
- localStorage.setItem 捕获 QuotaExceededError
- 路径参数缺失时显式抛出错误而非静默替换
2026-07-30 17:08:03 +08:00

46 lines
3.0 KiB
TypeScript

import { Table, Tag, Typography } from 'antd';
const labels: Record<string, string> = {
id: '编号', tenantId: '租户', name: '名称', code: '编码', slug: '短编码', host: '域名', status: '状态', billingStatus: '账务状态',
createdAt: '创建时间', updatedAt: '更新时间', expiresAt: '到期时间', dueAt: '到期时间', amountCents: '金额', totalAmountCents: '金额',
balanceCents: '余额', phone: '手机号', email: '邮箱', ownerName: '负责人', provider: '服务商', enabled: '启用', appName: '应用名称',
appCode: '应用代码', title: '标题', summary: '摘要', action: '动作', targetType: '目标类型', targetId: '目标编号', severity: '级别',
version: '版本', billingCycle: '计费周期', effectiveAt: '生效时间', questionCount: '题目数', nodeType: '节点类型', sortOrder: '排序',
};
function isScalar(value: unknown) { return value == null || ['string', 'number', 'boolean'].includes(typeof value); }
function cell(value: unknown, key: string) {
if (value === null || value === undefined || value === '') return '-';
if (typeof value === 'boolean') return <Tag color={value ? 'green' : 'default'}>{value ? '是' : '否'}</Tag>;
if (/status|severity/i.test(key)) return <Tag color={String(value).toLowerCase().includes('active') || String(value).includes('正常') ? 'green' : 'blue'}>{String(value)}</Tag>;
if (/Cents$/.test(key) && typeof value === 'number') return `¥${(value / 100).toLocaleString('zh-CN', { minimumFractionDigits: 2 })}`;
return <Typography.Text ellipsis={{ tooltip: String(value) }}>{String(value)}</Typography.Text>;
}
export function rowsFromPayload(payload: unknown): Record<string, unknown>[] {
if (Array.isArray(payload)) return payload.filter((item) => item && typeof item === 'object') as Record<string, unknown>[];
if (!payload || typeof payload !== 'object') return [];
const object = payload as Record<string, unknown>;
for (const key of ['items', 'data', 'results', 'tenants', 'domains', 'staff', 'alerts', 'logs']) {
if (Array.isArray(object[key])) return object[key] as Record<string, unknown>[];
}
return [object];
}
export function BusinessTable({ payload, loading, onSelect }: { payload: unknown; loading: boolean; onSelect(record: Record<string, unknown> | null): void }) {
const rows = rowsFromPayload(payload);
const keys = [...new Set(rows.flatMap((row) => Object.keys(row).filter((key) => isScalar(row[key]))))].slice(0, 9);
return (
<Table
loading={loading}
dataSource={rows}
rowKey={(row, index) => String(row.id ?? row.tenantId ?? row.code ?? index)}
rowSelection={{ type: 'radio', onChange: (_, selected) => onSelect(selected[0] || null) }}
scroll={{ x: 'max-content' }}
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `${total}` }}
locale={{ emptyText: '暂无业务数据' }}
columns={keys.map((key) => ({ title: labels[key] || key, dataIndex: key, key, ellipsis: true, render: (value) => cell(value, key) }))}
/>
);
}