修复: 项目详情批量(任务列表联动刷新/工作流降级标注/中文git乱码/子目录错误行内/零工程空态/切tab保活) + 依赖图批量(节点渲染register/环高亮/防环防重/点击选中) + 销账
This commit is contained in:
@@ -84,9 +84,18 @@
|
||||
|
||||
<!-- 文件树视图 -->
|
||||
<div v-show="viewMode === 'tree'" class="explorer-tree">
|
||||
<div v-if="!currentModuleId" class="tree-loading">
|
||||
<!-- B4[PD-P2-8→P2-10]:区分加载中 vs 零工程——currentModuleId 为空且已加载完工程列表
|
||||
说明无工程,显示空态引导(添加工程 CTA,对齐概览模块空态),而非无限 spinner -->
|
||||
<div v-if="!currentModuleId && !mLoaded" class="tree-loading">
|
||||
<span class="spinner"></span>
|
||||
</div>
|
||||
<div v-else-if="!currentModuleId" class="module-empty">
|
||||
<div class="module-empty-icon">📦</div>
|
||||
<p class="module-empty-hint">{{ $t('projectDetail.modulesEmptyHint') }}</p>
|
||||
<div class="module-empty-actions">
|
||||
<button class="btn btn-primary btn-sm" type="button" @click="openAddModule">{{ $t('fileExplorer.addModule') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<FileTree
|
||||
v-else
|
||||
:module-id="currentModuleId"
|
||||
@@ -106,6 +115,7 @@
|
||||
<GitChanges
|
||||
v-if="currentModuleId"
|
||||
:module-id="currentModuleId"
|
||||
:refresh-key="gitStatusRefreshKey"
|
||||
@select-file="onChangeFileSelect"
|
||||
/>
|
||||
</div>
|
||||
@@ -198,7 +208,7 @@
|
||||
* 父组件(ProjectDetail)传入 projectId,本组件按 id 拉工程列表(moduleApi.listProjectModules),
|
||||
* 单工程自动选中;多工程默认选首个 + 提供下拉切换。
|
||||
*/
|
||||
import { ref, computed, watch, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, watch, reactive, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
|
||||
import { moduleApi, type ProjectModuleRecord, type FileTreeEntry, type GitStatusResult } from '@/api/module'
|
||||
@@ -217,6 +227,9 @@ const modules = ref<ProjectModuleRecord[]>([])
|
||||
const currentModuleId = ref<string>('')
|
||||
const refreshing = ref(false)
|
||||
const moduleDropdownOpen = ref(false)
|
||||
// B4[PD-P2-8→P2-10]:工程列表是否已加载完(首次拉取 resolve)。currentModuleId 为空时用它
|
||||
// 区分「还在加载中(转圈)」vs「确实无工程(空态引导)」,避免无工程时无限 spinner。
|
||||
const mLoaded = ref(false)
|
||||
|
||||
// 工程 CRUD 弹窗
|
||||
const showModuleModal = ref(false)
|
||||
@@ -277,6 +290,9 @@ async function loadModules() {
|
||||
} catch (e) {
|
||||
console.error('[FileExplorer] 加载工程列表失败', e)
|
||||
modules.value = []
|
||||
} finally {
|
||||
// B4[PD-P2-8→P2-10]:标记加载完成(成功/失败都算),供空态判定
|
||||
mLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +380,15 @@ function onDetach() {
|
||||
// ── 视图模式切换:文件树 / Git 变更 ──
|
||||
const viewMode = ref<'tree' | 'changes'>('tree')
|
||||
const gitStatusData = ref<GitStatusResult | null>(null)
|
||||
// B2[PD-P1-5]:git 状态刷新信号(自增)。AI 写文件后(下)传 GitChanges 触发重拉,
|
||||
// 替代旧的「tree→changes 切换触发 watch」hack;同时自身重拉 gitStatusData 保徽标计数。
|
||||
const gitStatusRefreshKey = ref(0)
|
||||
// 本组件是否可见(KeepAlive 保活下 onDeactivated 置 false,onActivated 置 true)。
|
||||
// 保活后组件在切走 overview 时仍存活,其 df-data-changed 监听会在后台触发;
|
||||
// 用此守卫避免「用户切到 overview 时文件 tab 后台偷偷切到 changes 视图」。
|
||||
const viewVisible = ref(true)
|
||||
onActivated(() => { viewVisible.value = true })
|
||||
onDeactivated(() => { viewVisible.value = false })
|
||||
|
||||
const changedCount = computed(() => gitStatusData.value?.changed_files.length ?? 0)
|
||||
|
||||
@@ -396,16 +421,22 @@ onUnmounted(() => document.removeEventListener('click', closeDropdown))
|
||||
// AI 工具写入文件后(df-data-changed entity=file)自动跳转变更视图 + 刷新
|
||||
let _unlistenDataChanged: UnlistenFn | null = null
|
||||
onMounted(async () => {
|
||||
_unlistenDataChanged = await listen<{ entity: string; action: string }>('df-data-changed', (e) => {
|
||||
_unlistenDataChanged = await listen<{ entity: string; action: string }>('df-data-changed', async (e) => {
|
||||
if (e.payload.entity !== 'file') return
|
||||
// 刷新 git 状态缓存,使变更计数角标即时更新
|
||||
gitStatusData.value = null
|
||||
if (viewMode.value === 'changes') {
|
||||
// 已在变更视图,触发 GitChanges 重新拉取(切一次 viewMode 触发 watch)
|
||||
viewMode.value = 'tree'
|
||||
viewMode.value = 'changes'
|
||||
} else {
|
||||
// 不在变更视图,自动切换到变更视图,让用户看到 AI 写入的内容
|
||||
if (!currentModuleId.value) return
|
||||
// B2[PD-P1-5]:重拉 git 状态而非置 null —— 置 null 会让 changedCount 徽标瞬闪为 0,
|
||||
// 且 switchToChanges 短路 !gitStatusData,事件后依赖旧缓存不再重新拉取。
|
||||
try {
|
||||
gitStatusData.value = await moduleApi.getModuleGitStatus(currentModuleId.value)
|
||||
} catch {
|
||||
gitStatusData.value = null
|
||||
}
|
||||
// 通知 GitChanges 重拉(已在 changes 视图立即生效;未挂载时挂载即拉最新)。
|
||||
// 后端 git status 有 5s TTL 缓存(module.rs GIT_STATUS_CACHE_TTL),写后立即重拉可能拿到
|
||||
// 缓存旧值,5s 内随用户操作/再次刷新自然更新;跨层绕过缓存留待设计(不强行改事件协议)。
|
||||
gitStatusRefreshKey.value++
|
||||
if (viewMode.value !== 'changes' && viewVisible.value) {
|
||||
// 不在变更视图且本组件可见:自动切换到变更视图,让用户看到 AI 写入的内容
|
||||
viewMode.value = 'changes'
|
||||
}
|
||||
})
|
||||
@@ -757,6 +788,35 @@ async function onRemoveModule() {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* B4[PD-P2-8→P2-10]:零工程空态引导(对齐概览模块空态视觉) */
|
||||
.module-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 32px 16px;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.module-empty-icon {
|
||||
font-size: 28px;
|
||||
opacity: 0.6;
|
||||
line-height: 1;
|
||||
}
|
||||
.module-empty-hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--df-text-dim);
|
||||
line-height: 1.6;
|
||||
max-width: 300px;
|
||||
}
|
||||
.module-empty-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.tree-loading .spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
Reference in New Issue
Block a user