优化: 清理无意义注释(走查编号前缀/过时历史标注/死代码注释)

This commit is contained in:
lxy
2026-08-15 18:05:43 +08:00
parent 5a4c821629
commit 5ac815b9ca
137 changed files with 735 additions and 737 deletions
+1 -1
View File
@@ -419,7 +419,7 @@ function decidedClass(d: string): string {
return d === 'human' ? 'decided-human' : 'decided-auto'
}
// SA-2:对话角色 i18n 化(原硬编码 '🧑 用户'/'🤖 助手'/'🔧 工具'/'⚙️ 系统' 不随 locale)
// 对话角色 i18n 化(原硬编码 '🧑 用户'/'🤖 助手'/'🔧 工具'/'⚙️ 系统' 不随 locale)
// user/assistant/tool 映射固定 key,其他未知 role 回退到 '⚙️ 系统'(含 emoji,与其他四态视觉一致)
function convRoleLabel(role: string): string {
if (role === 'user') return t('auditLog.convRoleUser')
+12 -12
View File
@@ -228,7 +228,7 @@ function statusLabelKey(status: IdeaStatus): string {
return statusOptions.find(s => s.value === status)?.labelKey ?? status
}
// ── 后端查询构造(F-260621-02:status/keyword/order_by 下沉后端 WHERE) ──
// ── 后端查询构造status/keyword/order_by 下沉后端 WHERE) ──
//
// status 下沉:单一状态(all→不限 / promoted→promoted)走后端精确匹配;
// hot(按 score≥80)与 pending(多状态 draft+pending_review)无法映射单一后端 status,
@@ -249,7 +249,7 @@ function buildIdeaQuery(): IdeaQuery {
return q
}
// WK-4:按当前视图查询加载(登记 setActiveIdeaQuery 供 df-data-changed 联动刷新复用同一筛选,
// 按当前视图查询加载(登记 setActiveIdeaQuery 供 df-data-changed 联动刷新复用同一筛选,
// 避免 promote/relate/监听触发无参 loadIdeas 全量冲掉当前筛选视图)
function loadCurrentView() {
store.setActiveIdeaQuery(buildIdeaQuery())
@@ -331,14 +331,14 @@ watch([filteredIdeas, () => selectedId.value], () => {
if (targetPage !== page.value) page.value = targetPage
})
// WK-12:分页越界自愈(删除/筛选变化后 page 超界 → 钳制到最后一页,防空白页)
// 分页越界自愈(删除/筛选变化后 page 超界 → 钳制到最后一页,防空白页)
watch([filteredIdeas, pageSize], () => {
if (pageSize.value <= 0) return
const maxPage = Math.max(1, Math.ceil(filteredIdeas.value.length / pageSize.value))
if (page.value > maxPage) page.value = maxPage
})
// B-260615-25:灵感描述 Markdown 渲染已下沉至 IdeaDetail 子组件(共享模块单例渲染器)
// 灵感描述 Markdown 渲染已下沉至 IdeaDetail 子组件(共享模块单例渲染器)
// 评分档位 class(高/中/低 三色标),阈值统一走 scoreTier(单一来源,消除 4 处重复)
function scoreClass(score: number | null) {
@@ -369,7 +369,7 @@ async function confirmCapture() {
tags: JSON.stringify(tags),
source: newIdeaSource.value.trim() || undefined,
})
// WK-18:createIdea 返 null=失败(state.error 已置,顶部 error-banner 展示);成功关弹窗 + toast
// createIdea 返 null=失败(state.error 已置,顶部 error-banner 展示);成功关弹窗 + toast
if (created) {
showCaptureModal.value = false
Message.success(t('ideas.createdToast'))
@@ -384,7 +384,7 @@ async function deleteCurrentIdea() {
if (!await confirmDialog(t('ideas.confirmDelete', { title: currentIdea.value.title }))) return
deleting.value = true
try {
// WK-7:删除失败不清理选中态(错误走 error-banner),仅成功时清空详情
// 删除失败不清理选中态(错误走 error-banner),仅成功时清空详情
const ok = await store.deleteIdea(currentIdea.value.id)
if (ok) selectedId.value = null
} finally {
@@ -397,7 +397,7 @@ async function promoteToProject() {
promoting.value = true
try {
const res = await store.promoteIdea(currentIdea.value.id)
// WK-4:promote 后按当前视图重载(store 不再内部全量 loadIdeas 冲掉筛选)
// promote 后按当前视图重载(store 不再内部全量 loadIdeas 冲掉筛选)
await loadCurrentView()
router.push(`/projects/${res.project_id}`)
} catch (e: any) {
@@ -433,11 +433,11 @@ async function evaluateCurrentIdea() {
async function onStatusChange(newStatus: IdeaStatus) {
if (!currentIdea.value) return
// WK-5:promoted 走独立立项流程(promoteToProject),状态菜单/下拉已排除,此处兜底禁止直接置
// promoted 走独立立项流程(promoteToProject),状态菜单/下拉已排除,此处兜底禁止直接置
if (newStatus === 'promoted') return
// ⑥ 状态切换确认:落到 promoted/rejected/archived 等终态(或离开 approved 失去立项入口)
// 需用户确认,避免误点。中性流转(draft↔pending_review↔approved)直接落库。
// (WK-5:上方已拦截 promoted,此处不再重复比对,TS 收窄后 'promoted' 无交集)
// 上方已拦截 promoted,此处不再重复比对,TS 收窄后 'promoted' 无交集)
const from = currentIdea.value.status
const needsConfirm = newStatus === 'rejected' || newStatus === 'archived'
|| (from === 'approved' && newStatus !== 'approved')
@@ -453,7 +453,7 @@ async function onStatusChange(newStatus: IdeaStatus) {
}
// 接收 IdeaDetail 子组件 emit 的 'update-desc'(描述编辑保存)
// WK-16:返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
// 返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
async function onUpdateDesc(desc: string): Promise<boolean> {
if (!currentIdea.value) return false
try {
@@ -470,12 +470,12 @@ async function onUpdateDesc(desc: string): Promise<boolean> {
async function onUpdateRelated(ids: string[]) {
if (!currentIdea.value) return
await store.relateIdeas(currentIdea.value.id, ids)
// WK-4:relate 后端原子更新受影响灵感,按当前视图重载(store 不再内部全量 loadIdeas 冲筛选)
// relate 后端原子更新受影响灵感,按当前视图重载(store 不再内部全量 loadIdeas 冲筛选)
await loadCurrentView()
}
// 接收 IdeaDetail 子组件 emit 的 'update-tags'(标签编辑保存)
// WK-16:返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
// 返 boolean(成功 true/失败 false),失败 toast;子组件据 false 保持编辑态。
async function onUpdateTags(tags: string): Promise<boolean> {
if (!currentIdea.value) return false
try {
+3 -3
View File
@@ -241,7 +241,7 @@ function getCategoryCount(key: string): number {
// ===== 详情选中 + 加载 =====
const selectedId = ref<string | null>(null)
const detail = ref<KnowledgeDetailPayload | null>(null)
// B-260615-25:Markdown 渲染器预热保留在父级(模块单例,与 AiChat/TaskDetail/KnowledgeDetail 共享),
// Markdown 渲染器预热保留在父级(模块单例,与 AiChat/TaskDetail/KnowledgeDetail 共享),
// 详情内容渲染由 KnowledgeDetail 子组件通过 useRendered 消费,此处仅 ensureLoaded 预热不阻塞首屏
const { ensureLoaded } = useRendered(() => '')
@@ -286,8 +286,8 @@ async function onDetailSave(payload: { id: string; data: { title: string; conten
// ===== 审核操作(收件箱) =====
// P0-②:发布/拒绝/归档后自动选中下一条(而非清空详情),减少审核者来回点击
// WK-2:index 须在 store 操作前快照(操作后 candidates 已 filter 移除 currentId,findIndex 恒 -1)
// WK-3:操作失败(updateStatus/archive 返 false)toast 报错 + 中止推进(不 selectNext,不清详情)
// index 须在 store 操作前快照(操作后 candidates 已 filter 移除 currentId,findIndex 恒 -1)
// 操作失败(updateStatus/archive 返 false)toast 报错 + 中止推进(不 selectNext,不清详情)
async function publishCurrent() {
if (!detail.value) return
const currentId = detail.value.knowledge.id
+6 -7
View File
@@ -556,14 +556,13 @@ const currentProject = computed(() =>
const loading = computed(() => store.projects.length === 0 && !store.error)
// 状态文案/阶段进度统一走 ../constants/project
// B-260615-25:项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
// 项目描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
// useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
const { rendered: renderedDesc, ensureLoaded } = useRendered(
() => currentProject.value?.description ?? '',
)
const stageKey = computed(() => currentProject.value?.status ?? 'planning')
const statusLabel = computed(() => projectStatusLabel(stageKey.value))
// const currentStageIndex = computed(() => projectStageInfo(stageKey.value).stepIndex)
// ── 来源灵感(晋升携带评估结论回溯)──
// ProjectRecord.idea_id 存灵感 id(promote_idea 写入)。
@@ -850,7 +849,7 @@ async function onScanSubmodules() {
}
// ── 工作流 ──
// demoDag + runDemoWorkflow 已下线(R-PD-2):"script" 节点不再注册到 NodeRegistry,
// demoDag + runDemoWorkflow 已下线"script" 节点不再注册到 NodeRegistry,
// 前端构造含 script 节点的 DagDef 会在后端 build_dag 失败报错。
// 工作流执行日志面板保留,实时事件展示能力不依赖 demoDag。
// 待真实工作流需求落地(独立 BuildNode + 审批链)时重建演示入口。
@@ -868,7 +867,7 @@ const formattedEvents = computed<LogEntry[]>(() => {
const type = ev.type ?? 'unknown'
const level = type.includes('error') || type.includes('fail') ? 'error'
: type.includes('warn') ? 'warn' : 'info'
// 用事件入数组时固化的时间戳(FR-C1),非 computed 重算的当前时刻
// 用事件入数组时固化的时间戳,非 computed 重算的当前时刻
const time = new Date(evt._ts).toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', second: '2-digit' })
const message = ev.label
? `[${ev.label}] ${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}`
@@ -1013,8 +1012,8 @@ async function handleImportDir() {
// ── 审批处理 ──
// 审批对话框 UI + 单/多选决策逻辑抽离至 components/project/ApprovalDialog.vue
// handleApproval/handleApprovalMulti/isMultipleSelect/multiDecisions 已迁移;
// submitting(UX-260618-17)在子组件自治,父级 submitting 仅用于 submitNewTask
// P0-C:ApprovalDialog + watch pendingApproval + handleCancelApproval 已提至 App.vue(全局),
// submitting在子组件自治,父级 submitting 仅用于 submitNewTask
// ApprovalDialog + watch pendingApproval + handleCancelApproval 已提至 App.vue(全局),
// TaskDetail 等无 ApprovalDialog 的页面自动受益(原局部渲染致 HumanNode 超时失败)
// ── 删除项目(软删 → 回收站,可恢复)──
@@ -1115,7 +1114,7 @@ onUnmounted(() => {
gap: var(--df-gap-grid);
}
/* B-260615-31:字段同行布局(label 固定宽 + 值占余),描述字段 info-block 保持块状)
/* 字段同行布局(label 固定宽 + 值占余),描述字段 info-block 保持块状)
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
此处仅保留本组件特有覆盖。 */
.info-item a.idea-link {
+2 -2
View File
@@ -219,7 +219,7 @@ const pagedProjects = computed(() => {
return store.projects.slice(start, start + pageSize.value)
})
// WK-12:分页越界自愈(删除/回收后 store.projects 数量变化,page 超界 → 钳制到最后一页,防空白页)
// 分页越界自愈(删除/回收后 store.projects 数量变化,page 超界 → 钳制到最后一页,防空白页)
watch([() => store.projects, pageSize], () => {
if (pageSize.value <= 0) return
const maxPage = Math.max(1, Math.ceil(store.projects.length / pageSize.value))
@@ -598,6 +598,6 @@ async function runImport() {
}
.toast-info { background: var(--df-accent); color: #fff; }
.toast-error { background: var(--df-danger); color: #fff; }
/* P0-2: ToastType 新增 'success' 兜底样式(本视图当前未用,补全避免裸类) */
/* ToastType 新增 'success' 兜底样式(本视图当前未用,补全避免裸类) */
.toast-success { background: var(--df-success); color: #fff; }
</style>
+21 -21
View File
@@ -312,14 +312,14 @@ import { useTaskBatchActions } from '@/composables/task/useTaskBatchActions'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
// B-41: 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
// 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
const store = useProjectStore()
const loading = ref(true)
const errorMsg = ref('')
const task = ref<TaskRecord | null>(null)
const projects = ref<ProjectRecord[]>([])
// F-260619-01:灵感列表用于解析 task.idea_id → 灵感 title(独立入口,同 projects 模式)
// 灵感列表用于解析 task.idea_id → 灵感 title(独立入口,同 projects 模式)
const ideas = ref<IdeaRecord[]>([])
const advancing = ref(false)
@@ -471,7 +471,7 @@ async function setChildPriority(child: TaskRecord, priority: number) {
}
}
// F-260805:子任务新建小弹窗
// 子任务新建小弹窗
const showSubtaskModal = ref(false)
const subtaskTitle = ref('')
const subtaskDesc = ref('')
@@ -519,7 +519,7 @@ async function confirmSubtask() {
const wfAdvancing = ref(false)
const wfExecId = ref<string | null>(null)
const wfResult = ref<'completed' | 'failed' | null>(null)
// SW-260618-21: 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
// 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
let _wfResultTimer: ReturnType<typeof setTimeout> | null = null
/** 重置工作流推进相关 UI 态(切任务/refresh/手动推进成功后调用。
@@ -531,7 +531,7 @@ function resetWorkflowUi() {
if (_wfResultTimer) { clearTimeout(_wfResultTimer); _wfResultTimer = null }
}
// B-41: 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
// 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
// 派生节点状态/进度,与 ProjectDetail 同源(store.workflowProgress),替代原本地 onEvent 逐事件累加。
const wfProgress = computed(() => store.workflowProgress(wfExecId.value))
const wfNodeStatuses = computed(() => wfProgress.value.nodeStatuses)
@@ -567,7 +567,7 @@ async function refreshWorkflowDag(execId: string) {
const taskId = computed(() => route.params.id as string)
// B-24:任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
// 任务描述 Markdown 渲染(复用 AiChat 同款渲染器,模块级单例),useRendered 封装
// computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
const { rendered: renderedDesc, ensureLoaded } = useRendered(
() => task.value?.description ?? '',
@@ -604,7 +604,7 @@ async function measureDescHeight() {
descOverflow.value = el.scrollHeight > DESC_COLLAPSE_PX
}
// P2-10: 描述编辑(对齐 IdeaDetail 模式;taskApi.update(id, 'description', value))
// 描述编辑(对齐 IdeaDetail 模式;taskApi.update(id, 'description', value))
const editingDesc = ref(false)
const editDesc = ref('')
const savingDesc = ref(false)
@@ -636,7 +636,7 @@ const projectName = computed(() => {
return p?.name ?? task.value?.project_id ?? '—'
})
// F-260619-01:解析 task.idea_id → 灵感 title(找不到时回退 id,保证非空可点击)
// 解析 task.idea_id → 灵感 title(找不到时回退 id,保证非空可点击)
const ideaTitle = computed(() => {
const ideaId = task.value?.idea_id
if (!ideaId) return '—'
@@ -670,7 +670,7 @@ const advanceActions = computed<AdvanceAction[]>(() => {
// ADVANCE_MAP 各行的首个 primary 项:todo→in_progress, in_review→testing, testing→done, blocked→in_progress
// in_progress 的 primary 是 in_review(无模板,不显示工作流推进按钮)。
const WF_SUPPORTED_TARGETS = new Set(['in_progress', 'testing', 'done'])
// CR-08-O1: blocked 是阻塞态(任务被阻塞),其 primary=in_progress 虽 ∈ WF_SUPPORTED_TARGETS,
// blocked 是阻塞态(任务被阻塞),其 primary=in_progress 虽 ∈ WF_SUPPORTED_TARGETS,
// 但 template_for('in_progress') 是「todo→in_progress 从头执行」拓扑(AiNode 等重新跑一遍),
// blocked 恢复语义不该重跑 AiNode(任务已有上下文/进度,只需恢复非从头执行)。
// 最小修法:blocked 不显示工作流推进按钮,用户手动 advance(blocked→in_progress 走后端状态机)或先解除阻塞。
@@ -693,7 +693,7 @@ async function handleAdvance(target: string) {
// advance_task 返回更新后的 TaskRecord(含新 status / review_rounds)
const updated = await taskApi.advance(task.value.id, target)
task.value = updated
// P2-8: 手动推进成功后清工作流 UI 态(防上一轮工作流残留进度/终态提示污染新任务态)
// 手动推进成功后清工作流 UI 态(防上一轮工作流残留进度/终态提示污染新任务态)
resetWorkflowUi()
errorMsg.value = ''
} catch (e: any) {
@@ -704,7 +704,7 @@ async function handleAdvance(target: string) {
}
// F-260616-06 ①-1: 工作流推进 — 传空 dag + target_status,后端自动选推进链模板。
// B-41: 监听 workflow-event 显示轻量进度;完成/失败后任务态由后端回调推进(②-3/②-4),
// 监听 workflow-event 显示轻量进度;完成/失败后任务态由后端回调推进(②-3/②-4),
// df-data-changed 自动刷新 task(AR-11 已通),此处不直接改 task.value。
// name 用推进链标识(模板内未硬编码 task_id,运行时由后端 config 注入,前端只传 topology hint)。
async function handleWorkflowAdvance(target: string) {
@@ -729,7 +729,7 @@ async function handleWorkflowAdvance(target: string) {
}
}
// B-41: 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加。
// 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加。
// 原有 handleWorkflowEvent 已移除 —— 事件仍由 workflow store 全局收集,TaskDetail 只读派生结果。
async function load() {
@@ -740,13 +740,13 @@ async function load() {
taskApi.get(taskId.value),
// 项目列表用于解析 project_id → 项目名(独立入口,不依赖全局 store)
projectApi.list(),
// F-260619-01:灵感列表用于解析 idea_id → 灵感 title
// 灵感列表用于解析 idea_id → 灵感 title
ideaApi.list(),
])
task.value = t
projects.value = ps
ideas.value = ideasList
// F-260805:父子数据(子任务取父标题 / 顶层任务取子任务列表),与主数据并行
// 父子数据(子任务取父标题 / 顶层任务取子任务列表),与主数据并行
await Promise.all([loadParentTitle(), loadChildren()])
} catch (e: any) {
task.value = null
@@ -769,7 +769,7 @@ watch(taskId, () => { resetWorkflowUi(); load() })
watch(() => task.value?.id, () => { descExpanded.value = false })
watch(renderedDesc, () => { measureDescHeight() })
// B-260616-18: 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
// 数据变更联动刷新 unlistener(onMounted 注册,onBeforeUnmount 释放)
// 对齐 AiChat _unlistenToolSlow 生命周期模式。本视图绕 store 直调 taskApi.get/projectApi.list,
// 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
// 故此本地监听 entity=task/project 时重载当前 task。
@@ -778,9 +778,9 @@ let _unlistenDataChanged: (() => void) | null = null
onMounted(async () => {
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
load()
// F-260805:点击子任务快捷菜单外关闭菜单
// 点击子任务快捷菜单外关闭菜单
document.addEventListener('click', closeChildMenu)
// B-260616-18: 后端 AI 工具(create/update/delete 等)emit df-data-changed → 本视图重载当前 task
// 后端 AI 工具(create/update/delete 等)emit df-data-changed → 本视图重载当前 task
// entity=task:任务本体字段(标题/状态/描述/分支…)被改时刷新;entity=project:项目名变更影响 projectName 解析时刷新
try {
_unlistenDataChanged = await listen<DfDataChangedPayload>('df-data-changed', (event) => {
@@ -792,7 +792,7 @@ onMounted(async () => {
} catch (e) {
console.error('[TaskDetail] 启动 df-data-changed 监听失败:', e)
}
// B-41: 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
// 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
// 此处只需确保 store 事件监听已启动(幂等,共享单例;按 execution_id 过滤由派生层完成)。
try {
await store.startEventListener()
@@ -803,9 +803,9 @@ onMounted(async () => {
onBeforeUnmount(() => {
if (_unlistenDataChanged) { _unlistenDataChanged(); _unlistenDataChanged = null }
// F-260805:移除子任务快捷菜单关闭监听
// 移除子任务快捷菜单关闭监听
document.removeEventListener('click', closeChildMenu)
// SW-260618-21: 清终态提示 timer 防卸载后写已销毁 ref
// 清终态提示 timer 防卸载后写已销毁 ref
if (_wfResultTimer) { clearTimeout(_wfResultTimer); _wfResultTimer = null }
})
</script>
@@ -903,7 +903,7 @@ onBeforeUnmount(() => {
flex-direction: column;
gap: var(--df-gap-grid);
}
/* B-260615-31:字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
/* 字段同行布局(label 固定宽 + value 占余),描述字段 info-block 保持块状)
基础 .info-item/.label/.value 已收敛至全局 components.css(DRY 收口 B-260619),
此处仅保留本组件特有覆盖。 */
/* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li)。
+21 -21
View File
@@ -417,7 +417,7 @@ const router = useRouter()
const store = useProjectStore()
const { t } = useI18n()
const { confirmState, confirmDialog, answerConfirm } = useConfirm()
// WK-13:快捷操作失败 toast 反馈(对齐 Projects.vue useToast 模式)
// 快捷操作失败 toast 反馈(对齐 Projects.vue useToast 模式)
const { toast, showToast } = useToast()
// 列表筛选/分页状态持久化到 localStorage(key 前缀 'tasks.'),
@@ -447,14 +447,14 @@ function toggleStatus(key: string) {
const sortBy = usePersistedRef('tasks.sortBy', 'updated_at')
const searchKeyword = usePersistedRef('tasks.searchKeyword', '')
const loading = ref(false)
// WK-11:搜索态(keyword 过滤可能截断父子关系,子进度徽章/迷你条语义失真 → 隐藏)
// 搜索态(keyword 过滤可能截断父子关系,子进度徽章/迷你条语义失真 → 隐藏)
const isSearching = computed(() => searchKeyword.value.trim().length > 0)
// F-260805 D5:列表页一次性加载 + 前端组装树,移除真分页(page/pageSize/totalTasks 已删)。
// 分组折叠状态(localStorage 记忆)
const collapsedGroups = reactive(new Set<string>())
// F-260805:父任务展开/折叠状态(localStorage 记忆,沿用 collapsedGroups 模式)
// 父任务展开/折叠状态(localStorage 记忆,沿用 collapsedGroups 模式)
const expandedParents = reactive(new Set<string>())
function toggleParent(id: string) {
if (expandedParents.has(id)) {
@@ -491,7 +491,7 @@ const STATUS_FILTER_META: Record<string, { icon: string; labelKey: string }> = {
const quickStatuses = computed(() =>
TASK_STATUS_ORDER.map(key => ({ key, icon: STATUS_FILTER_META[key].icon, label: t(STATUS_FILTER_META[key].labelKey) })),
)
/** WK-8:快捷状态目标 = 状态机合法流转目标(TASK_STATUS_TRANSITIONS 过滤,对齐 TaskDetail ADVANCE_MAP) */
/** 快捷状态目标 = 状态机合法流转目标(TASK_STATUS_TRANSITIONS 过滤,对齐 TaskDetail ADVANCE_MAP) */
function quickStatusesFor(task: TaskRecord): { key: string; icon: string; label: string }[] {
const valid = new Set(TASK_STATUS_TRANSITIONS[task.status] ?? [])
return quickStatuses.value.filter(s => valid.has(s.key))
@@ -508,14 +508,14 @@ async function quickAdvance(id: string, target: string) {
await taskApi.advance(id, target)
await store.loadTasks(buildTaskQuery())
} catch (e: any) {
// WK-13:快捷操作失败 toast 反馈(原 console.error 静默,用户无感知)
// 快捷操作失败 toast 反馈(原 console.error 静默,用户无感知)
showToast(e?.toString() ?? t('common.unknownError'), 'error')
}
}
async function quickPriority(id: string, priority: number) {
quickMenuId.value = null
try {
// WK-13:改用 taskApi 直调(不走 store.updateTask,避免失败置 store.error 触发整表错误态)
// 改用 taskApi 直调(不走 store.updateTask,避免失败置 store.error 触发整表错误态)
await taskApi.update(id, 'priority', String(priority))
await store.loadTasks(buildTaskQuery())
} catch (e: any) {
@@ -524,13 +524,13 @@ async function quickPriority(id: string, priority: number) {
}
async function quickDelete(task: TaskRecord, childCount = 0) {
quickMenuId.value = null
// F-260805:父任务带子任务时确认文案含子任务数(后端级联软删)
// 父任务带子任务时确认文案含子任务数(后端级联软删)
const msg = childCount > 0
? t('tasks.confirmDeleteWithChildren', { title: task.title, n: childCount })
: t('tasks.confirmDelete', { title: task.title })
if (!await confirmDialog(msg)) return
try {
// WK-13:deleteTask 返成功布尔,失败 toast(store.error 同时走 error-banner)
// deleteTask 返成功布尔,失败 toast(store.error 同时走 error-banner)
const ok = await store.deleteTask(task.id)
if (!ok) showToast(t('tasks.err.deleteFailed'), 'error')
} catch (e) { console.error('删除失败:', e) }
@@ -556,7 +556,7 @@ try {
// 搜索防抖
let _searchTimer: ReturnType<typeof setTimeout> | null = null
watch(searchKeyword, () => {
// WK-10:登记当前关键字到 barrel(数据变更联动刷新复用同一筛选,不冲掉搜索结果)
// 登记当前关键字到 barrel(数据变更联动刷新复用同一筛选,不冲掉搜索结果)
store.setActiveTaskKeyword(searchKeyword.value.trim() || undefined)
if (_searchTimer) clearTimeout(_searchTimer)
_searchTimer = setTimeout(() => {
@@ -572,18 +572,18 @@ const newTaskTitle = ref('')
const newTaskDesc = ref('')
const newTaskBranch = ref('')
const newTaskPriority = ref(2)
// F-260805:父任务下拉选中值(空串=无/顶层任务)
// 父任务下拉选中值(空串=无/顶层任务)
const newTaskParentId = ref('')
// F-260619-01:关联灵感(空串=不关联,与后端 idea_id 空串处理一致)
// 关联灵感(空串=不关联,与后端 idea_id 空串处理一致)
const newTaskIdeaId = ref('')
const submitting = ref(false)
// F-260805:父任务选项 = 当前选中项目的顶层任务(无 parent_id;仅顶层可作父,1 级嵌套)
// 父任务选项 = 当前选中项目的顶层任务(无 parent_id;仅顶层可作父,1 级嵌套)
const parentTaskOptions = computed(() =>
store.tasks.filter(t => !t.parent_id && t.project_id === newTaskProjectId.value),
)
// F-260805:树形行结构(父任务 + 直接子任务 + 子进度)。1 级嵌套,无递归。
// 树形行结构(父任务 + 直接子任务 + 子进度)。1 级嵌套,无递归。
interface TaskRow {
task: TaskRecord
/** 父任务的直接子任务(仅父有;叶子为空数组) */
@@ -615,7 +615,7 @@ interface TaskGroup {
rows: TaskRow[]
}
// F-260805:树组装 — 顶层 = 无 parent_id 的任务,每个顶层挂 children;父进度前端计算(D6,全量已在前端)。
// 树组装 — 顶层 = 无 parent_id 的任务,每个顶层挂 children;父进度前端计算(D6,全量已在前端)。
const taskRows = computed<TaskRow[]>(() => {
// 子任务按 parent_id 分组(1 级嵌套)
const childMap = new Map<string, TaskRecord[]>()
@@ -806,7 +806,7 @@ function buildTaskQuery(): TaskQuery | undefined {
return query
}
// F-260805:打开新建弹窗。可选 parent(父任务快捷菜单「添加子任务」):
// 打开新建弹窗。可选 parent(父任务快捷菜单「添加子任务」):
// 有父 → project_id 锁定为该父所属项目、parent_id 预填;无父(顶部「新建任务」)→ 默认父=无(顶层任务)。
function openCreateModal(parent?: TaskRecord) {
if (parent) {
@@ -835,9 +835,9 @@ async function confirmCreate() {
description: newTaskDesc.value.trim(),
branch_name: newTaskBranch.value.trim() || undefined,
priority: newTaskPriority.value,
// F-260619-01:空串=不关联(对齐后端 idea_id 空串语义)
// 空串=不关联(对齐后端 idea_id 空串语义)
idea_id: newTaskIdeaId.value.trim() || undefined,
// F-260805:父任务透传(空串=顶层任务,后端视为 None)
// 父任务透传(空串=顶层任务,后端视为 None)
parent_id: newTaskParentId.value || undefined,
})
if (!r) return
@@ -912,7 +912,7 @@ onMounted(async () => {
store.loadProjects(),
// F-260805 D5:一次拉当前筛选全量(limit 500),树形需要完整父子关系
store.loadTasks(buildTaskQuery()),
// F-260619-01:加载灵感供新建任务模态关联下拉选择(幂等,已加载则 no-op)
// 加载灵感供新建任务模态关联下拉选择(幂等,已加载则 no-op)
store.loadIdeas(),
])
} finally {
@@ -1171,7 +1171,7 @@ onUnmounted(() => {
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
/* WK-9:原 overflow:hidden 会把快捷菜单(.quick-menu 绝对定位向下溢出)裁切。
/* 原 overflow:hidden 会把快捷菜单(.quick-menu 绝对定位向下溢出)裁切。
改为组自身不裁切,圆角裁剪下放给 header(顶角)与末行(底角,见下),菜单可正常浮出。 */
}
@@ -1184,7 +1184,7 @@ onUnmounted(() => {
user-select: none;
border-bottom: 0.5px solid var(--df-border);
transition: background 0.1s;
/* WK-9:承接原 .task-group overflow:hidden 的顶角圆角裁剪(自身 overflow,组不再裁切快捷菜单) */
/* 承接原 .task-group overflow:hidden 的顶角圆角裁剪(自身 overflow,组不再裁切快捷菜单) */
border-radius: var(--df-radius-lg) var(--df-radius-lg) 0 0;
overflow: hidden;
}
@@ -1216,7 +1216,7 @@ onUnmounted(() => {
cursor: pointer;
}
.task-item:last-child { border-bottom: none; }
/* WK-9:末行承接组底角圆角裁剪(替代原 .task-group overflow:hidden) */
/* 末行承接组底角圆角裁剪(替代原 .task-group overflow:hidden) */
.task-item:last-child { border-radius: 0 0 var(--df-radius-lg) var(--df-radius-lg); }
.task-item:hover { background: var(--df-accent-bg); }