Phase 007: migrate every remaining view from naive-ui to Arco Design Vue

The project now has exactly one UI library. Every naive-ui component in
packages/client/src is replaced by its Arco equivalent across 24 views and
roughly 80 components, and naive-ui is dropped from package.json along with
its transitive vueuc / @css-render / vdirs / seemly dependencies.

The migration is rendering-layer only. No store logic, no API contract, and
no /chat-run payload was touched.

Three classes of leftover were invisible to `grep naive-ui` and are now
covered by tests/client/no-naive-ui.test.ts, which asserts all three:
  - dead .n-* class names in style blocks
  - dead --n-* custom properties
  - naive-ui in package.json

Notable behavioural traps handled along the way, recorded in
docs/arco-migration-rules.md:
  - a-input-password defaults to masked; passing default-visibility="false"
    would have exposed API keys in clear text
  - a-upload fires a real XHR unless auto-upload is false
  - nested Trigger components fight over the first element child, so the
    group-chat settings dropdown was flattened
  - Arco renders tooltip default-slot text inline, which was silently
    collapsing the chat and group-chat toolbars
This commit is contained in:
2026-10-18 22:57:25 +08:00
parent c3eeea4187
commit 836d0228d2
115 changed files with 4067 additions and 3891 deletions
+31 -5
View File
@@ -12,16 +12,42 @@ describe('client style system', () => {
expect(globalStyles).toContain('.page-header > .header-actions')
})
it('aligns SCSS surfaces and radii with the Naive UI theme', () => {
it('aligns SCSS surfaces and radii with the shared theme tokens', () => {
const variables = readClientFile('styles/variables.scss')
const theme = readClientFile('styles/theme.ts')
expect(variables).toContain('--bg-card: #ffffff;')
// 深色主题必须重新定义 card surface,否则浅色值会漏到深色模式
expect(variables).toContain('--bg-card: #2a2a2a;')
expect(theme).toContain("cardColor: '#2a2a2a'")
expect(variables).toContain('$radius-sm: 6px;')
expect(variables).toContain('$radius-md: 8px;')
expect(variables).toContain('$radius-lg: 8px;')
expect(theme).toContain("borderRadius: '8px'")
expect(theme).toContain("borderRadiusSmall: '6px'")
})
it('exports the frozen Arco theme tokens', async () => {
const { arcoTheme } = await import('../../packages/client/src/styles/arco-theme')
expect(arcoTheme.colorPrimary).toBeTruthy()
expect(arcoTheme.colorBgLayout).toBeTruthy()
expect(arcoTheme.colorBgContainer).toBeTruthy()
expect(arcoTheme.borderRadius).toBeTypeOf('number')
expect(arcoTheme.fontFamily).toContain('Inter')
expect(arcoTheme.fontFamilyMono).toContain('JetBrains Mono')
})
it('carries dark mode through the arco-theme attribute on the root element', () => {
// 不 import useTheme:它在模块顶层读 localStorage,node 环境下会抛。
// 断言源码契约即可,真实行为由 useTheme 自己的测试覆盖。
const source = readClientFile('composables/useTheme.ts')
expect(source).toContain("setAttribute('arco-theme'")
expect(source).toMatch(/'dark' : 'light'|'dark'|'light'/)
})
it('keeps mobile inputs at 16px so iOS does not zoom on focus', () => {
const globalStyles = readClientFile('styles/global.scss')
// Arco 的输入框类名,不是 naive 的 .n-input__*
expect(globalStyles).toContain('.arco-input')
expect(globalStyles).not.toMatch(/\.n-input/)
})
})