优化: 清理无意义注释(走查编号前缀/过时历史标注/死代码注释)
This commit is contained in:
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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); }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user