diff --git a/docs/前端壳子与接口层开发说明.md b/docs/前端壳子与接口层开发说明.md index 16df6c9..5168630 100644 --- a/docs/前端壳子与接口层开发说明.md +++ b/docs/前端壳子与接口层开发说明.md @@ -184,13 +184,13 @@ pnpm build ```text pnpm build passed -pnpm test 17 passed +pnpm test 18 passed uv run pytest 71 passed preview smoke HTTP 200 git diff --check passed ``` -当前前端使用 Vitest 执行 Store、Workspace、文件树、编辑器组件、智能体标签和轻量动效约束测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 71 项测试结果,也不涉及产品代码。 +当前前端使用 Vitest 执行 Store、Workspace、文件树、编辑器组件、智能体标签、轻量动效约束和 Markdown 对比度 Token 测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 71 项测试结果,也不涉及产品代码。 Vite 当前会提示 Chat 与 Workspace 的部分异步 Chunk 超过 500 kB,这是 Milkdown、CodeMirror、KaTeX 和 Shiki 等编辑/渲染依赖带来的性能优化项,不影响构建成功或功能正确性;进入桌面打包前应通过手动分包或更细粒度动态加载继续优化。 diff --git a/docs/前端视觉与轻量动效优化开发说明.md b/docs/前端视觉与轻量动效优化开发说明.md index 417798e..6e764d2 100644 --- a/docs/前端视觉与轻量动效优化开发说明.md +++ b/docs/前端视觉与轻量动效优化开发说明.md @@ -97,12 +97,12 @@ backdrop-filter / filter 模糊 ## 6. 验证 -新增 `styles/motion.spec.ts`,防止全局样式重新引入 `transition: all`、高成本模糊滤镜或布局型页面入场动画。 +新增 `styles/motion.spec.ts`,防止全局样式重新引入 `transition: all`、高成本模糊滤镜或布局型页面入场动画,并约束 Markdown 表格与列表使用独立的高对比度主题变量。 当前验证结果: ```text -pnpm test 7 files / 17 tests passed +pnpm test 7 files / 18 tests passed pnpm build passed git diff --check passed ``` diff --git a/docs/第一阶段测试验证操作手册.md b/docs/第一阶段测试验证操作手册.md index a70afdd..029c671 100644 --- a/docs/第一阶段测试验证操作手册.md +++ b/docs/第一阶段测试验证操作手册.md @@ -84,10 +84,10 @@ pnpm test ```text 7 test files passed -17 tests passed +18 tests passed ``` -通过标准:退出码为 0、失败数为 0。测试覆盖 Provider Store、Workspace、文件树、文件切换、可视化编辑器、智能体中文标签和轻量动效性能约束。 +通过标准:退出码为 0、失败数为 0。测试覆盖 Provider Store、Workspace、文件树、文件切换、可视化编辑器、智能体中文标签、轻量动效性能约束和 Markdown 对比度 Token。 ### 3.3 类型检查与生产构建 diff --git a/frontend/src/components/common/MarkdownContent.vue b/frontend/src/components/common/MarkdownContent.vue index b0a225d..3e3f173 100644 --- a/frontend/src/components/common/MarkdownContent.vue +++ b/frontend/src/components/common/MarkdownContent.vue @@ -23,12 +23,14 @@ watch(() => props.source, async (source) => { .markdown-content :deep(h1), .markdown-content :deep(h2), .markdown-content :deep(h3) { margin: 1em 0 .5em; line-height: var(--line-height-tight); } .markdown-content :deep(ul) { padding-left: 1.5em; list-style: disc; } .markdown-content :deep(ol) { padding-left: 1.5em; list-style: decimal; } +.markdown-content :deep(li::marker) { color: var(--color-markdown-marker); font-weight: 700; } .markdown-content :deep(.shiki) { overflow: auto; padding: var(--space-md); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-md); } .markdown-content :deep(code) { padding: .1em .3em; border-radius: var(--radius-sm); background: var(--color-background-tertiary); font-family: var(--font-ui-mono); } .markdown-content :deep(pre code) { padding: 0; background: transparent; } .markdown-content :deep(blockquote) { padding-left: 1em; border-left: 3px solid var(--color-accent-primary); color: var(--color-text-secondary); } .markdown-content :deep(table) { width: 100%; margin: .65em 0; border-collapse: collapse; } -.markdown-content :deep(th), .markdown-content :deep(td) { padding: .45em .65em; border: 1px solid var(--color-border-default); text-align: left; } +.markdown-content :deep(th), .markdown-content :deep(td) { padding: .45em .65em; border: 1px solid var(--color-markdown-grid); text-align: left; } +.markdown-content :deep(th) { background: var(--color-markdown-table-header); font-weight: 700; } .markdown-content :deep(img) { max-width: 100%; } .markdown-content :deep(hr) { margin: 1em 0; border: 0; border-top: 1px solid var(--color-border-default); } :global([data-theme='dark']) .markdown-content :deep(.shiki), diff --git a/frontend/src/features/editor/VisualMarkdownEditor.vue b/frontend/src/features/editor/VisualMarkdownEditor.vue index c3d323f..a339f4a 100644 --- a/frontend/src/features/editor/VisualMarkdownEditor.vue +++ b/frontend/src/features/editor/VisualMarkdownEditor.vue @@ -250,7 +250,7 @@ defineExpose({ getEditor: () => crepe?.editor }) --crepe-color-surface-low: var(--color-background-secondary); --crepe-color-on-surface: var(--color-text-primary); --crepe-color-on-surface-variant: var(--color-text-secondary); - --crepe-color-outline: var(--color-border-default); + --crepe-color-outline: var(--color-markdown-grid); --crepe-color-primary: var(--color-accent-primary); --crepe-color-secondary: var(--color-accent-soft); --crepe-color-on-secondary: var(--color-text-primary); @@ -272,10 +272,11 @@ defineExpose({ getEditor: () => crepe?.editor }) :global(.milkdown-toolbar) { border: 1px solid var(--color-border-default) !important; background: var(--color-surface-elevated) !important; box-shadow: var(--shadow-md) !important; } :global(.milkdown-toolbar .toolbar-item svg), :global(.milkdown-toolbar .toolbar-item.active svg) { color: var(--color-text-primary) !important; fill: var(--color-text-primary) !important; opacity: 1 !important; } :global(.milkdown-toolbar .toolbar-item:hover svg), :global(.milkdown-toolbar .toolbar-item.active svg) { color: var(--color-accent-primary) !important; fill: var(--color-accent-primary) !important; } -:global([data-theme='light']) .milkdown-host :deep(.milkdown-table-block th), -:global([data-theme='light']) .milkdown-host :deep(.milkdown-table-block td) { border-color: var(--color-text-tertiary); } -:global([data-theme='light']) .milkdown-host :deep(.milkdown-list-item-block li .label-wrapper) { color: var(--color-text-secondary); font-weight: 600; } -:global([data-theme='light']) .milkdown-host :deep(.milkdown-list-item-block li .label-wrapper svg) { fill: var(--color-text-secondary); } +.milkdown-host :deep(.milkdown-table-block th), +.milkdown-host :deep(.milkdown-table-block td) { border-color: var(--color-markdown-grid); } +.milkdown-host :deep(.milkdown-table-block th) { background: var(--color-markdown-table-header); font-weight: 700; } +.milkdown-host :deep(.milkdown-list-item-block li .label-wrapper) { color: var(--color-markdown-marker); font-weight: 700; } +.milkdown-host :deep(.milkdown-list-item-block li .label-wrapper svg) { fill: var(--color-markdown-marker); } .milkdown-host :deep(code) { font-family: var(--font-editor-mono); } :global([data-theme='dark']) .milkdown-host :deep(.milkdown) { color-scheme: dark; } @media (max-width: 680px) { .toolbar-select select { min-width: 46px; width: 46px; } } diff --git a/frontend/src/styles/motion.spec.ts b/frontend/src/styles/motion.spec.ts index 083a9d4..59f8e82 100644 --- a/frontend/src/styles/motion.spec.ts +++ b/frontend/src/styles/motion.spec.ts @@ -8,6 +8,11 @@ const globalStyles = [ new URL('./features.css', import.meta.url), ].map((path) => readFileSync(path, 'utf8')).join('\n') +const markdownStyles = [ + new URL('../features/editor/VisualMarkdownEditor.vue', import.meta.url), + new URL('../components/common/MarkdownContent.vue', import.meta.url), +].map((path) => readFileSync(path, 'utf8')).join('\n') + describe('轻量动效基线', () => { it('为减少动态效果偏好提供全局回退', () => { expect(globalStyles).toContain('@media (prefers-reduced-motion: reduce)') @@ -24,4 +29,11 @@ describe('轻量动效基线', () => { expect(pageAnimation).toContain('transform') expect(pageAnimation).not.toMatch(/(?:width|height|margin|padding|top|left)\s*:/) }) + + it('Markdown 序号和表格使用独立的高对比度主题变量', () => { + expect(globalStyles).toContain('--color-markdown-grid:') + expect(globalStyles).toContain('--color-markdown-marker:') + expect(markdownStyles).toContain('var(--color-markdown-grid)') + expect(markdownStyles).toContain('var(--color-markdown-marker)') + }) }) diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index 5703776..a13cbfc 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -44,6 +44,11 @@ --color-border-focus: #5b67f1; --color-border-disabled: #eef0f3; + /* Markdown */ + --color-markdown-grid: #8b949e; + --color-markdown-marker: #343b44; + --color-markdown-table-header: #eef0f3; + /* Shadow */ --shadow-sm: 0 1px 2px rgba(31, 35, 40, 0.05), 0 1px 5px rgba(31, 35, 40, 0.03); --shadow-md: 0 8px 22px rgba(31, 35, 40, 0.08), 0 2px 6px rgba(31, 35, 40, 0.04); @@ -152,6 +157,10 @@ --color-border-focus: #7d8bff; --color-border-disabled: #21262d; + --color-markdown-grid: #6e7681; + --color-markdown-marker: #c9d1d9; + --color-markdown-table-header: #21262d; + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4); --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5); @@ -172,6 +181,9 @@ --color-text-tertiary: #9a8a72; --color-border-default: #ddcfad; --color-border-subtle: #eadfc4; + --color-markdown-grid: #9c8353; + --color-markdown-marker: #554735; + --color-markdown-table-header: #eadbb8; --color-accent-primary: #8a5b32; --color-accent-primary-hover: #704724; --color-accent-soft: #edddbd;