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
+14 -15
View File
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { NButton } from 'naive-ui'
import { useMessage } from '@/composables/useMessage'
import { useI18n } from 'vue-i18n'
import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue'
@@ -82,7 +81,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
<div class="memory-view">
<header class="page-header">
<h2 class="header-title">{{ t('memory.title') }}</h2>
<NButton size="small" quaternary @click="loadMemory">
<a-button size="small" type="text" @click="loadMemory">
<template #icon>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10" />
@@ -90,7 +89,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
</svg>
</template>
{{ t('memory.refresh') }}
</NButton>
</a-button>
</header>
<div class="memory-content">
@@ -111,7 +110,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
<span class="section-title">{{ t('memory.myNotes') }}</span>
<span v-if="data?.memory_mtime" class="section-mtime">{{ formatTime(data.memory_mtime) }}</span>
</div>
<NButton v-if="editingSection !== 'memory'" size="tiny" quaternary @click="startEdit('memory')">
<a-button v-if="editingSection !== 'memory'" size="mini" type="text" @click="startEdit('memory')">
<template #icon>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
@@ -119,7 +118,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
</svg>
</template>
{{ t('common.edit') }}
</NButton>
</a-button>
</div>
<!-- View mode -->
@@ -137,8 +136,8 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
spellcheck="false"
></textarea>
<div class="edit-actions">
<NButton size="small" @click="cancelEdit">{{ t('common.cancel') }}</NButton>
<NButton size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</NButton>
<a-button size="small" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
</div>
</div>
</div>
@@ -156,7 +155,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
<span class="section-title">{{ t('memory.userProfile') }}</span>
<span v-if="data?.user_mtime" class="section-mtime">{{ formatTime(data.user_mtime) }}</span>
</div>
<NButton v-if="editingSection !== 'user'" size="tiny" quaternary @click="startEdit('user')">
<a-button v-if="editingSection !== 'user'" size="mini" type="text" @click="startEdit('user')">
<template #icon>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
@@ -164,7 +163,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
</svg>
</template>
{{ t('common.edit') }}
</NButton>
</a-button>
</div>
<!-- View mode -->
@@ -182,8 +181,8 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
spellcheck="false"
></textarea>
<div class="edit-actions">
<NButton size="small" @click="cancelEdit">{{ t('common.cancel') }}</NButton>
<NButton size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</NButton>
<a-button size="small" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
</div>
</div>
</div>
@@ -203,7 +202,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
<span class="section-title">{{ t('memory.soul') }}</span>
<span v-if="data?.soul_mtime" class="section-mtime">{{ formatTime(data.soul_mtime) }}</span>
</div>
<NButton v-if="editingSection !== 'soul'" size="tiny" quaternary @click="startEdit('soul')">
<a-button v-if="editingSection !== 'soul'" size="mini" type="text" @click="startEdit('soul')">
<template #icon>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
@@ -211,7 +210,7 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
</svg>
</template>
{{ t('common.edit') }}
</NButton>
</a-button>
</div>
<!-- View mode -->
@@ -229,8 +228,8 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
spellcheck="false"
></textarea>
<div class="edit-actions">
<NButton size="small" @click="cancelEdit">{{ t('common.cancel') }}</NButton>
<NButton size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</NButton>
<a-button size="small" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
</div>
</div>
</div>