新增: F-260619-01 任务可关联灵感(tasks.idea_id 1对1 单向)
- df-storage V20 迁移:tasks 加 idea_id TEXT REFERENCES ideas(id)(填预留空位, 幂等 column_exists 探测)+ V9_SQL 同步 + TaskRecord.idea_id + task_repo SQL/SELECT + 白名单 - AI 工具 create_task 加 idea_id 参数;update_task 经白名单自动支持 - IPC commands/task.rs CreateTaskInput 加 idea_id - 前端 types TaskRecord/CreateTaskInput idea_id + TaskDetail.vue 关联灵感展示 (router-link 友好 title 非裸 id)+ i18n - 补 idea_id 字段:df-mcp tools / df-nodes 测试 helper - 单向 1对1,Option 可空,复用 projects.idea_id 模式,老任务 None 兼容 自验: df-storage 47 passed(含 V20 2)+ workspace EXIT 0 + vue-tsc EXIT 0
This commit is contained in:
@@ -117,6 +117,8 @@ export interface TaskRecord {
|
||||
* 旧任务无产出时为 undefined。前端 parsedOutput try/catch 兜底解析。
|
||||
*/
|
||||
output_json?: string
|
||||
/** 关联灵感 ID(F-260619-01,1对1 单向,任务→灵感)。未关联时为 undefined。 */
|
||||
idea_id?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@@ -128,6 +130,8 @@ export interface CreateTaskInput {
|
||||
priority?: number
|
||||
branch_name?: string
|
||||
assignee?: string
|
||||
/** 关联灵感 ID(1对1 单向,可空)。空字符串视为不关联(与后端一致)。 */
|
||||
idea_id?: string
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -14,6 +14,7 @@ export default {
|
||||
status: 'Status',
|
||||
priority: 'Priority',
|
||||
project: 'Project',
|
||||
relatedIdea: 'Related Idea',
|
||||
branch: 'Branch',
|
||||
assignee: 'Assignee',
|
||||
baseBranch: 'Base Branch',
|
||||
|
||||
@@ -14,6 +14,7 @@ export default {
|
||||
status: '状态',
|
||||
priority: '优先级',
|
||||
project: '关联项目',
|
||||
relatedIdea: '关联灵感',
|
||||
branch: '分支',
|
||||
assignee: '负责人',
|
||||
baseBranch: '基础分支',
|
||||
|
||||
@@ -12,7 +12,7 @@ export function createTasksStore() {
|
||||
}
|
||||
}
|
||||
|
||||
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string }) {
|
||||
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string; idea_id?: string }) {
|
||||
try {
|
||||
const record = await taskApi.create(input)
|
||||
state.tasks.push(record)
|
||||
|
||||
@@ -104,6 +104,16 @@
|
||||
<span v-else>—</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- F-260619-01:关联灵感(1对1 单向,idea_id 解析为友好 title,非裸 id) -->
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.relatedIdea') }}</span>
|
||||
<span class="value">
|
||||
<router-link v-if="task.idea_id" :to="`/ideas/${task.idea_id}`" class="project-link">
|
||||
{{ ideaTitle }}
|
||||
</router-link>
|
||||
<span v-else>—</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('taskDetail.branch') }}</span>
|
||||
<span class="value">
|
||||
@@ -146,7 +156,7 @@ import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { taskApi, projectApi } from '@/api'
|
||||
import { taskApi, projectApi, ideaApi } from '@/api'
|
||||
import { workflowApi } from '@/api'
|
||||
import { formatDate } from '@/utils/time'
|
||||
import { useRendered } from '@/composables/useMarkdown'
|
||||
@@ -156,7 +166,7 @@ import {
|
||||
priorityLabel,
|
||||
priorityClass,
|
||||
} from '../constants/project'
|
||||
import type { TaskRecord, ProjectRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
|
||||
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
|
||||
import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -166,6 +176,8 @@ 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 模式)
|
||||
const ideas = ref<IdeaRecord[]>([])
|
||||
const advancing = ref(false)
|
||||
|
||||
// ============================================================
|
||||
@@ -204,6 +216,14 @@ const projectName = computed(() => {
|
||||
return p?.name ?? task.value?.project_id ?? '—'
|
||||
})
|
||||
|
||||
// F-260619-01:解析 task.idea_id → 灵感 title(找不到时回退 id,保证非空可点击)
|
||||
const ideaTitle = computed(() => {
|
||||
const ideaId = task.value?.idea_id
|
||||
if (!ideaId) return '—'
|
||||
const idea = ideas.value.find(i => i.id === ideaId)
|
||||
return idea?.title ?? ideaId
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
// F-05 推进按钮 — 状态机合法下一态映射
|
||||
// ------------------------------------------------------------
|
||||
@@ -379,13 +399,16 @@ async function load() {
|
||||
loading.value = true
|
||||
errorMsg.value = ''
|
||||
try {
|
||||
const [t, ps] = await Promise.all([
|
||||
const [t, ps, ideasList] = await Promise.all([
|
||||
taskApi.get(taskId.value),
|
||||
// 项目列表用于解析 project_id → 项目名(独立入口,不依赖全局 store)
|
||||
projectApi.list(),
|
||||
// F-260619-01:灵感列表用于解析 idea_id → 灵感 title
|
||||
ideaApi.list(),
|
||||
])
|
||||
task.value = t
|
||||
projects.value = ps
|
||||
ideas.value = ideasList
|
||||
} catch (e: any) {
|
||||
task.value = null
|
||||
errorMsg.value = t('taskDetail.loadFailed', { msg: e?.toString() ?? t('common.unknownError') })
|
||||
@@ -417,7 +440,7 @@ onMounted(async () => {
|
||||
try {
|
||||
_unlistenDataChanged = await listen<DfDataChangedPayload>('df-data-changed', (event) => {
|
||||
const { entity } = event.payload
|
||||
if (entity === 'task' || entity === 'project') {
|
||||
if (entity === 'task' || entity === 'project' || entity === 'idea') {
|
||||
load()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user