新增: 文件浏览器增强(窗口分离/Git 变更/行号/Diff/分页提交历史)
- 窗口分离: FileExplorer 可弹出独立 Tauri 窗口 - 行号显示: 文件预览左侧显示行号列 - 按后缀图标: rs/ts/js/vue/css/html 等 20+ 类型彩色图标 - Diff 视图: 有 Git 变更的文件可切换 diff 红绿视图 - Git 变更面板: 变更文件列表(按目录缩进分组)+提交历史(分页加载) - 提交详情: 点击提交查看变更文件列表及文件级 diff - 时间显示: 提交历史显示相对时间/具体日期 - 面包屑导航不丢预览: 导航时不改变当前预览文件 - 刷新保留选中文件: 只清树缓存不清预览 - 中文编码修复: git 命令注入 LANG/LC_ALL 环境变量 - 自适应布局: 弹性 flex 布局,窄窗口适配 - 滚动条优化: 内容区内部滚动,不溢出页面层 - 多工程管理: 工具栏添加工程按钮+弹窗表单 - 审批加载超时缩短: 130s 降至 30s - 文件变更自动刷新: write_file/patch_file 触发 df-data-changed
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
>
|
||||
<span class="expand-icon placeholder"></span>
|
||||
<span class="file-icon">📄</span>
|
||||
<span class="file-icon file-icon-ext" v-html="fileIcon(entry.name)"></span>
|
||||
<span class="file-name">{{ entry.name }}</span>
|
||||
<!-- Git 状态标记(仅文件):M 橙 / A 绿 / ?? 灰 -->
|
||||
<span
|
||||
@@ -127,6 +128,8 @@ const error = ref<string | null>(null)
|
||||
|
||||
/** 拉取当前 subPath 的条目列表。 */
|
||||
async function loadEntries() {
|
||||
// moduleId 为空时不发起请求(工程列表还在加载中)
|
||||
if (!props.moduleId) return
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
@@ -144,6 +147,8 @@ async function loadEntries() {
|
||||
/** 点击文件夹:切换展开态。 */
|
||||
async function toggleDir(entry: FileTreeEntry) {
|
||||
if (!entry.is_dir) return
|
||||
// moduleId 为空时不发起请求(工程列表还在加载中)
|
||||
if (!props.moduleId) return
|
||||
if (props.expandedPaths.has(entry.path)) {
|
||||
// 收起:仅移除展开标记(loadedChildren 缓存保留,下次展开秒开)。
|
||||
emit('toggle-dir', entry.path, [])
|
||||
@@ -188,6 +193,40 @@ function gitStatusLabel(status: string): string {
|
||||
return x.charAt(0) || '?'
|
||||
}
|
||||
|
||||
/** 文件后缀 → SVG 图标映射(纯文本,安全注入 v-html)。 */
|
||||
const FILE_ICONS: Record<string, string> = {
|
||||
// 代码
|
||||
rs: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#DE5842"/><path d="M8 8h8M8 12h8M8 16h5" stroke="#fff" stroke-width="1.5" stroke-linecap="round"/></svg>',
|
||||
ts: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#3178C6"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="11" font-weight="bold">TS</text></svg>',
|
||||
tsx: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#3178C6"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="10" font-weight="bold">TSX</text></svg>',
|
||||
js: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#F7DF1E"/><text x="12" y="16" text-anchor="middle" fill="#000" font-size="11" font-weight="bold">JS</text></svg>',
|
||||
jsx: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#F7DF1E"/><text x="12" y="16" text-anchor="middle" fill="#000" font-size="10" font-weight="bold">JSX</text></svg>',
|
||||
vue: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#42B883"/><path d="M7 7l5 10 5-10h-3l-2 4-2-4H7z" fill="#fff"/></svg>',
|
||||
// 样式
|
||||
css: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#2965F1"/><path d="M7 7l1 10 4 1.5 4-1.5 1-10H7z" fill="#fff" opacity="0.9"/></svg>',
|
||||
scss: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#C6538C"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">SCSS</text></svg>',
|
||||
html: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#E34F26"/><path d="M8 7l-.5 5.5h8.5l-.5 4-3.5 1-3.5-1" stroke="#fff" stroke-width="1.2" fill="none"/><path d="M12 12h3l-.5-3H12" stroke="#fff" stroke-width="1.2" fill="none"/></svg>',
|
||||
// 配置
|
||||
json: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#5C5C5C"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="8" font-weight="bold">{ }</text></svg>',
|
||||
toml: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#8B5CF6"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">TOML</text></svg>',
|
||||
yaml: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#6BB5A0"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">YM</text></svg>',
|
||||
yml: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#6BB5A0"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="9" font-weight="bold">YM</text></svg>',
|
||||
// 文档
|
||||
md: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#083FA1"/><path d="M7 15V9l2.5 3L12 9v6" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>',
|
||||
// Shell
|
||||
sh: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#4EAA25"/><path d="M8 9l3 3-3 3M13 15h3" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>',
|
||||
bash: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#4EAA25"/><path d="M8 9l3 3-3 3M13 15h3" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>',
|
||||
// 构建
|
||||
dockerfile: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#2496ED"/><path d="M7 12h1M9 12h1M11 12h1M13 12h1M15 12h1M17 10h-2v2h2v-2zM10 9H8v2h2V9z" stroke="#fff" stroke-width="1" fill="none"/></svg>',
|
||||
sql: '<svg viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="18" height="18" rx="3" fill="#E38C00"/><text x="12" y="16" text-anchor="middle" fill="#fff" font-size="8" font-weight="bold">SQL</text></svg>',
|
||||
}
|
||||
|
||||
/** 文件后缀 → 图标 HTML(v-html 安全注入)。 */
|
||||
function fileIcon(name: string): string {
|
||||
const ext = name.split('.').pop()?.toLowerCase() ?? ''
|
||||
return FILE_ICONS[ext] || ''
|
||||
}
|
||||
|
||||
onMounted(loadEntries)
|
||||
|
||||
// moduleId 变化(切换工程)时重拉。
|
||||
@@ -198,6 +237,8 @@ watch(
|
||||
() => props.loadedChildren.has(props.subPath || ''),
|
||||
(has) => {
|
||||
if (!has && entries.value.length > 0) {
|
||||
// moduleId 为空时不发起请求
|
||||
if (!props.moduleId) return
|
||||
loadEntries()
|
||||
}
|
||||
},
|
||||
@@ -260,6 +301,22 @@ watch(
|
||||
.file-icon {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-icon-ext {
|
||||
margin-left: -18px;
|
||||
}
|
||||
|
||||
.file-icon-ext svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
|
||||
Reference in New Issue
Block a user