新增: 项目文件浏览器(文件树+内容预览+Git 状态标记)

项目详情页新增文件标签页,用户可在 DevFlow 内浏览项目
文件,查看文件内容和 Git 改动状态,无需切换外部编辑器。

后端接口:
- 文件树查询:列工程目录,合并 Git 状态到每个文件
  (噪音过滤 node_modules/target/.git 等,路径穿越防御)
- 文件读取:支持文本/图片/二进制检测,1MB 上限

前端组件:
- 文件树:递归树形展示,懒加载子目录,Git 状态标记
  (橙色=已修改/绿色=已新增/灰色=未跟踪)
- 文件预览:代码文本/图片/二进制三分支
- 主容器:工程选择(单工程隐藏)+ 面包屑导航 + 刷新

界面文案中英文同步补齐。
This commit is contained in:
2026-06-29 01:48:52 +08:00
parent b72df78462
commit 50aad375eb
12 changed files with 1550 additions and 3 deletions

View File

@@ -58,8 +58,33 @@
</div>
</div>
<!-- 主体两栏 -->
<div class="detail-grid">
<!-- Tab 导航(概览 / 文件浏览器) -->
<nav class="detail-tabs">
<button
class="tab-btn"
:class="{ 'tab-active': activeTab === 'overview' }"
type="button"
@click="activeTab = 'overview'"
>
{{ $t('projectDetail.tabOverview') }}
</button>
<button
class="tab-btn"
:class="{ 'tab-active': activeTab === 'files' }"
type="button"
@click="activeTab = 'files'"
>
{{ $t('fileExplorer.tabTitle') }}
</button>
</nav>
<!-- 文件浏览器 Tab -->
<section v-if="activeTab === 'files'" class="file-explorer-wrap">
<FileExplorer :project-id="projectId" />
</section>
<!-- 概览 Tab:原有主体两栏 -->
<div v-else class="detail-grid">
<!-- 左栏项目信息 -->
<section class="panel">
<div class="panel-header">
@@ -250,6 +275,7 @@ import { parseScores as parseScoresJson, assessmentClass, assessmentLabel as ass
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import ApprovalDialog from '@/components/project/ApprovalDialog.vue'
import FileExplorer from '@/components/project/FileExplorer.vue'
import { useConfirm } from '@/composables/useConfirm'
import { useRendered } from '@/composables/useMarkdown'
@@ -261,6 +287,9 @@ const logListRef = ref<HTMLElement | null>(null)
const showApprovalDialog = ref(false)
let unlisten: (() => void) | null = null
// Tab 导航:概览(项目信息/任务/工作流日志) vs 文件浏览器(Batch 10)。
const activeTab = ref<'overview' | 'files'>('overview')
// 确认弹层状态机抽至 composables/useConfirm(原 4 视图重复:Projects/ProjectDetail/Ideas/Settings)
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
@@ -658,6 +687,38 @@ onUnmounted(() => {
<style scoped>
.project-detail { padding: 16px 20px 20px; }
/* ===== Tab 导航(Batch 10 文件浏览器) ===== */
.detail-tabs {
display: flex;
gap: 4px;
border-bottom: 0.5px solid var(--df-border);
margin-bottom: 16px;
}
.tab-btn {
padding: 8px 16px;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
color: var(--df-text-dim);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
margin-bottom: -0.5px;
}
.tab-btn:hover {
color: var(--df-text);
}
.tab-btn.tab-active {
color: var(--df-text);
border-bottom-color: var(--df-accent);
}
/* 文件浏览器容器(占满高度,内部 FileExplorer 自管布局) */
.file-explorer-wrap {
height: calc(100vh - 340px);
min-height: 480px;
}
.page-header {
display: flex;
justify-content: space-between;
@@ -736,7 +797,7 @@ onUnmounted(() => {
.stage-active .stage-dot {
background: var(--df-accent);
color: #fff;
}
.stage-done .stage-dot {
background: var(--df-success);