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:
@@ -1,9 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, h } from 'vue'
|
||||
import { NTree } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useFilesStore } from '@/stores/hermes/files'
|
||||
import type { TreeOption } from 'naive-ui'
|
||||
import type { TreeNodeData } from '@arco-design/web-vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const filesStore = useFilesStore()
|
||||
@@ -14,12 +13,12 @@ const props = defineProps<{
|
||||
|
||||
const effectiveProfile = computed(() => props.profile === undefined ? filesStore.currentProfile : props.profile)
|
||||
|
||||
const treeData = ref<TreeOption[]>([])
|
||||
const treeData = ref<TreeNodeData[]>([])
|
||||
const selectedKeys = ref<string[]>([])
|
||||
const treeInstanceKey = ref(0)
|
||||
let rootLoadSeq = 0
|
||||
|
||||
async function loadChildren(path: string): Promise<TreeOption[]> {
|
||||
async function loadChildren(path: string): Promise<TreeNodeData[]> {
|
||||
try {
|
||||
const result = filesStore.currentWorkspaceSessionId
|
||||
? await filesStore.listEntries(path)
|
||||
@@ -29,7 +28,7 @@ async function loadChildren(path: string): Promise<TreeOption[]> {
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(e => ({
|
||||
key: e.path,
|
||||
label: e.name,
|
||||
title: e.name,
|
||||
isLeaf: false,
|
||||
}))
|
||||
} catch {
|
||||
@@ -37,16 +36,16 @@ async function loadChildren(path: string): Promise<TreeOption[]> {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLoad(node: TreeOption): Promise<void> {
|
||||
async function handleLoad(node: TreeNodeData): Promise<void> {
|
||||
const seq = rootLoadSeq
|
||||
const children = await loadChildren(node.key as string)
|
||||
const children = await loadChildren(String(node.key ?? ''))
|
||||
if (seq === rootLoadSeq) node.children = children
|
||||
}
|
||||
|
||||
function handleSelect(keys: string[]) {
|
||||
function handleSelect(keys: (string | number)[]) {
|
||||
if (keys.length > 0) {
|
||||
selectedKeys.value = keys
|
||||
filesStore.navigateTo(keys[0], { profile: effectiveProfile.value })
|
||||
selectedKeys.value = keys.map(String)
|
||||
filesStore.navigateTo(String(keys[0]), { profile: effectiveProfile.value })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +54,6 @@ function handleRootClick() {
|
||||
filesStore.navigateTo('', { profile: effectiveProfile.value })
|
||||
}
|
||||
|
||||
function renderLabel({ option }: { option: TreeOption }) {
|
||||
const label = String(option.label || '')
|
||||
return h('span', { class: 'tree-node-label', title: label }, label)
|
||||
}
|
||||
|
||||
watch([effectiveProfile, () => filesStore.currentWorkspaceSessionId, () => props.workspaceKey], async () => {
|
||||
const seq = ++rootLoadSeq
|
||||
selectedKeys.value = []
|
||||
@@ -78,16 +72,21 @@ watch([effectiveProfile, () => filesStore.currentWorkspaceSessionId, () => props
|
||||
</svg>
|
||||
<span>{{ t('files.breadcrumbRoot') }}</span>
|
||||
</div>
|
||||
<NTree
|
||||
<a-tree
|
||||
:key="treeInstanceKey"
|
||||
:data="treeData"
|
||||
:selected-keys="selectedKeys"
|
||||
:on-load="handleLoad"
|
||||
:render-label="renderLabel"
|
||||
expand-on-click
|
||||
block-line
|
||||
@update:selected-keys="handleSelect"
|
||||
/>
|
||||
:load-more="handleLoad"
|
||||
:default-expand-all="false"
|
||||
action-on-node-click="expand"
|
||||
block-node
|
||||
show-line
|
||||
@select="handleSelect"
|
||||
>
|
||||
<template #title="node">
|
||||
<span class="tree-node-label" :title="node.title">{{ node.title }}</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user