fix: 修复前端界面边界条件问题
- rowKey 使用 ?? 替代 ||,避免合法值 0 被当作 falsy - parseJson 增加非字符串类型防御检查 - toTree 增加递归深度限制防止循环引用栈溢出 - saveBatchNodes 增加空名称数组检查 - localStorage.setItem 捕获 QuotaExceededError - 路径参数缺失时显式抛出错误而非静默替换
This commit is contained in:
@@ -64,7 +64,11 @@ export async function apiRequest<T = unknown>(
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
const pathValues = (input.path || {}) as Record<string, unknown>;
|
||||
let route = operation.path.replace(/\{([^}]+)\}/g, (_, name: string) => encodeURIComponent(String(pathValues[name] ?? '')));
|
||||
let route = operation.path.replace(/\{([^}]+)\}/g, (_, name: string) => {
|
||||
const value = pathValues[name];
|
||||
if (value === undefined || value === null) throw new Error(`缺少必填路径参数:${name}`);
|
||||
return encodeURIComponent(String(value));
|
||||
});
|
||||
const url = new URL(route, window.location.origin);
|
||||
appendQuery(url, ((input.query || {}) as Record<string, unknown>));
|
||||
route = `${url.pathname}${url.search}`;
|
||||
|
||||
@@ -21,8 +21,11 @@ export const tokenStore = {
|
||||
get: () => currentTokens,
|
||||
set(tokens: TokenPair | null) {
|
||||
currentTokens = tokens;
|
||||
if (tokens) localStorage.setItem(storageKey, JSON.stringify(tokens));
|
||||
else localStorage.removeItem(storageKey);
|
||||
if (tokens) {
|
||||
try { localStorage.setItem(storageKey, JSON.stringify(tokens)); } catch { /* quota exceeded — in-memory copy still valid */ }
|
||||
} else {
|
||||
localStorage.removeItem(storageKey);
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('platform-auth-change'));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export function BusinessTable({ payload, loading, onSelect }: { payload: unknown
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={rows}
|
||||
rowKey={(row, index) => String(row.id || row.tenantId || row.code || index)}
|
||||
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} 条` }}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function ResponseView({ payload }: { payload: unknown }) {
|
||||
return (
|
||||
<Table
|
||||
size="small"
|
||||
rowKey={(row, index) => String(row.id || row.code || row.userId || index)}
|
||||
rowKey={(row, index) => String(row.id ?? row.code ?? row.userId ?? index)}
|
||||
dataSource={rows}
|
||||
scroll={{ x: 'max-content' }}
|
||||
pagination={{ pageSize: 20, showSizeChanger: true }}
|
||||
|
||||
@@ -75,18 +75,21 @@ function toTree(nodes: BankNode[]): DataNode[] {
|
||||
const key = node.parentId || null;
|
||||
children.set(key, [...(children.get(key) || []), node]);
|
||||
}
|
||||
const build = (parentId: string | null): DataNode[] => (children.get(parentId) || [])
|
||||
.sort((a, b) => Number(a.sortOrder) - Number(b.sortOrder))
|
||||
.map((node) => ({
|
||||
key: node.id,
|
||||
title: <Flex justify="space-between" gap={8}><span>{node.name}</span><Badge count={Number(node.questionCount)} showZero color="#b9c4cf" /></Flex>,
|
||||
children: build(node.id),
|
||||
}));
|
||||
const build = (parentId: string | null, depth = 0): DataNode[] => {
|
||||
if (depth > 50) return [];
|
||||
return (children.get(parentId) || [])
|
||||
.sort((a, b) => Number(a.sortOrder) - Number(b.sortOrder))
|
||||
.map((node) => ({
|
||||
key: node.id,
|
||||
title: <Flex justify="space-between" gap={8}><span>{node.name}</span><Badge count={Number(node.questionCount)} showZero color="#b9c4cf" /></Flex>,
|
||||
children: build(node.id, depth + 1),
|
||||
}));
|
||||
};
|
||||
return build(null);
|
||||
}
|
||||
|
||||
function parseJson(value: string | undefined, fallback: unknown) {
|
||||
if (!value?.trim()) return fallback;
|
||||
function parseJson(value: unknown, fallback: unknown) {
|
||||
if (typeof value !== 'string' || !value.trim()) return fallback;
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
@@ -197,6 +200,7 @@ export function QuestionBankPage() {
|
||||
const saveBatchNodes = async () => {
|
||||
const values = await batchForm.validateFields();
|
||||
const names = String(values.names).split(/\n|,/).map((item) => item.trim()).filter(Boolean);
|
||||
if (!names.length) return message.error('请输入至少一个节点名称');
|
||||
if (names.length > 100) return message.error('一次最多创建 100 个节点');
|
||||
await platformRequest('POST', '/api/platform-admin/question-banks/nodes/batch', { body: { questionBankId: bankId, parentId: values.parentId || null, nodeType: values.nodeType, names } });
|
||||
message.success(`已创建 ${names.length} 个节点`); setBatchNodeOpen(false); batchForm.resetFields(); await loadNodes();
|
||||
|
||||
Reference in New Issue
Block a user