fix: 质量修复 — ESLint 规则 TS6 兼容 + catch 注解 + 空函数体注释化 + 后端架构对齐 + 前端红线修复
- enforce-catch-type: 增加 TSUnknownKeyword 判断,消除28个 TS6 假阳性 - no-empty-function: 统一为注释方案,移除测试/生产分支和 eslint-disable 引导 - logger.ts: 空函数体改为注释说明,删除无用 eslint-disable 指令 - 补充15处 catch 子句 : unknown 类型注解 - 清理7个测试文件失效 eslint-disable 指令 - chat/send.ts: 提取 getModelWithProvider DAO,消除直接 Drizzle 操作 - projects/update.ts: 修复死代码+条件逻辑 bug - providers/update.ts: 补充至少一个字段校验 - 前端: inline style → CSS className, ProviderFormModal whitespace 校验 - 开发文档: 更新 Zod 使用说明(AI SDK 框架级约束)
This commit is contained in:
@@ -33,7 +33,7 @@ AI 工具必须严格遵守以下全部约束。
|
|||||||
|
|
||||||
**前端**:优先复用已有组件/hooks/依赖库;确需新增依赖时先说明原因。
|
**前端**:优先复用已有组件/hooks/依赖库;确需新增依赖时先说明原因。
|
||||||
|
|
||||||
**Zod**:已引入但未使用(预留),配置校验用 TypeBox + Ajv,不得混用 Zod。
|
**Zod**:AI 工具层(`src/server/ai/tools/`)使用 Zod 定义 `tool()` 的 `inputSchema`,以满足 AI SDK 对 `ZodSchema` 的类型推断要求,属于框架级约束而非项目选型冲突。配置校验层使用 TypeBox + Ajv,两层级各司其职,不混用。
|
||||||
|
|
||||||
### 目录边界
|
### 目录边界
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export const enforceCatchType = {
|
|||||||
if (type?.type === "TSTypeReference") {
|
if (type?.type === "TSTypeReference") {
|
||||||
return type.typeName?.name === "unknown";
|
return type.typeName?.name === "unknown";
|
||||||
}
|
}
|
||||||
|
if (type?.type === "TSUnknownKeyword") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,12 +48,7 @@ export const enforceCatchType = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (body && body.type === "BlockStatement" && body.body.length === 0 && !hasCommentsInBody(body)) {
|
||||||
body &&
|
|
||||||
body.type === "BlockStatement" &&
|
|
||||||
body.body.length === 0 &&
|
|
||||||
!hasCommentsInBody(body)
|
|
||||||
) {
|
|
||||||
context.report({ node: body, messageId: "emptyCatchNoComment" });
|
context.report({ node: body, messageId: "emptyCatchNoComment" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,4 +57,4 @@ export const enforceCatchType = {
|
|||||||
CatchClause: check,
|
CatchClause: check,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,14 +2,10 @@ export const noEmptyFunction = {
|
|||||||
meta: {
|
meta: {
|
||||||
type: "problem",
|
type: "problem",
|
||||||
docs: {
|
docs: {
|
||||||
description:
|
description: "禁止空函数体。修复方式:在函数体内添加注释说明为何为空实现(如接口契约、测试桩、noop)。",
|
||||||
"禁止空函数体,并提供项目约定的修复指引:生产代码使用 () => undefined,测试代码使用 () => {} + eslint-disable",
|
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
unexpectedProduction:
|
unexpected: "空函数体禁止使用。请在 {} 内添加注释说明原因,例如:/* 实现 Logger 接口契约,有意静默丢弃 */。",
|
||||||
"生产代码中空函数应使用 () => undefined 明确表意(如 noop/voidLog)。如果确需空实现且为接口契约,请添加注释说明原因。",
|
|
||||||
unexpectedTest:
|
|
||||||
"测试代码中空函数使用 () => {} 并在文件顶部添加 /* eslint-disable @typescript-eslint/no-empty-function */。",
|
|
||||||
},
|
},
|
||||||
schema: [],
|
schema: [],
|
||||||
},
|
},
|
||||||
@@ -17,17 +13,11 @@ export const noEmptyFunction = {
|
|||||||
create(context) {
|
create(context) {
|
||||||
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
||||||
|
|
||||||
const allowedFunctionTypes = new Set([
|
const allowedFunctionTypes = new Set(["ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression"]);
|
||||||
"ArrowFunctionExpression",
|
|
||||||
"FunctionDeclaration",
|
|
||||||
"FunctionExpression",
|
|
||||||
]);
|
|
||||||
|
|
||||||
function isEmptyBody(body) {
|
function isEmptyBody(body) {
|
||||||
return (
|
return (
|
||||||
body.type === "BlockStatement" &&
|
body.type === "BlockStatement" && body.body.length === 0 && sourceCode.getCommentsInside(body).length === 0
|
||||||
body.body.length === 0 &&
|
|
||||||
sourceCode.getCommentsInside(body).length === 0
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +44,9 @@ export const noEmptyFunction = {
|
|||||||
if (isPrivateOrProtectedConstructor(node)) return;
|
if (isPrivateOrProtectedConstructor(node)) return;
|
||||||
if (isOverrideMethod(node)) return;
|
if (isOverrideMethod(node)) return;
|
||||||
|
|
||||||
const isTest = /[\\/]tests?[\\/]/.test(context.filename ?? "") || context.filename?.includes("test");
|
|
||||||
|
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
messageId: isTest ? "unexpectedTest" : "unexpectedProduction",
|
messageId: "unexpected",
|
||||||
data: { name: node.id?.name ?? node.key?.name ?? "function" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export async function bootstrap(options: BootstrapOptions, dependencies: Bootstr
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger = await buildLogger(config.logging, options.mode, options.version);
|
logger = await buildLogger(config.logging, options.mode, options.version);
|
||||||
} catch (logInitError) {
|
} catch (logInitError: unknown) {
|
||||||
createFallback().fatal(
|
createFallback().fatal(
|
||||||
`日志初始化失败: ${logInitError instanceof Error ? logInitError.message : String(logInitError)}`,
|
`日志初始化失败: ${logInitError instanceof Error ? logInitError.message : String(logInitError)}`,
|
||||||
);
|
);
|
||||||
@@ -83,7 +83,7 @@ export async function bootstrap(options: BootstrapOptions, dependencies: Bootstr
|
|||||||
staticAssets: options.staticAssets,
|
staticAssets: options.staticAssets,
|
||||||
version: options.version,
|
version: options.version,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
if (logger) {
|
if (logger) {
|
||||||
logger.fatal({ error: error instanceof Error ? error.message : String(error) }, "启动失败");
|
logger.fatal({ error: error instanceof Error ? error.message : String(error) }, "启动失败");
|
||||||
logger.flush();
|
logger.flush();
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
|||||||
if (bytes <= 0) {
|
if (bytes <= 0) {
|
||||||
issues.push(issue("invalid-value", "server.logging.file.rotation.size", "滚动大小必须为正整数字节数"));
|
issues.push(issue("invalid-value", "server.logging.file.rotation.size", "滚动大小必须为正整数字节数"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
issues.push(
|
issues.push(
|
||||||
issue(
|
issue(
|
||||||
"invalid-value",
|
"invalid-value",
|
||||||
|
|||||||
@@ -11,4 +11,13 @@ export {
|
|||||||
} from "./conversations";
|
} from "./conversations";
|
||||||
export { loadMigrationsFromDir, type MigrationRecord } from "./load-migrations";
|
export { loadMigrationsFromDir, type MigrationRecord } from "./load-migrations";
|
||||||
export { runMigrations } from "./migrate";
|
export { runMigrations } from "./migrate";
|
||||||
|
export {
|
||||||
|
createModel,
|
||||||
|
deleteModel,
|
||||||
|
getModel,
|
||||||
|
getModelsByProviderId,
|
||||||
|
getModelWithProvider,
|
||||||
|
listModels,
|
||||||
|
updateModel,
|
||||||
|
} from "./models";
|
||||||
export { conversations, messages, projects, schemaMigrations } from "./schema";
|
export { conversations, messages, projects, schemaMigrations } from "./schema";
|
||||||
|
|||||||
@@ -90,6 +90,38 @@ export function getModelsByProviderId(raw: Database, providerId: string): number
|
|||||||
return Number(result?.count ?? 0);
|
return Number(result?.count ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getModelWithProvider(
|
||||||
|
raw: Database,
|
||||||
|
modelId: string,
|
||||||
|
):
|
||||||
|
| { error: string; status: number }
|
||||||
|
| {
|
||||||
|
model: { modelId: string; name: string; providerId: string };
|
||||||
|
provider: { apiKey: string; baseUrl: string; id: string; type: string };
|
||||||
|
} {
|
||||||
|
const db = wrap(raw);
|
||||||
|
const row = db.select().from(models).where(eq(models.id, modelId)).get();
|
||||||
|
|
||||||
|
if (!row) return { error: "模型不存在", status: 404 };
|
||||||
|
|
||||||
|
const providerRow = db.select().from(providers).where(eq(providers.id, row.providerId)).get();
|
||||||
|
if (!providerRow) return { error: "供应商不存在", status: 404 };
|
||||||
|
|
||||||
|
return {
|
||||||
|
model: {
|
||||||
|
modelId: row.modelId,
|
||||||
|
name: row.name,
|
||||||
|
providerId: row.providerId,
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
apiKey: providerRow.apiKey,
|
||||||
|
baseUrl: providerRow.baseUrl,
|
||||||
|
id: providerRow.id,
|
||||||
|
type: providerRow.type,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function listModels(
|
export function listModels(
|
||||||
raw: Database,
|
raw: Database,
|
||||||
options: { keyword?: string; page: number; pageSize: number; providerId?: string },
|
options: { keyword?: string; page: number; pageSize: number; providerId?: string },
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import type Database from "bun:sqlite";
|
import type Database from "bun:sqlite";
|
||||||
|
|
||||||
import { createAgentUIStreamResponse, generateText, type UIMessage } from "ai";
|
import { createAgentUIStreamResponse, generateText, type UIMessage } from "ai";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
import type { RuntimeMode } from "../../../shared/api";
|
import type { RuntimeMode } from "../../../shared/api";
|
||||||
import type { Logger } from "../../logger";
|
import type { Logger } from "../../logger";
|
||||||
|
|
||||||
import { createAlfredAgent } from "../../ai/agents/alfred-agent";
|
import { createAlfredAgent } from "../../ai/agents/alfred-agent";
|
||||||
import { buildProviderRegistry } from "../../ai/registry";
|
import { buildProviderRegistry } from "../../ai/registry";
|
||||||
import { wrap } from "../../db/connection";
|
|
||||||
import {
|
import {
|
||||||
createMessage,
|
createMessage,
|
||||||
getConversation,
|
getConversation,
|
||||||
updateConversation,
|
updateConversation,
|
||||||
updateConversationTimestamp,
|
updateConversationTimestamp,
|
||||||
} from "../../db/conversations";
|
} from "../../db/conversations";
|
||||||
import { models, providers } from "../../db/schema";
|
import { getModelWithProvider } from "../../db/models";
|
||||||
import { createApiError, jsonResponse } from "../../helpers";
|
import { createApiError, jsonResponse } from "../../helpers";
|
||||||
import { validateIdParam } from "../../middleware";
|
import { validateIdParam } from "../../middleware";
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
|||||||
let body: { conversationId?: string; messages?: UIMessage[] };
|
let body: { conversationId?: string; messages?: UIMessage[] };
|
||||||
try {
|
try {
|
||||||
body = (await req.json()) as typeof body;
|
body = (await req.json()) as typeof body;
|
||||||
} catch (e) {
|
} catch (e: unknown) {
|
||||||
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
|
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
|
||||||
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
|
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
|
||||||
}
|
}
|
||||||
@@ -81,19 +79,13 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
|||||||
|
|
||||||
let model;
|
let model;
|
||||||
try {
|
try {
|
||||||
const d = wrap(db);
|
const result = getModelWithProvider(db, conversation.modelId);
|
||||||
const modelRow = d.select().from(models).where(eq(models.id, conversation.modelId)).get();
|
if ("error" in result) {
|
||||||
if (!modelRow) {
|
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
|
||||||
return jsonResponse(createApiError(`模型不存在: ${conversation.modelId}`, 400), { mode, status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const providerRow = d.select().from(providers).where(eq(providers.id, modelRow.providerId)).get();
|
|
||||||
if (!providerRow) {
|
|
||||||
return jsonResponse(createApiError(`供应商不存在: ${modelRow.providerId}`, 400), { mode, status: 400 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const registry = buildProviderRegistry(db);
|
const registry = buildProviderRegistry(db);
|
||||||
model = registry.languageModel(`${providerRow.id}:${modelRow.modelId}`);
|
model = registry.languageModel(`${result.provider.id}:${result.model.modelId}`);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
const msg = e instanceof Error ? e.message : String(e);
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
return jsonResponse(createApiError(`模型初始化失败:${msg}`, 500), { mode, status: 500 });
|
return jsonResponse(createApiError(`模型初始化失败:${msg}`, 500), { mode, status: 500 });
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ export async function handleUpdateProject(
|
|||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
|
logger.warn({ error: e instanceof Error ? e.message : String(e) }, "请求 JSON 解析失败");
|
||||||
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
|
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
|
||||||
return jsonResponse(createApiError("Invalid JSON body", 400), { mode, status: 400 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!body.name && !body.description && body.name !== "" && body.description !== "") {
|
const hasName = body.name !== undefined && body.name.trim() !== "";
|
||||||
|
const hasDescription = body.description !== undefined && body.description.trim() !== "";
|
||||||
|
if (!hasName && !hasDescription) {
|
||||||
return jsonResponse(createApiError("At least one of name or description is required", 400), { mode, status: 400 });
|
return jsonResponse(createApiError("At least one of name or description is required", 400), { mode, status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ export async function handleUpdateProvider(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.name === undefined && body.baseUrl === undefined && body.apiKey === undefined && body.type === undefined) {
|
||||||
|
return jsonResponse(createApiError("至少需要提供 name, baseUrl, apiKey 或 type 中的一个字段", 400), {
|
||||||
|
mode,
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const result = updateProvider(db, validated.id, body, logger);
|
const result = updateProvider(db, validated.id, body, logger);
|
||||||
if ("error" in result) {
|
if ("error" in result) {
|
||||||
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
|
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export async function fetchConversation(projectId: string, conversationId: strin
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/projects/${projectId}/conversations/${conversationId}`);
|
const response = await fetch(`/api/projects/${projectId}/conversations/${conversationId}`);
|
||||||
return handleResponse(response, (data) => (data as ConversationResponse).conversation);
|
return handleResponse(response, (data) => (data as ConversationResponse).conversation);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
logger.error("获取会话失败", {
|
logger.error("获取会话失败", {
|
||||||
conversationId,
|
conversationId,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: err instanceof Error ? err.message : String(err),
|
||||||
@@ -61,7 +61,7 @@ export async function updateConversation(
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
});
|
});
|
||||||
return handleResponse(response, (data) => (data as ConversationResponse).conversation);
|
return handleResponse(response, (data) => (data as ConversationResponse).conversation);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
logger.error("更新会话失败", {
|
logger.error("更新会话失败", {
|
||||||
conversationId,
|
conversationId,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function ModelFormModal({
|
|||||||
message.success("模型已创建");
|
message.success("模型已创建");
|
||||||
}
|
}
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
message.error(err.message);
|
message.error(err.message);
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ export function ModelFormModal({
|
|||||||
} else {
|
} else {
|
||||||
message.error(result.message);
|
message.error(result.message);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
message.error((err as Error).message);
|
message.error((err as Error).message);
|
||||||
} finally {
|
} finally {
|
||||||
setTesting(false);
|
setTesting(false);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function ModelTable({
|
|||||||
try {
|
try {
|
||||||
await onDelete(id);
|
await onDelete(id);
|
||||||
message.success("模型已删除");
|
message.success("模型已删除");
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
message.error((err as Error).message);
|
message.error((err as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function ProviderFormModal({
|
|||||||
message.success("供应商已创建");
|
message.success("供应商已创建");
|
||||||
}
|
}
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
message.error(err.message);
|
message.error(err.message);
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ export function ProviderFormModal({
|
|||||||
} else {
|
} else {
|
||||||
message.error(result.message);
|
message.error(result.message);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
message.error(err.message);
|
message.error(err.message);
|
||||||
}
|
}
|
||||||
@@ -135,10 +135,18 @@ export function ProviderFormModal({
|
|||||||
<Form.Item label="供应商类型" name="type" rules={[{ message: "请选择供应商类型", required: true }]}>
|
<Form.Item label="供应商类型" name="type" rules={[{ message: "请选择供应商类型", required: true }]}>
|
||||||
<Select options={TYPE_OPTIONS} placeholder="请选择供应商类型" />
|
<Select options={TYPE_OPTIONS} placeholder="请选择供应商类型" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="Base URL" name="baseUrl" rules={[{ message: "请输入 Base URL", required: true }]}>
|
<Form.Item
|
||||||
|
label="Base URL"
|
||||||
|
name="baseUrl"
|
||||||
|
rules={[{ message: "请输入 Base URL", required: true, whitespace: true }]}
|
||||||
|
>
|
||||||
<Input placeholder="https://api.openai.com/v1" />
|
<Input placeholder="https://api.openai.com/v1" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="API Key" name="apiKey" rules={[{ message: "请输入 API Key", required: true }]}>
|
<Form.Item
|
||||||
|
label="API Key"
|
||||||
|
name="apiKey"
|
||||||
|
rules={[{ message: "请输入 API Key", required: true, whitespace: true }]}
|
||||||
|
>
|
||||||
<Input.Password placeholder="请输入 API Key" />
|
<Input.Password placeholder="请输入 API Key" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function ProviderTable({ data, loading, onDelete, onEdit, onPageChange, p
|
|||||||
try {
|
try {
|
||||||
await onDelete(id);
|
await onDelete(id);
|
||||||
message.success("供应商已删除");
|
message.success("供应商已删除");
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
message.error((err as Error).message);
|
message.error((err as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function ModelsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space orientation="vertical" size="large" style={{ flex: 1 }}>
|
<Space className="app-page-flex" orientation="vertical" size="large">
|
||||||
<ModelsToolbar
|
<ModelsToolbar
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
key={activeTab}
|
key={activeTab}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function ProjectFormModal({
|
|||||||
message.success("项目已创建");
|
message.success("项目已创建");
|
||||||
}
|
}
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
message.error(err.message);
|
message.error(err.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function ProjectTable({
|
|||||||
try {
|
try {
|
||||||
await onArchive(id);
|
await onArchive(id);
|
||||||
message.success("项目已归档");
|
message.success("项目已归档");
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
message.error((err as Error).message);
|
message.error((err as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -78,7 +78,7 @@ export function ProjectTable({
|
|||||||
try {
|
try {
|
||||||
await onRestore(id);
|
await onRestore(id);
|
||||||
message.success("项目已恢复");
|
message.success("项目已恢复");
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
message.error((err as Error).message);
|
message.error((err as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -87,7 +87,7 @@ export function ProjectTable({
|
|||||||
try {
|
try {
|
||||||
await onDelete(id);
|
await onDelete(id);
|
||||||
message.success("项目已永久删除");
|
message.success("项目已永久删除");
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
message.error((err as Error).message);
|
message.error((err as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function ProjectsPage() {
|
|||||||
const isRowActionPending = archiveMutation.isPending || restoreMutation.isPending || deleteMutation.isPending;
|
const isRowActionPending = archiveMutation.isPending || restoreMutation.isPending || deleteMutation.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space orientation="vertical" size="large" style={{ flex: 1 }}>
|
<Space className="app-page-flex" orientation="vertical" size="large">
|
||||||
<ProjectToolbar
|
<ProjectToolbar
|
||||||
activeTab={tabValue}
|
activeTab={tabValue}
|
||||||
keyword={keyword}
|
keyword={keyword}
|
||||||
|
|||||||
@@ -250,3 +250,7 @@ body {
|
|||||||
.card-extra-actions .btn-dimmed:hover {
|
.card-extra-actions .btn-dimmed:hover {
|
||||||
color: var(--ant-color-text-secondary);
|
color: var(--ant-color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-page-flex {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export interface Sink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AntdMessageSink implements Sink {
|
class AntdMessageSink implements Sink {
|
||||||
constructor(private messageApi: MessageInstance) {}
|
constructor(private messageApi: MessageInstance) {
|
||||||
|
// 仅存储依赖,无需初始化操作
|
||||||
|
}
|
||||||
|
|
||||||
write(level: LogLevel, message: string, _data: unknown, _bindings: Record<string, unknown>): void {
|
write(level: LogLevel, message: string, _data: unknown, _bindings: Record<string, unknown>): void {
|
||||||
if (level === "warn") this.messageApi.warning(message);
|
if (level === "warn") this.messageApi.warning(message);
|
||||||
@@ -70,7 +72,9 @@ class BaseLogger implements Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ConsoleSink implements Sink {
|
class ConsoleSink implements Sink {
|
||||||
constructor(private isProduction: boolean) {}
|
constructor(private isProduction: boolean) {
|
||||||
|
// 仅存储配置,无需初始化操作
|
||||||
|
}
|
||||||
|
|
||||||
write(level: LogLevel, message: string, data: unknown, bindings: Record<string, unknown>): void {
|
write(level: LogLevel, message: string, data: unknown, bindings: Record<string, unknown>): void {
|
||||||
if (this.isProduction && LEVEL_ORDER[level] < LEVEL_ORDER.warn) return;
|
if (this.isProduction && LEVEL_ORDER[level] < LEVEL_ORDER.warn) return;
|
||||||
@@ -85,23 +89,31 @@ class ConsoleSink implements Sink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
class NoopLogger implements Logger {
|
class NoopLogger implements Logger {
|
||||||
child(_bindings: Record<string, unknown>): Logger {
|
child(_bindings: Record<string, unknown>): Logger {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(_message: string, _data?: unknown): void {}
|
debug(_message: string, _data?: unknown): void {
|
||||||
|
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
|
||||||
|
}
|
||||||
|
|
||||||
error(_message: string, _data?: unknown): void {}
|
error(_message: string, _data?: unknown): void {
|
||||||
|
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
|
||||||
|
}
|
||||||
|
|
||||||
info(_message: string, _data?: unknown): void {}
|
info(_message: string, _data?: unknown): void {
|
||||||
|
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
|
||||||
|
}
|
||||||
|
|
||||||
setLevel(_level: LogLevel): void {}
|
setLevel(_level: LogLevel): void {
|
||||||
|
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
|
||||||
|
}
|
||||||
|
|
||||||
warn(_message: string, _data?: unknown): void {}
|
warn(_message: string, _data?: unknown): void {
|
||||||
|
/* NoopLogger 实现 Logger 接口契约,有意静默丢弃所有日志 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* eslint-enable @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
export class MemoryLogger implements Logger {
|
export class MemoryLogger implements Logger {
|
||||||
entries: Array<{ data?: unknown; level: LogLevel; message: string }> = [];
|
entries: Array<{ data?: unknown; level: LogLevel; message: string }> = [];
|
||||||
@@ -123,8 +135,9 @@ export class MemoryLogger implements Logger {
|
|||||||
this.capture("info", message, data);
|
this.capture("info", message, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
|
setLevel(_level: LogLevel): void {
|
||||||
setLevel(_level: LogLevel): void {}
|
// MemoryLogger.setLevel 为接口兼容,无需实际过滤
|
||||||
|
}
|
||||||
|
|
||||||
warn(message: string, data?: unknown): void {
|
warn(message: string, data?: unknown): void {
|
||||||
this.capture("warn", message, data);
|
this.capture("warn", message, data);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/require-await */
|
||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { mkdirSync } from "node:fs";
|
import { mkdirSync } from "node:fs";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
* 后端测试无需 DOM 环境,前端测试依赖 jsdom 及 antd polyfill
|
* 后端测试无需 DOM 环境,前端测试依赖 jsdom 及 antd polyfill
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
// 仅当前端测试需要时初始化 jsdom(所有测试共享 preload,后端测试也在此环境中运行)
|
// 仅当前端测试需要时初始化 jsdom(所有测试共享 preload,后端测试也在此环境中运行)
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ describe("QueryClient MutationCache onError", () => {
|
|||||||
mutate(
|
mutate(
|
||||||
{ name: "test" },
|
{ name: "test" },
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
onError: () => {},
|
onError: () => {},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { describe, expect, mock, test } from "bun:test";
|
import { describe, expect, mock, test } from "bun:test";
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
import { describe, expect, mock, test } from "bun:test";
|
import { describe, expect, mock, test } from "bun:test";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
import { describe, expect, mock, test } from "bun:test";
|
import { describe, expect, mock, test } from "bun:test";
|
||||||
|
|
||||||
import { handleResponse, handleVoidResponse } from "../../../src/web/utils/api";
|
import { handleResponse, handleVoidResponse } from "../../../src/web/utils/api";
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
import { describe, expect, mock, test } from "bun:test";
|
import { describe, expect, mock, test } from "bun:test";
|
||||||
|
|
||||||
import type { Sink } from "../../../src/web/utils/logger";
|
import type { Sink } from "../../../src/web/utils/logger";
|
||||||
|
|||||||
Reference in New Issue
Block a user