Skip to content

Commit 9271a55

Browse files
committed
```
feat(frontend): 添加嵌套项目加载错误提示功能 - 在McpIndexStatusDrawer和ZhiIndexPanel组件中添加nestedError响应式变量 - 实现嵌套项目加载失败时的错误捕获和前端显性提示 - 更新watch监听逻辑,支持项目路径变化时重置状态并重新加载 - 添加错误提示样式和UI展示,提升用户体验 fix(rust): 完善嵌套项目状态获取的错误处理 - 在get_acemcp_project_with_nested命令中添加目录存在性校验 - 在get_project_with_nested_status方法中增强错误处理机制 - 统一使用配置中的排除模式过滤嵌套目录,与索引阶段保持一致 - 改进文件系统操作的错误传播,提供更准确的错误信息 feat(frontend): 优化嵌套项目统计计算逻辑 - 重构pendingFiles和failedFiles计算属性,支持包含嵌套项目时的汇总统计 - 修复切换项目时可能出现的数据残留问题 - 优化watch监听器,支持同时监听show和projectRoot变化 ```
1 parent 886bf5b commit 9271a55

File tree

4 files changed

+160
-59
lines changed

4 files changed

+160
-59
lines changed

src/frontend/components/popup/McpIndexStatusDrawer.vue

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ const showOnlyPending = ref(false)
6161
// 嵌套项目状态
6262
const nestedStatus = ref<ProjectWithNestedStatus | null>(null)
6363
const loadingNested = ref(false)
64+
// 记录嵌套项目加载错误(用于前端显性提示)
65+
const nestedError = ref<string | null>(null)
6466
6567
const message = useMessage()
6668
@@ -305,6 +307,7 @@ async function fetchNestedStatus() {
305307
return
306308
307309
loadingNested.value = true
310+
nestedError.value = null
308311
try {
309312
const result = await invoke<ProjectWithNestedStatus>('get_acemcp_project_with_nested', {
310313
projectRootPath: props.projectRoot,
@@ -313,6 +316,7 @@ async function fetchNestedStatus() {
313316
}
314317
catch (err) {
315318
console.error('获取嵌套项目状态失败:', err)
319+
nestedError.value = String(err)
316320
}
317321
finally {
318322
loadingNested.value = false
@@ -350,11 +354,16 @@ function getNestedStatusText(np: NestedProjectInfo): string {
350354
return `${status.indexed_files}/${status.total_files}`
351355
}
352356
353-
// 当弹窗打开且有项目路径时,自动加载文件状态和嵌套项目状态
357+
// 当弹窗打开或项目路径变化时,刷新文件状态与嵌套项目状态
354358
watch(
355-
() => props.show,
356-
(visible) => {
357-
if (visible && props.projectRoot) {
359+
() => [props.show, props.projectRoot],
360+
([visible, root]) => {
361+
if (visible && root) {
362+
// 重置状态,避免切换项目时显示旧数据
363+
filesStatus.value = null
364+
nestedStatus.value = null
365+
filesError.value = null
366+
nestedError.value = null
358367
fetchFilesStatus()
359368
fetchNestedStatus()
360369
}
@@ -526,7 +535,7 @@ function handleCopyPath() {
526535
</div>
527536

528537
<!-- 嵌套项目区域(如果有) -->
529-
<div v-if="hasNestedProjects" class="nested-projects-card">
538+
<div v-if="hasNestedProjects || nestedError" class="nested-projects-card">
530539
<div class="nested-card__header">
531540
<div class="i-carbon-folder-parent nested-card__icon" />
532541
<span class="nested-card__title">Git 子项目</span>
@@ -539,6 +548,11 @@ function handleCopyPath() {
539548
<div class="skeleton-text" />
540549
</div>
541550
</div>
551+
<!-- 错误提示 -->
552+
<div v-else-if="nestedError" class="nested-error">
553+
<div class="i-carbon-warning-alt" />
554+
<span>{{ nestedError }}</span>
555+
</div>
542556
<!-- 嵌套项目列表 -->
543557
<div v-else class="nested-card__list">
544558
<div
@@ -1145,6 +1159,17 @@ function handleCopyPath() {
11451159
color: rgba(52, 211, 153, 0.95);
11461160
}
11471161
1162+
.nested-error {
1163+
display: flex;
1164+
align-items: center;
1165+
gap: 8px;
1166+
padding: 8px 10px;
1167+
border-radius: 8px;
1168+
background: rgba(248, 113, 113, 0.08);
1169+
color: rgba(248, 113, 113, 0.9);
1170+
font-size: 12px;
1171+
}
1172+
11481173
.nested-card__list {
11491174
display: flex;
11501175
flex-direction: column;

src/frontend/components/popup/ZhiIndexPanel.vue

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ const showSyncMenu = ref(false)
5757
// 嵌套项目状态
5858
const nestedStatus = ref<ProjectWithNestedStatus | null>(null)
5959
const loadingNested = ref(false)
60+
// 记录嵌套项目加载错误(用于前端显性提示)
61+
const nestedError = ref<string | null>(null)
6062
6163
// ==================== 计算属性 ====================
6264
@@ -141,11 +143,31 @@ const indexedFiles = computed(() => {
141143
return props.projectStatus?.indexed_files ?? 0
142144
})
143145
144-
// 待处理文件数
145-
const pendingFiles = computed(() => props.projectStatus?.pending_files ?? 0)
146+
// 待处理文件数(包含嵌套项目时汇总)
147+
const pendingFiles = computed(() => {
148+
if (hasNestedProjects.value && nestedStatus.value) {
149+
let pending = nestedStatus.value.root_status.pending_files
150+
for (const np of nestedStatus.value.nested_projects) {
151+
if (np.index_status)
152+
pending += np.index_status.pending_files
153+
}
154+
return pending
155+
}
156+
return props.projectStatus?.pending_files ?? 0
157+
})
146158
147-
// 失败文件数
148-
const failedFiles = computed(() => props.projectStatus?.failed_files ?? 0)
159+
// 失败文件数(包含嵌套项目时汇总)
160+
const failedFiles = computed(() => {
161+
if (hasNestedProjects.value && nestedStatus.value) {
162+
let failed = nestedStatus.value.root_status.failed_files
163+
for (const np of nestedStatus.value.nested_projects) {
164+
if (np.index_status)
165+
failed += np.index_status.failed_files
166+
}
167+
return failed
168+
}
169+
return props.projectStatus?.failed_files ?? 0
170+
})
149171
150172
// 格式化最后同步时间
151173
const lastSyncTime = computed(() => {
@@ -185,6 +207,7 @@ async function fetchNestedStatus() {
185207
return
186208
187209
loadingNested.value = true
210+
nestedError.value = null
188211
try {
189212
const result = await invoke<ProjectWithNestedStatus>('get_acemcp_project_with_nested', {
190213
projectRootPath: props.projectRoot,
@@ -193,6 +216,7 @@ async function fetchNestedStatus() {
193216
}
194217
catch (err) {
195218
console.error('获取嵌套项目状态失败:', err)
219+
nestedError.value = String(err)
196220
}
197221
finally {
198222
loadingNested.value = false
@@ -249,6 +273,7 @@ function getNestedStatusText(np: NestedProjectInfo): string {
249273
// 监听项目路径变化,重新加载嵌套状态
250274
watch(() => props.projectRoot, () => {
251275
nestedStatus.value = null
276+
nestedError.value = null
252277
if (isExpanded.value)
253278
fetchNestedStatus()
254279
})
@@ -339,7 +364,7 @@ onMounted(() => {
339364
<n-collapse-transition :show="isExpanded">
340365
<div class="panel-content">
341366
<!-- 嵌套项目区域(如果有) -->
342-
<div v-if="hasNestedProjects" class="nested-projects-section">
367+
<div v-if="hasNestedProjects || nestedError" class="nested-projects-section">
343368
<div class="section-header">
344369
<div class="i-carbon-folder-parent section-icon" />
345370
<span class="section-title">Git 子项目</span>
@@ -351,6 +376,11 @@ onMounted(() => {
351376
<div class="skeleton-text" />
352377
</div>
353378
</div>
379+
<!-- 错误提示 -->
380+
<div v-else-if="nestedError" class="nested-error">
381+
<div class="i-carbon-warning-alt" />
382+
<span>{{ nestedError }}</span>
383+
</div>
354384
<!-- 嵌套项目列表 -->
355385
<div v-else class="nested-list">
356386
<div
@@ -599,6 +629,18 @@ onMounted(() => {
599629
gap: 8px;
600630
}
601631
632+
/* 嵌套项目错误提示 */
633+
.nested-error {
634+
display: flex;
635+
align-items: center;
636+
gap: 8px;
637+
padding: 8px 10px;
638+
border-radius: 8px;
639+
background: rgba(248, 113, 113, 0.08);
640+
color: rgba(248, 113, 113, 0.9);
641+
font-size: 12px;
642+
}
643+
602644
.skeleton-item {
603645
display: flex;
604646
align-items: center;

src/rust/mcp/tools/acemcp/commands.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,12 @@ pub async fn get_acemcp_project_files_status(
593593
/// 用于前端展示包含多个 Git 子仓库的项目结构
594594
#[tauri::command]
595595
pub fn get_acemcp_project_with_nested(project_root_path: String) -> Result<ProjectWithNestedStatus, String> {
596-
Ok(AcemcpTool::get_project_with_nested_status(project_root_path))
596+
// 关键校验:目录不存在时直接返回错误
597+
if !check_directory_exists(project_root_path.clone())? {
598+
return Err(format!("项目根目录不存在: {}", project_root_path));
599+
}
600+
AcemcpTool::get_project_with_nested_status(project_root_path)
601+
.map_err(|e| e.to_string())
597602
}
598603

599604
/// 手动触发索引更新

src/rust/mcp/tools/acemcp/mcp.rs

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -309,73 +309,102 @@ impl AcemcpTool {
309309
///
310310
/// 该方法会扫描项目根目录下的直接子目录,检测哪些是独立的 Git 仓库,
311311
/// 并返回每个子项目的索引状态。用于前端展示多项目结构。
312-
pub fn get_project_with_nested_status(project_root_path: String) -> ProjectWithNestedStatus {
312+
pub fn get_project_with_nested_status(project_root_path: String) -> Result<ProjectWithNestedStatus> {
313313
let root_path = PathBuf::from(&project_root_path);
314+
// 关键校验:路径不存在时直接返回错误,避免前端静默失败
315+
if !root_path.exists() || !root_path.is_dir() {
316+
anyhow::bail!("项目根目录不存在: {}", project_root_path);
317+
}
314318
let root_status = get_project_status(&project_root_path);
315319

316320
let mut nested_projects = Vec::new();
317321
let mut regular_directories = Vec::new();
322+
323+
// 从配置读取排除模式,用于过滤嵌套目录(与索引阶段保持一致)
324+
let exclude_patterns = crate::config::load_standalone_config()
325+
.ok()
326+
.and_then(|c| c.mcp_config.acemcp_exclude_patterns)
327+
.unwrap_or_else(|| {
328+
vec![
329+
"node_modules".to_string(),
330+
".git".to_string(),
331+
"target".to_string(),
332+
"dist".to_string(),
333+
]
334+
});
335+
let exclude_globset = if exclude_patterns.is_empty() {
336+
None
337+
} else {
338+
match build_exclude_globset(&exclude_patterns) {
339+
Ok(gs) => Some(gs),
340+
Err(e) => {
341+
log_debug!("构建排除模式失败,将忽略目录过滤: {}", e);
342+
None
343+
}
344+
}
345+
};
318346

319347
// 扫描直接子目录(仅第一层)
320-
if let Ok(entries) = fs::read_dir(&root_path) {
321-
for entry in entries.flatten() {
322-
let path = entry.path();
323-
if !path.is_dir() {
324-
continue;
325-
}
326-
327-
let dir_name = path.file_name()
328-
.and_then(|n| n.to_str())
329-
.unwrap_or("")
330-
.to_string();
331-
332-
// 跳过隐藏目录和常见排除目录
333-
if dir_name.starts_with('.')
334-
|| dir_name == "node_modules"
335-
|| dir_name == "target"
336-
|| dir_name == "dist"
337-
{
338-
continue;
339-
}
340-
341-
// 检测是否是 Git 仓库
342-
let git_dir = path.join(".git");
343-
let is_git_repo = git_dir.exists() && git_dir.is_dir();
348+
let entries = fs::read_dir(&root_path)
349+
.map_err(|e| anyhow::anyhow!("读取项目根目录失败: {}", e))?;
350+
for entry in entries {
351+
let entry = entry.map_err(|e| anyhow::anyhow!("读取目录条目失败: {}", e))?;
352+
let path = entry.path();
353+
if !path.is_dir() {
354+
continue;
355+
}
356+
357+
let dir_name = path.file_name()
358+
.and_then(|n| n.to_str())
359+
.unwrap_or("")
360+
.to_string();
361+
362+
// 跳过隐藏目录
363+
if dir_name.starts_with('.') {
364+
continue;
365+
}
366+
// 使用配置排除目录/路径(支持 glob)
367+
if should_exclude(&path, &root_path, exclude_globset.as_ref()) {
368+
continue;
369+
}
370+
371+
// 检测是否是 Git 仓库
372+
let git_dir = path.join(".git");
373+
let is_git_repo = git_dir.exists() && git_dir.is_dir();
374+
375+
if is_git_repo {
376+
// 获取子项目的索引状态
377+
let sub_path_str = normalize_project_path(&path.to_string_lossy());
378+
let sub_status = get_project_status(&sub_path_str);
344379

345-
if is_git_repo {
346-
// 获取子项目的索引状态
347-
let sub_path_str = normalize_project_path(&path.to_string_lossy());
348-
let sub_status = get_project_status(&sub_path_str);
349-
350-
// 粗略估计文件数量(使用索引状态中的 total_files,如果没有则设为 0)
351-
let file_count = if sub_status.status != IndexStatus::Idle {
352-
sub_status.total_files
353-
} else {
354-
0
355-
};
356-
357-
nested_projects.push(NestedProjectInfo {
358-
relative_path: dir_name,
359-
absolute_path: sub_path_str,
360-
is_git_repo: true,
361-
index_status: Some(sub_status),
362-
file_count,
363-
});
380+
// 粗略估计文件数量(使用索引状态中的 total_files,如果没有则设为 0)
381+
let file_count = if sub_status.status != IndexStatus::Idle {
382+
sub_status.total_files
364383
} else {
365-
regular_directories.push(dir_name);
366-
}
384+
0
385+
};
386+
387+
nested_projects.push(NestedProjectInfo {
388+
relative_path: dir_name,
389+
absolute_path: sub_path_str,
390+
is_git_repo: true,
391+
index_status: Some(sub_status),
392+
file_count,
393+
});
394+
} else {
395+
regular_directories.push(dir_name);
367396
}
368397
}
369398

370399
// 按字母顺序排序
371400
nested_projects.sort_by(|a, b| a.relative_path.cmp(&b.relative_path));
372401
regular_directories.sort();
373402

374-
ProjectWithNestedStatus {
403+
Ok(ProjectWithNestedStatus {
375404
root_status,
376405
nested_projects,
377406
regular_directories,
378-
}
407+
})
379408
}
380409
}
381410

0 commit comments

Comments
 (0)