新增: Phase2 阶段收尾(Sprint 1-20)
重构:删 5 零引用 crate(df-evolve/plugin/stages/task/traceability)+ 清死模块、ai.rs 拆 11 子 module、ai.ts 拆 6 composable、i18n 拆目录 功能:知识库全栈(df-project/scan + CRUD + 时间线 + 前端)、Settings 拆分、appSettings KV 迁移、模型池、LLM 并发 Semaphore 修复:审批持久化根治、ConditionEngine 默认拒绝、NodeRegistry unimplemented 清除、promote 补偿删除、工具结果截断 50KB、路径校验防 symlink 逃逸 文档:B-03 人工审批设计、决策记录三分档、规格契约自检、经验记录、todo 看板、PROGRESS 更新 详见 PROGRESS.md。src-tauri/儿童每日打卡应用/ 与本项目无关,已排除。
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="tasks">
|
||||
<header class="page-header">
|
||||
<h1>🔀 任务队列</h1>
|
||||
<h1>{{ $t('tasks.title') }}</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-ghost" @click="refresh">🔄 刷新</button>
|
||||
<button class="btn btn-primary" @click="openCreateModal">+ 新建任务</button>
|
||||
<button class="btn btn-ghost" @click="refresh">{{ $t('tasks.refresh') }}</button>
|
||||
<button class="btn btn-primary" @click="openCreateModal">{{ $t('tasks.create') }}</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">项目:</span>
|
||||
<span class="filter-label">{{ $t('tasks.filter.project') }}</span>
|
||||
<button
|
||||
v-for="p in projectFilters"
|
||||
:key="p.key"
|
||||
@@ -23,7 +23,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">状态:</span>
|
||||
<span class="filter-label">{{ $t('tasks.filter.status') }}</span>
|
||||
<button
|
||||
v-for="s in statusFilters"
|
||||
:key="s.key"
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="group-header">
|
||||
<span class="group-icon">{{ group.icon }}</span>
|
||||
<h2 class="group-name">{{ group.projectName }}</h2>
|
||||
<span class="group-count">{{ group.tasks.length }} 个任务</span>
|
||||
<span class="group-count">{{ $t('tasks.group.taskCount', { n: group.tasks.length }) }}</span>
|
||||
</div>
|
||||
<div class="task-list">
|
||||
<div class="task-item" v-for="task in group.tasks" :key="task.id">
|
||||
@@ -60,11 +60,11 @@
|
||||
<span class="branch-icon">⑂</span>
|
||||
{{ task.branch_name }}
|
||||
</span>
|
||||
<span class="task-date">{{ formatTime(task.created_at) }}</span>
|
||||
<span class="task-time">{{ formatTime(task.updated_at) }}</span>
|
||||
<span class="task-date">{{ formatRelativeZh(task.created_at) }}</span>
|
||||
<span class="task-time">{{ formatRelativeZh(task.updated_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="status-tag" :class="'status-' + task.status">{{ statusLabel(task.status) }}</span>
|
||||
<span class="status-tag" :class="taskStatusClass(task.status)">{{ $t(statusLabel(task.status)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -73,16 +73,27 @@
|
||||
<!-- 新建任务模态框 -->
|
||||
<div class="modal-overlay" v-if="showCreateModal" @click.self="showCreateModal = false">
|
||||
<div class="modal-box">
|
||||
<h3>新建任务</h3>
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">项目</label>
|
||||
<h3>{{ $t('tasks.modal.title') }}</h3>
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('tasks.modal.project') }}</label>
|
||||
<select v-model="newTaskProjectId" style="width:100%;padding:8px 12px;border: 0.5px solid var(--df-border);border-radius: var(--df-radius-sm);background:var(--df-bg);color:var(--df-text);font-size:13px;margin-bottom:12px">
|
||||
<option v-for="p in store.projects" :key="p.id" :value="p.id">{{ p.name }}</option>
|
||||
</select>
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">标题</label>
|
||||
<input v-model="newTaskTitle" placeholder="输入任务标题..." @keyup.enter="confirmCreate" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('tasks.modal.titleField') }}</label>
|
||||
<input v-model="newTaskTitle" :placeholder="$t('tasks.modal.titlePlaceholder')" @keyup.enter="confirmCreate" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('tasks.modal.desc') }}</label>
|
||||
<input v-model="newTaskDesc" :placeholder="$t('tasks.modal.descPlaceholder')" style="width:100%;padding:8px 12px;border: 0.5px solid var(--df-border);border-radius: var(--df-radius-sm);background:var(--df-bg);color:var(--df-text);font-size:13px;margin-bottom:12px;box-sizing:border-box" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('tasks.modal.branch') }}</label>
|
||||
<input v-model="newTaskBranch" :placeholder="$t('tasks.modal.branchPlaceholder')" style="width:100%;padding:8px 12px;border: 0.5px solid var(--df-border);border-radius: var(--df-radius-sm);background:var(--df-bg);color:var(--df-text);font-size:13px;margin-bottom:12px;box-sizing:border-box" />
|
||||
<label style="font-size:12px;color:var(--df-text-secondary);margin-bottom:4px;display:block">{{ $t('tasks.modal.priority') }}</label>
|
||||
<select v-model="newTaskPriority" style="width:100%;padding:8px 12px;border: 0.5px solid var(--df-border);border-radius: var(--df-radius-sm);background:var(--df-bg);color:var(--df-text);font-size:13px;margin-bottom:12px">
|
||||
<option :value="0">{{ $t('tasks.modal.priorityCritical') }}</option>
|
||||
<option :value="1">{{ $t('tasks.modal.priorityHigh') }}</option>
|
||||
<option :value="2">{{ $t('tasks.modal.priorityMedium') }}</option>
|
||||
<option :value="3">{{ $t('tasks.modal.priorityLow') }}</option>
|
||||
</select>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" @click="showCreateModal = false">取消</button>
|
||||
<button class="btn-confirm" @click="confirmCreate">确认</button>
|
||||
<button class="btn-cancel" @click="showCreateModal = false">{{ $t('common.cancel') }}</button>
|
||||
<button class="btn-confirm" @click="confirmCreate">{{ $t('common.confirm') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,11 +102,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useProjectStore } from '../stores/project'
|
||||
import { formatRelativeZh } from '../utils/time'
|
||||
import { taskStatusLabel as statusLabel, taskStatusClass, priorityLabel, priorityClass } from '../constants/project'
|
||||
import type { TaskRecord } from '../api/types'
|
||||
|
||||
const store = useProjectStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const activeProject = ref('all')
|
||||
const activeStatus = ref('all')
|
||||
@@ -104,6 +118,9 @@ const activeStatus = ref('all')
|
||||
const showCreateModal = ref(false)
|
||||
const newTaskProjectId = ref('')
|
||||
const newTaskTitle = ref('')
|
||||
const newTaskDesc = ref('')
|
||||
const newTaskBranch = ref('')
|
||||
const newTaskPriority = ref(2) // 默认 medium
|
||||
|
||||
interface TaskGroup {
|
||||
projectName: string
|
||||
@@ -112,21 +129,21 @@ interface TaskGroup {
|
||||
}
|
||||
|
||||
const projectFilters = computed(() => [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'all', label: t('tasks.filter.all') },
|
||||
...store.projects.map(p => ({ key: p.id, label: p.name })),
|
||||
])
|
||||
|
||||
const statusFilters: { key: string; label: string; icon: string }[] = [
|
||||
{ key: 'all', label: '全部', icon: '📋' },
|
||||
{ key: 'todo', label: '待开始', icon: '📝' },
|
||||
{ key: 'in_progress', label: '进行中', icon: '🔨' },
|
||||
{ key: 'review_ready', label: '待审查', icon: '👀' },
|
||||
{ key: 'merged', label: '已合并', icon: '✅' },
|
||||
{ key: 'abandoned', label: '已废弃', icon: '🗑️' },
|
||||
]
|
||||
const statusFilters = computed<{ key: string; label: string; icon: string }[]>(() => [
|
||||
{ key: 'all', label: t('tasks.statusFilter.all'), icon: '📋' },
|
||||
{ key: 'todo', label: t('tasks.statusFilter.todo'), icon: '📝' },
|
||||
{ key: 'in_progress', label: t('tasks.statusFilter.in_progress'), icon: '🔨' },
|
||||
{ key: 'review_ready', label: t('tasks.statusFilter.review_ready'), icon: '👀' },
|
||||
{ key: 'merged', label: t('tasks.statusFilter.merged'), icon: '✅' },
|
||||
{ key: 'abandoned', label: t('tasks.statusFilter.abandoned'), icon: '🗑️' },
|
||||
])
|
||||
|
||||
function getProjectName(projectId: string): string {
|
||||
return store.projects.find(p => p.id === projectId)?.name ?? '未知项目'
|
||||
return store.projects.find(p => p.id === projectId)?.name ?? t('tasks.group.unknownProject')
|
||||
}
|
||||
|
||||
const projectIcons: Record<string, string> = {
|
||||
@@ -164,30 +181,7 @@ const filteredGroups = computed(() => {
|
||||
return result
|
||||
})
|
||||
|
||||
function statusLabel(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
todo: '📝 待开始',
|
||||
in_progress: '🔨 进行中',
|
||||
review_ready: '👀 待审查',
|
||||
merged: '✅ 已合并',
|
||||
abandoned: '🗑️ 已废弃',
|
||||
}
|
||||
return map[status] ?? status
|
||||
}
|
||||
|
||||
function priorityLabel(priority: number) {
|
||||
const map: Record<number, string> = { 0: 'P0', 1: 'P1', 2: 'P2', 3: 'P3' }
|
||||
return map[priority] ?? `P${priority}`
|
||||
}
|
||||
|
||||
function priorityClass(priority: number): string {
|
||||
const map: Record<number, string> = { 0: 'priority-critical', 1: 'priority-high', 2: 'priority-medium', 3: 'priority-low' }
|
||||
return map[priority] ?? 'priority-low'
|
||||
}
|
||||
|
||||
function formatTime(timestamp: string | number): string {
|
||||
return formatRelativeZh(timestamp)
|
||||
}
|
||||
// statusLabel / priorityLabel / priorityClass 由 ../constants/project 提供(与 ProjectDetail 文案统一)
|
||||
|
||||
async function refresh() {
|
||||
await store.loadTasks()
|
||||
@@ -196,15 +190,22 @@ async function refresh() {
|
||||
function openCreateModal() {
|
||||
newTaskProjectId.value = store.projects.length > 0 ? store.projects[0].id : ''
|
||||
newTaskTitle.value = ''
|
||||
newTaskDesc.value = ''
|
||||
newTaskBranch.value = ''
|
||||
newTaskPriority.value = 2
|
||||
showCreateModal.value = true
|
||||
}
|
||||
|
||||
async function confirmCreate() {
|
||||
if (!newTaskTitle.value.trim() || !newTaskProjectId.value) return
|
||||
await store.createTask({
|
||||
const r = await store.createTask({
|
||||
project_id: newTaskProjectId.value,
|
||||
title: newTaskTitle.value.trim(),
|
||||
description: newTaskDesc.value.trim(),
|
||||
branch_name: newTaskBranch.value.trim() || undefined,
|
||||
priority: newTaskPriority.value,
|
||||
})
|
||||
if (!r) return // 失败已 toast,保持弹窗不关
|
||||
showCreateModal.value = false
|
||||
}
|
||||
|
||||
@@ -370,12 +371,11 @@ onMounted(async () => {
|
||||
border-radius: var(--df-radius-sm);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.status-created { background: rgba(90,99,128,0.2); color: var(--df-text-dim); }
|
||||
.status-in_progress { background: rgba(100,181,246,0.2); color: var(--df-info); }
|
||||
.status-review_ready { background: rgba(255,217,61,0.2); color: var(--df-warning); }
|
||||
.status-merged { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
.status-abandoned { background: rgba(255,107,107,0.2); color: var(--df-danger); }
|
||||
.status-todo { background: rgba(90,99,128,0.2); color: var(--df-text-dim); }
|
||||
.status-progress { background: rgba(100,181,246,0.2); color: var(--df-info); }
|
||||
.status-review { background: rgba(255,217,61,0.2); color: var(--df-warning); }
|
||||
.status-done { background: rgba(100,255,218,0.15); color: var(--df-success); }
|
||||
.status-abandoned { background: rgba(255,107,107,0.2); color: var(--df-danger); }
|
||||
|
||||
/* ===== 模态框 ===== */
|
||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 100; }
|
||||
|
||||
Reference in New Issue
Block a user