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,
|
signal?: AbortSignal,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const pathValues = (input.path || {}) as Record<string, unknown>;
|
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);
|
const url = new URL(route, window.location.origin);
|
||||||
appendQuery(url, ((input.query || {}) as Record<string, unknown>));
|
appendQuery(url, ((input.query || {}) as Record<string, unknown>));
|
||||||
route = `${url.pathname}${url.search}`;
|
route = `${url.pathname}${url.search}`;
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ export const tokenStore = {
|
|||||||
get: () => currentTokens,
|
get: () => currentTokens,
|
||||||
set(tokens: TokenPair | null) {
|
set(tokens: TokenPair | null) {
|
||||||
currentTokens = tokens;
|
currentTokens = tokens;
|
||||||
if (tokens) localStorage.setItem(storageKey, JSON.stringify(tokens));
|
if (tokens) {
|
||||||
else localStorage.removeItem(storageKey);
|
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'));
|
window.dispatchEvent(new CustomEvent('platform-auth-change'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function BusinessTable({ payload, loading, onSelect }: { payload: unknown
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={rows}
|
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) }}
|
rowSelection={{ type: 'radio', onChange: (_, selected) => onSelect(selected[0] || null) }}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `共 ${total} 条` }}
|
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `共 ${total} 条` }}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function ResponseView({ payload }: { payload: unknown }) {
|
|||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
size="small"
|
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}
|
dataSource={rows}
|
||||||
scroll={{ x: 'max-content' }}
|
scroll={{ x: 'max-content' }}
|
||||||
pagination={{ pageSize: 20, showSizeChanger: true }}
|
pagination={{ pageSize: 20, showSizeChanger: true }}
|
||||||
|
|||||||
@@ -75,18 +75,21 @@ function toTree(nodes: BankNode[]): DataNode[] {
|
|||||||
const key = node.parentId || null;
|
const key = node.parentId || null;
|
||||||
children.set(key, [...(children.get(key) || []), node]);
|
children.set(key, [...(children.get(key) || []), node]);
|
||||||
}
|
}
|
||||||
const build = (parentId: string | null): DataNode[] => (children.get(parentId) || [])
|
const build = (parentId: string | null, depth = 0): DataNode[] => {
|
||||||
.sort((a, b) => Number(a.sortOrder) - Number(b.sortOrder))
|
if (depth > 50) return [];
|
||||||
.map((node) => ({
|
return (children.get(parentId) || [])
|
||||||
key: node.id,
|
.sort((a, b) => Number(a.sortOrder) - Number(b.sortOrder))
|
||||||
title: <Flex justify="space-between" gap={8}><span>{node.name}</span><Badge count={Number(node.questionCount)} showZero color="#b9c4cf" /></Flex>,
|
.map((node) => ({
|
||||||
children: build(node.id),
|
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);
|
return build(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseJson(value: string | undefined, fallback: unknown) {
|
function parseJson(value: unknown, fallback: unknown) {
|
||||||
if (!value?.trim()) return fallback;
|
if (typeof value !== 'string' || !value.trim()) return fallback;
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +200,7 @@ export function QuestionBankPage() {
|
|||||||
const saveBatchNodes = async () => {
|
const saveBatchNodes = async () => {
|
||||||
const values = await batchForm.validateFields();
|
const values = await batchForm.validateFields();
|
||||||
const names = String(values.names).split(/\n|,/).map((item) => item.trim()).filter(Boolean);
|
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 个节点');
|
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 } });
|
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();
|
message.success(`已创建 ${names.length} 个节点`); setBatchNodeOpen(false); batchForm.resetFields(); await loadNodes();
|
||||||
|
|||||||
Reference in New Issue
Block a user