Files
DevFlow/src/stores/project.ts
绝尘 2196c7748f 优化: P0 shell stdout修复+灵感路由+3工具新增+ai-md全局CSS+import路径统一
P0 修复:
- B-260615-37: shell.rs 补 Stdio::piped() 修复 run_command stdout/stderr 恒空
  (tokio 默认 inherit 导致 wait_with_output 读不到 pipe)

P1 功能:
- B-260615-36: 路由加 /ideas/:id + Ideas.vue 接 route.params.id + watch
  (修复项目详情点「来源灵感」跳空白页)
- F-260615-08: 新增 file_info 工具(元信息: exists/size/lines/binary/dir)
- F-260615-09: 新增 append_file 工具(追加写入, RiskLevel::Medium)
- F-260615-12: 新增 search_files 工具(文件名 glob 搜索,递归,限50条)
  + search_files_recursive 辅助函数 + i18n zh/en 3工具×2命名空间

DRY/重构:
- CR-260615-09: .ai-md 样式5份→全局 src/styles/ai-md.css(75行)
  AiChat.vue 删71行重复 + main.ts 引入 + typo修正(.a-md→.ai-md)
- 全量 import 路径统一为 @/ 别名(stores/composables/views ~28文件)
  vite.config.ts 加 resolve.alias.{ '@': '/src' }
- i18n 批量改进: store error fallback 11处中文→t() / ToolCard ARG_LABEL
  / useAiSend queue文案 / Dashboard 空态 / Ideas.vue null守卫
- cargo check 0 error / vue-tsc 0 error
2026-06-15 16:40:09 +08:00

144 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { reactive, computed } from 'vue'
import { listen } from '@tauri-apps/api/event'
import { createProjectsStore } from './project/projects'
import { createTasksStore } from './project/tasks'
import { createIdeasStore } from './project/ideas'
import { createWorkflowStore } from './project/workflow'
import { state, clearError } from './project/state'
import type { DfDataChangedPayload } from '@/api/types'
// ── barrel 组合器(零行为变更,纯重构) ──
//
// 原 project.ts 是上帝 store(项目/任务/灵感/工作流多领域 + 越层 invoke 审批 IPC)。
// 已拆为 src/stores/project/{projects,tasks,ideas,workflow}.ts 四子 store,
// 共享 src/stores/project/state.ts 单例 state。
//
// 本文件改为组合器:内部实例化四子 store + re-export 所有成员,
// 使现有 import { useProjectStore } 的 view 零改动。
// 越层 invoke(approve_human_approval / cancel_human_node) 已下沉到 src/api/workflow.ts。
function createStore() {
const projectsStore = createProjectsStore()
const tasksStore = createTasksStore()
const ideasStore = createIdeasStore()
const workflowStore = createWorkflowStore()
// ── AR-11方案A数据变更联动刷新 ──
// 后端 AI 工具(create/update/delete/restore/purge/bind_directory)执行成功后 emit
// "df-data-changed" { entity, action },前端按 entity 调对应 load 刷新列表,
// 取代工具执行后需手动刷新。
//
// CR-260615-26(R-1): task 分支不再无参 loadTasks(会把 store.tasks 冲成全量,破坏
// B-260615-29「store.tasks 反映当前筛选」契约)。改为读 _activeTaskProject:
// - undefined: Tasks 视图未挂载/从未设过筛选 → 不触发 task load(避免破坏筛选,
// 让 B-29 watch/handle 主导;此时改动由用户下次进 Tasks 自然刷新)
// - 'all' : 全量拉取
// - 具体 id : 按项目拉取
// 由 Tasks.vue 经 setActiveTaskProject 在 onMounted/watch 更新本变量。
let _activeTaskProject: string | undefined = undefined
function setActiveTaskProject(projectKey: string | undefined) {
_activeTaskProject = projectKey
}
let _dataChangedUnlisten: (() => void) | null = null
async function startDataChangedListener() {
if (_dataChangedUnlisten) return _dataChangedUnlisten
_dataChangedUnlisten = await listen<DfDataChangedPayload>('df-data-changed', (event) => {
const { entity } = event.payload
// 按 entity 调对应 load 刷新CR-260615-19:action 当前未消费,为未来 delete 本地移除等差异化刷新预留,统一 load 全量拉取)
if (entity === 'project') {
void projectsStore.loadProjects()
} else if (entity === 'task') {
// 无筛选状态(undefined)时不触发,保 B-29 筛选契约;有筛选则按当前 activeProject 拉
if (_activeTaskProject !== undefined) {
void tasksStore.loadTasks(_activeTaskProject === 'all' ? undefined : _activeTaskProject)
}
} else if (entity === 'idea') {
void ideasStore.loadIdeas()
}
})
return _dataChangedUnlisten
}
function stopDataChangedListener() {
if (_dataChangedUnlisten) {
try {
_dataChangedUnlisten()
} catch (e) {
console.error('停止数据变更监听失败:', e)
}
_dataChangedUnlisten = null
}
}
// ── 统计(跨领域,留在组合器) ──
const stats = computed(() => ({
ideas: state.ideas.length,
projects: state.projects.length,
activeTasks: state.tasks.filter(t => t.status === 'in_progress').length,
drafts: state.ideas.filter(i => i.status === 'draft').length,
}))
return reactive({
// 状态用 getter 实时读 state —— 直接 ideas: state.ideas 会快照引用,
// loadIdeas 等重新赋值 state.ideas 时返回对象的 ideas 属性不跟随,刷新后视图为空
get projects() { return state.projects },
get tasks() { return state.tasks },
get ideas() { return state.ideas },
get workflowExecutions() { return state.workflowExecutions },
get liveEvents() { return state.liveEvents },
get loading() { return state.loading },
get error() { return state.error },
get deletedProjects() { return state.deletedProjects },
clearError,
// project actions
loadProjects: projectsStore.loadProjects,
createProject: projectsStore.createProject,
importProject: projectsStore.importProject,
updateProject: projectsStore.updateProject,
deleteProject: projectsStore.deleteProject,
relocateProjectPath: projectsStore.relocateProjectPath,
loadDeletedProjects: projectsStore.loadDeletedProjects,
restoreProject: projectsStore.restoreProject,
purgeProject: projectsStore.purgeProject,
// task actions
loadTasks: tasksStore.loadTasks,
createTask: tasksStore.createTask,
updateTask: tasksStore.updateTask,
deleteTask: tasksStore.deleteTask,
// idea actions
loadIdeas: ideasStore.loadIdeas,
createIdea: ideasStore.createIdea,
updateIdea: ideasStore.updateIdea,
deleteIdea: ideasStore.deleteIdea,
evaluateIdea: ideasStore.evaluateIdea,
promoteIdea: ideasStore.promoteIdea,
// workflow actions
runWorkflow: workflowStore.runWorkflow,
loadWorkflowExecutions: workflowStore.loadWorkflowExecutions,
startEventListener: workflowStore.startEventListener,
stopEventListener: workflowStore.stopEventListener,
clearLiveEvents: workflowStore.clearLiveEvents,
approveHumanApproval: workflowStore.approveHumanApproval,
cancelHumanApproval: workflowStore.cancelHumanApproval,
// AR-11方案A数据变更联动刷新监听(后端工具执行后 emit df-data-changed)
startDataChangedListener,
stopDataChangedListener,
// CR-260615-26(R-1): Tasks 视图登记当前 activeProject 筛选,供 listener 按筛选拉取
setActiveTaskProject,
pendingApproval: computed(() => state.pendingApproval),
// computed
stats,
})
}
type ProjectStore = ReturnType<typeof createStore>
let _storeInstance: ProjectStore | null = null
/** 项目/任务/灵感/工作流 全局状态(单例,多组件复用同一 reactive 包装) */
export function useProjectStore(): ProjectStore {
if (_storeInstance) return _storeInstance
_storeInstance = createStore()
return _storeInstance
}