feat(file-tree): 优化文件加载反馈,添加加载状态指示器
This commit is contained in:
parent
39a0fba402
commit
ced97d5636
@ -41,6 +41,7 @@
|
||||
- 收紧文件系统安全边界:viewer/chat 下载与删除路径统一基于解析后的真实路径做允许目录校验,阻止通过软链接逃逸工作区/线程目录;同时将密码哈希默认实现升级为 Argon2,并移除 skill frontmatter 解析中的正则回溯风险。
|
||||
- 调整 Skills 导入能力:`/api/system/skills/import` 现在除 ZIP 外也支持直接上传单个 `SKILL.md`,前端上传入口与后端导入服务同步兼容,便于快速导入单文件技能
|
||||
- 扩展 viewer 工作区文件操作:`/home/gem/user-data/workspace` 支持从文件系统面板新建文件夹和上传文件,后端限制写入范围并保持同名冲突直接报错。
|
||||
- 优化文件系统面板目录展开反馈:异步加载子文件时在对应文件夹图标位置显示 loading 状态,避免点击后无视觉响应。
|
||||
- 新增 Skills 远程安装能力:Skills 管理页支持填写 `owner/repo` 或 GitHub URL,后端通过隔离的临时 `HOME` 调用 `npx skills add` 下载指定 skill,再复用现有导入链路写入 `saves/skills` 和数据库,避免将 `~/.agents/skills` 直接作为系统主存储;前端远程安装弹窗补充多选串行安装与批量进度展示,复用现有单 skill 安装接口逐个提交请求
|
||||
- 调整部门删除语义:删除部门时不再要求用户数为 0,而是将部门下用户迁移到默认部门,同时清理部门级配置和部门 API Key,保证测试部门、撤换部门等场景可直接删除,并补充对应集成测试覆盖该链路
|
||||
- 重构 MCP 运行时配置加载模型:移除 `MCP_SERVERS` 作为运行正确性前提的设计,改为每次直接从数据库读取最新 MCP 配置,并用 `server_name:config_hash` 作为本地工具缓存 key;同时将内置 MCP 初始化职责收敛为仅同步数据库默认项,前端 MCP 选项改为直接使用实时资源列表,解决 `api`/`worker` 分进程下的配置不一致与缓存失效问题
|
||||
|
||||
@ -870,7 +870,7 @@ watch(useInlinePreview, (isInline) => {
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px 16px;
|
||||
min-height: 56px;
|
||||
min-height: 44px;
|
||||
background: var(--gray-25);
|
||||
flex-shrink: 0;
|
||||
|
||||
@ -1250,7 +1250,6 @@ watch(useInlinePreview, (isInline) => {
|
||||
|
||||
/* File Tree Styles - VS Code Style Refined */
|
||||
.file-tree-container {
|
||||
padding: 4px;
|
||||
margin: 0 -4px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
:selected-keys="selectedKeys"
|
||||
:expanded-keys="expandedKeys"
|
||||
:tree-data="treeData"
|
||||
:load-data="loadData"
|
||||
:load-data="loadData ? internalLoadData : undefined"
|
||||
:show-icon="showIcon"
|
||||
:block-node="blockNode"
|
||||
:show-line="showLine"
|
||||
@ -25,7 +25,12 @@
|
||||
<FileText v-else :size="16" class="file-icon" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<FolderOpen v-if="expanded" :size="18" class="folder-icon open" />
|
||||
<span
|
||||
v-if="isNodeLoading(data.key)"
|
||||
class="folder-loading-icon"
|
||||
aria-label="正在加载"
|
||||
></span>
|
||||
<FolderOpen v-else-if="expanded" :size="18" class="folder-icon open" />
|
||||
<Folder v-else :size="18" class="folder-icon" />
|
||||
</template>
|
||||
</slot>
|
||||
@ -49,6 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Folder, FolderOpen, FileText } from 'lucide-vue-next'
|
||||
import { getFileIcon, getFileIconColor } from '@/utils/file_utils'
|
||||
|
||||
@ -100,6 +106,32 @@ const emit = defineEmits([
|
||||
'toggleFolder'
|
||||
])
|
||||
|
||||
const loadingKeys = ref(new Set())
|
||||
|
||||
const setNodeLoading = (key, isLoading) => {
|
||||
const nextLoadingKeys = new Set(loadingKeys.value)
|
||||
if (isLoading) {
|
||||
nextLoadingKeys.add(key)
|
||||
} else {
|
||||
nextLoadingKeys.delete(key)
|
||||
}
|
||||
loadingKeys.value = nextLoadingKeys
|
||||
}
|
||||
|
||||
const isNodeLoading = (key) => loadingKeys.value.has(key)
|
||||
|
||||
const internalLoadData = async (treeNode) => {
|
||||
if (!props.loadData) return
|
||||
|
||||
const key = treeNode?.key
|
||||
if (key) setNodeLoading(key, true)
|
||||
try {
|
||||
await props.loadData(treeNode)
|
||||
} finally {
|
||||
if (key) setNodeLoading(key, false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectedUpdate = (keys) => {
|
||||
emit('update:selectedKeys', keys)
|
||||
}
|
||||
@ -233,11 +265,26 @@ const handleNodeClick = (data) => {
|
||||
}
|
||||
|
||||
.folder-icon {
|
||||
color: #dcb67a;
|
||||
fill: #dcb67a;
|
||||
color: var(--main-500);
|
||||
fill: var(--main-500);
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
|
||||
.folder-loading-icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 2px solid var(--gray-200);
|
||||
border-top-color: var(--main-500);
|
||||
border-radius: 50%;
|
||||
animation: file-tree-folder-loading 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes file-tree-folder-loading {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
display: none;
|
||||
align-items: center;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user