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
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { NInputNumber, NSelect, NSwitch } from "naive-ui";
import { useMessage } from "@/composables/useMessage";
import { useI18n } from "vue-i18n";
import { useSettingsStore } from "@/stores/hermes/settings";
@@ -15,7 +14,7 @@ const { t } = useI18n();
const debounceTimers: Record<string, ReturnType<typeof setTimeout>> = {};
function save(values: Record<string, any>) {
// NSelect/NSwitch 等一次性操作,直接保存,不需要防抖
// 下拉/开关等一次性操作,直接保存,不需要防抖
settingsStore.updateLocal('session_reset', values)
settingsStore.saveSection('session_reset', values).then(() => {
message.success(t("settings.saved"));
@@ -66,32 +65,32 @@ async function toggleWriteApproval(section: "memory" | "skills", value: boolean)
:label="t('settings.session.requireAuth')"
:hint="t('settings.session.requireAuthHint')"
>
<NSwitch :value="settingsStore.approvals.mode === 'manual'" @update:value="toggleRequireAuth" />
<a-switch :model-value="settingsStore.approvals.mode === 'manual'" @update:model-value="(v) => toggleRequireAuth(v as boolean)" />
</SettingRow>
<SettingRow
:label="t('settings.session.memoryWriteApproval')"
:hint="t('settings.session.memoryWriteApprovalHint')"
>
<NSwitch
:value="settingsStore.memory.write_approval === true"
@update:value="(value) => toggleWriteApproval('memory', value)"
<a-switch
:model-value="settingsStore.memory.write_approval === true"
@update:model-value="(value) => toggleWriteApproval('memory', value as boolean)"
/>
</SettingRow>
<SettingRow
:label="t('settings.session.skillsWriteApproval')"
:hint="t('settings.session.skillsWriteApprovalHint')"
>
<NSwitch
:value="settingsStore.skills.write_approval === true"
@update:value="(value) => toggleWriteApproval('skills', value)"
<a-switch
:model-value="settingsStore.skills.write_approval === true"
@update:model-value="(value) => toggleWriteApproval('skills', value as boolean)"
/>
</SettingRow>
<SettingRow
:label="t('settings.session.mode')"
:hint="t('settings.session.modeHint')"
>
<NSelect
:value="settingsStore.sessionReset.mode || 'both'"
<a-select
:model-value="settingsStore.sessionReset.mode || 'both'"
:options="[
{ label: t('settings.session.modeBoth'), value: 'both' },
{ label: t('settings.session.modeIdle'), value: 'idle' },
@@ -100,44 +99,44 @@ async function toggleWriteApproval(section: "memory" | "skills", value: boolean)
]"
size="small"
class="input-md"
@update:value="(v) => save({ mode: v })"
@update:model-value="(v) => save({ mode: v })"
/>
</SettingRow>
<SettingRow
:label="t('settings.session.idleMinutes')"
:hint="t('settings.session.idleMinutesHint')"
>
<NInputNumber
:value="settingsStore.sessionReset.idle_minutes"
<a-input-number
:model-value="settingsStore.sessionReset.idle_minutes"
:min="10"
:max="10080"
:step="30"
size="small"
class="input-sm"
@update:value="(v) => v != null && debouncedSave('idle_minutes', v)"
@update:model-value="(v) => v != null && debouncedSave('idle_minutes', v)"
/>
</SettingRow>
<SettingRow
:label="t('settings.session.atHour')"
:hint="t('settings.session.atHourHint')"
>
<NInputNumber
:value="settingsStore.sessionReset.at_hour"
<a-input-number
:model-value="settingsStore.sessionReset.at_hour"
:min="0"
:max="23"
:step="1"
size="small"
class="input-sm"
@update:value="(v) => v != null && debouncedSave('at_hour', v)"
@update:model-value="(v) => v != null && debouncedSave('at_hour', v)"
/>
</SettingRow>
<SettingRow
:label="t('settings.session.liveMonitorHumanOnly')"
:hint="t('settings.session.liveMonitorHumanOnlyHint')"
>
<NSwitch
:value="sessionBrowserPrefsStore.humanOnly"
@update:value="(value) => sessionBrowserPrefsStore.setHumanOnly(value)"
<a-switch
:model-value="sessionBrowserPrefsStore.humanOnly"
@update:model-value="(value) => sessionBrowserPrefsStore.setHumanOnly(value as boolean)"
/>
</SettingRow>
</section>