From c0212943227dd89d62a31f7c530171467876bede Mon Sep 17 00:00:00 2001 From: skycurtain Date: Tue, 8 Sep 2026 00:49:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=99=9A=E6=8B=9F=E6=BB=9A=E5=8A=A8=20?= =?UTF-8?q?JSON=20=E5=93=8D=E5=BA=94=E5=8C=BA=20+=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit json-text-view:DOM 恒定(可视窗口 ± overscan)、懒高亮(仅窗口内行 segments 切词)、spacer 撑高 + translateY 偏移;组件测试 stub useElementSize 固定视口断言行内容实际渲染。响应区完整化:元信息行/复制(useClipboard legacy 降级)/下载;README 定稿(运行需配 GATEWAY)。 --- README.md | 61 +++++++++++ src/components/json-text-view.spec.ts | 81 ++++++++++++++ src/components/json-text-view.vue | 145 ++++++++++++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 README.md create mode 100644 src/components/json-text-view.spec.ts create mode 100644 src/components/json-text-view.vue diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c3ef76 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# new-api-key-bench + +NewAPI sk-key 接口的「文档 + 调试」二合一前端工具(bench),替代 Postman 的专用调试台,服务 karpool 拼车项目的接口调试与换算验证。 + +## 技术栈 + +Vite + Vue 3 + TypeScript + TDesign + TanStack Query(vue-query)+ Pinia + VueUse + Vitest。运行时与包管理器为 Bun。 + +## 运行 + +网关地址只从环境变量 `GATEWAY` 读取(代码不写死默认值)。两种配置方式任选其一: + +```bash +# 方式一:复制示例文件后填入真实地址(.env.local 已被 gitignore,不会提交) +cp new-api-key-bench/.env.example new-api-key-bench/.env.local +# 然后编辑 .env.local,把 GATEWAY 改成你的网关地址 + +# 方式二:shell 环境变量(PowerShell:$env:GATEWAY="https://…") +export GATEWAY=https://your-gateway.example.com +``` + +未配置时 `bun run dev` 会直接报错提示;`build` 无代理不受影响。 + +```bash +cd new-api-key-bench +bun install +bun run dev # 默认 http://localhost:5173,接口经 dev server 代理转发到 GATEWAY +``` + +```bash +bun run test # vitest(单元 + 组件 + 集成冒烟全量) +bun run lint # oxlint + eslint +bun run build # type-check + vite build(dist/) +``` + +## 使用 + +1. 左侧选择接口(5 个,平铺不分组)→ 右侧开 tab +2. tab 内展开「注意事项」看该接口的实证调研坑文档 +3. 填 sk-key(每 tab 独立、仅存内存、刷新即重置,不落盘;切换 tab 输入保留) +4. 点「发送请求」→ 响应区显示元信息(状态/耗时/字符数)+ 虚拟滚动高亮 JSON +5. 复制 / 下载 `.json`;非 2xx 响应体照常展示(调试 401/403 是刚需) + +## 架构说明 + +- **CORS**:页面请求打相对路径(`/api`、`/v1`),由 Vite dev server 代理转发到网关(`vite.config.ts`,网关地址从环境变量 `GATEWAY` 读取),网关未配跨域头也不受影响 +- **大响应性能**:`/api/log/token` 1000 条日志(pretty-print 2~4MB、约两万行)由 `json-text-view.vue` 虚拟滚动渲染——DOM 恒定 ~40 行,语法高亮仅对可视区行执行 +- **请求语义**:纯手动触发(TanStack Query `enabled` 门控 + `refetch`),`staleTime: Infinity`,切 tab 不自动重发;queryKey 为 `['endpoint', path, skKey]`,换 key 即新查询 +- **tab 状态**:Pinia(开/关/激活/拖拽重排 + 每 tab sk-key,Postman 语义);面板随 tab 切换卸载重建,sk-key 由 store 持有所以输入保留,响应数据由 Query 缓存兜底恢复;t-tabs 用官方 `list` prop 数据驱动(插槽 panel 在动态增删下与数据脱钩,是已踩过的坑) + +## 已知边界 + +- 生产部署(`bun run build`)无 Vite 代理,需为网关路径配置反向代理 +- `useClipboard` 在非 https 环境(如 http://localhost)自动降级 `execCommand` +- `/v1/models/:model` 单模型详情接口未纳入(数据源是硬编码静态表,与 `/v1/models` 不一致,无调试价值) + +## 文档 + +- 交互与技术决策:`docs/bench交互设计文档.md` +- 施工计划与验收记录:`docs/bench实施计划.md` +- 接口数据源:`docs/NewAPI-sk-key接口文档.md` diff --git a/src/components/json-text-view.spec.ts b/src/components/json-text-view.spec.ts new file mode 100644 index 0000000..2595af0 --- /dev/null +++ b/src/components/json-text-view.spec.ts @@ -0,0 +1,81 @@ +// @vitest-environment jsdom +/** + * json-text-view 虚拟滚动组件测试(阶段五测试预算,防性能回归) + * jsdom 下 ResizeObserver/真实布局不可用,故 stub useElementSize 返回固定视口高度, + * 让 computeVisibleRowWindow 走真实窗口路径;断言「行内容实际渲染」—— + * tokens→segments 迁移时曾只改 script 漏改模板,type-check 挂而现有测试全绿没拦住 + */ + +import { mount } from '@vue/test-utils'; +import { ref } from 'vue'; +import { describe, expect, it, vi } from 'vitest'; + +import JsonTextView from './json-text-view.vue'; + +vi.mock('@vueuse/core', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + // 固定视口 400px(约 20 行可视 + 2×5 overscan):窗口形状由测试直接控制 + useElementSize: () => ({ width: ref(800), height: ref(400) }), + }; +}); + +function makeJsonText(entryCount: number): string { + const entries = Array.from({ length: entryCount }, (_, i) => ({ + id: i, + model_name: `model-${i}`, + quota: 2500, + })); + return JSON.stringify({ data: entries }, null, 2); +} + +describe('json-text-view 虚拟滚动', () => { + it('小 JSON(一屏内)全部行渲染:括号等 plain 不丢、token 按类着色', () => { + const wrapper = mount(JsonTextView, { + props: { json: { code: true, total_used: 673419457 }, rawText: '' }, + attachTo: document.body, + }); + const lines = wrapper.findAll('.line'); + expect(lines).toHaveLength(4); // pretty-print 共 4 行({ + 2 字段 + }) + // 首尾行是纯结构行(plain),必须渲染出来(丢括号 bug 的回归测试) + expect(lines[0]!.find('.line-content').text()).toBe('{'); + expect(lines[3]!.find('.line-content').text()).toBe('}'); + // key / boolean / number 着色正确,且整行内容与原文一致(textContent 保留前导缩进) + const codeLineText = lines[1]!.find('.line-content').element.textContent; + expect(codeLineText).toBe(' "code": true,'); + expect(lines[1]!.find('.token.key').text()).toBe('"code":'); + expect(lines[1]!.find('.token.boolean').text()).toBe('true'); + expect(lines[2]!.find('.token.key').text()).toBe('"total_used":'); + expect(lines[2]!.find('.token.number').text()).toBe('673419457'); + wrapper.unmount(); + }); + + it('两万行级 JSON:DOM 行数恒定(窗口 ± overscan,非总行数)', () => { + const text = makeJsonText(6000); // ~2.4 万行 + const wrapper = mount(JsonTextView, { + props: { json: JSON.parse(text), rawText: text }, + attachTo: document.body, + }); + const renderedLineCount = wrapper.findAll('.line').length; + // 400px 视口 → 20 可视 + 2×5 缓冲 ≈ 30 行;绝不等于总行数 + expect(renderedLineCount).toBeGreaterThan(0); + expect(renderedLineCount).toBeLessThan(100); + expect(text.split('\n').length).toBeGreaterThan(20000); // 前提成立 + wrapper.unmount(); + }); + + it('非 JSON 响应:原文降级展示,行内容为原文本', () => { + const wrapper = mount(JsonTextView, { + props: { json: null, rawText: '502 Bad Gateway' }, + attachTo: document.body, + }); + const spacer = wrapper.find('.scroll-spacer'); + expect(spacer.exists()).toBe(true); + expect(spacer.attributes('style')).toContain('height: 20px'); // 1 行 × 20px + expect(wrapper.findAll('.line-content').map((line) => line.text())).toEqual([ + '502 Bad Gateway', + ]); + wrapper.unmount(); + }); +}); diff --git a/src/components/json-text-view.vue b/src/components/json-text-view.vue new file mode 100644 index 0000000..2b45199 --- /dev/null +++ b/src/components/json-text-view.vue @@ -0,0 +1,145 @@ +/** + * 虚拟滚动 JSON 文本视图(核心性能组件,对齐 DevTools Network Response 体验) + * + * 性能承诺(实施计划阶段五验收):/api/log/token 1000 条日志(2~4MB、约两万行) + * 渲染与全程滚动不掉帧;「收到响应 → 首屏渲染」小于 200ms。 + * + * 实现要点: + * - DOM 恒定:只渲染可视窗口 ± overscan 的行(json-text-processor.computeVisibleRowWindow) + * - 懒高亮:仅对窗口内行做 token 切词(结构化 span),转义与着色在模板层,无 v-html + * - 滚动条:撑高度用总行数 × 行高的占位 div,窗口偏移用 translateY + */ + + + + + +