diff --git a/docs/superpowers/specs/2026-05-24-ai-call-log-detail-design.md b/docs/superpowers/specs/2026-05-24-ai-call-log-detail-design.md index 0f72814..df3cd31 100644 --- a/docs/superpowers/specs/2026-05-24-ai-call-log-detail-design.md +++ b/docs/superpowers/specs/2026-05-24-ai-call-log-detail-design.md @@ -87,8 +87,9 @@ purpose: AI 调用日志详情查看功能设计文档 **弹窗特性:** - 入参/出参使用代码高亮 + 等宽字体,支持纵向滚动 - 每个代码块右上角有「复制」按钮,点击复制原始 JSON 到剪贴板 -- 错误信息区域仅在 `status === 'failed'` 时显示 -- 弹窗宽度 800px +- 错误信息区域仅在 `status === 'failed'` 时显示,格式为 `{errorCode}: {errorMessage}`(如 `AI_STREAM_INTERRUPTED: 连接超时`) +- 弹窗宽度 800px,响应式限制 `max-width: 90vw`,防止窄屏溢出 +- 支持 ESC 关闭和点击遮罩关闭(Element Plus Dialog 默认行为) ## 前端组件设计 @@ -153,16 +154,17 @@ interface LogQueryParams { function previewText(jsonStr: string, maxLen = 200): string { if (!jsonStr) return '-' if (jsonStr.length <= maxLen) return jsonStr - // 优先在空白字符处截断,避免截断在转义序列中间 const truncated = jsonStr.slice(0, maxLen) - const lastSpace = truncated.search(/\s+(?!.*\s)/) - return lastSpace > maxLen * 0.5 ? truncated.slice(0, lastSpace) + '...' : truncated + '...' + // 优先在空白字符处截断;无空白字符时直接截断到 maxLen + const lastSpace = truncated.lastIndexOf(' ') + const cutAt = lastSpace > maxLen * 0.5 ? lastSpace : maxLen + return truncated.slice(0, cutAt) + '...' } ``` ### 类型定义 -在 `web-admin/src/types/aiconfig.ts` 中新增: +在 `web-admin/src/types/common.ts` 中新增通用分页类型(如文件不存在则创建): ```typescript export interface LogQueryParams { status?: string @@ -198,7 +200,7 @@ export function queryAiCallLogs(params: LogQueryParams) { } ``` -**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。新接口使用 POST,旧接口使用 GET,路径相同方法不同,技术上可共存。实现时直接替换前端调用即可,旧 GET 接口可保留或后续清理。 +**兼容性说明:** 经检查,`listAiCallLogs` 仅在 `AiRoutingList.vue` 的调用日志 Tab 中使用,无其他调用方。新接口使用 POST,旧接口使用 GET,路径相同方法不同,技术上可共存。实现时直接替换前端调用为 POST 新接口,旧 GET 接口保留不删除(避免影响其他潜在调用方)。 ## 后端 API 设计 @@ -264,8 +266,8 @@ PageResult query(AiCallLogQueryRequest request); ```java @Override -public Page query(AiCallLogQueryRequest request) { - Page page = new Page<>(request.getPageNum(), request.getPageSize()); +public PageResult query(AiCallLogQueryRequest request) { + Page pageParam = new Page<>(request.getPageNum(), request.getPageSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(AiCallLog::getIsDeleted, 0) @@ -278,13 +280,22 @@ public Page query(AiCallLogQueryRequest request) { .orderByDesc(AiCallLog::getCreateTime); // keyword 为空字符串或纯空格时不执行 LIKE 搜索(isNotBlank 已处理) + // MyBatis-Plus like 默认使用预编译语句,无 SQL 注入风险 + // LIKE 是否区分大小写取决于数据库字符集(项目使用 utf8mb4,默认不区分大小写) if (StringUtils.isNotBlank(request.getKeyword())) { wrapper.and(w -> w.like(AiCallLog::getInputText, request.getKeyword()) .or() .like(AiCallLog::getOutputText, request.getKeyword())); } - return page(page, wrapper); + Page page = page(pageParam, wrapper); + // 转换为 PageResult,字段名与前端对齐 + PageResult result = new PageResult<>(); + result.setRecords(page.getRecords()); + result.setTotal(page.getTotal()); + result.setPageNum((int) page.getCurrent()); + result.setPageSize((int) page.getSize()); + return result; } ```