新增: 初始化 DevFlow 项目仓库

Tauri 2 + Vue 3 + Vite 6 桌面应用,Rust workspace 含 13 个 crate
(df-ai / df-storage / df-workflow / df-core / df-execute 等)。
核心能力:AI 聊天 agentic 循环(工具调用+人工审批)、工作流引擎、
任务/想法/项目/阶段管理、可追溯性,及配套前端组件。
This commit is contained in:
2026-06-12 01:31:05 +08:00
commit 98393b4908
178 changed files with 27859 additions and 0 deletions

289
src/views/Projects.vue Normal file
View File

@@ -0,0 +1,289 @@
<template>
<div class="projects">
<header class="page-header">
<h1>📂 项目列表</h1>
<div class="header-actions">
<button class="btn btn-primary" @click="showCreateModal = true">+ 新建项目</button>
</div>
</header>
<!-- 新建项目模态框 -->
<div v-if="showCreateModal" class="modal-overlay" @click.self="showCreateModal = false">
<div class="modal-box">
<h3 class="modal-title">新建项目</h3>
<div class="modal-field">
<label>项目名称</label>
<input v-model="newName" placeholder="输入项目名称" @keyup.enter="submitCreate" />
</div>
<div class="modal-field">
<label>项目描述</label>
<textarea v-model="newDesc" placeholder="简要描述项目目标" rows="3"></textarea>
</div>
<div class="modal-actions">
<button class="btn btn-ghost" @click="showCreateModal = false">取消</button>
<button class="btn btn-primary" @click="submitCreate" :disabled="!newName.trim()">确认创建</button>
</div>
</div>
</div>
<!-- 项目卡片网格 -->
<div class="project-grid">
<div class="project-card" v-for="project in store.projects" :key="project.id" @click="router.push('/projects/' + project.id)">
<div class="card-top">
<div class="card-title-row">
<h2 class="card-name">{{ project.name }}</h2>
</div>
<span class="stage-badge" :class="stageClass(project.status)">{{ statusLabel(project.status) }}</span>
</div>
<p class="card-desc">{{ project.description }}</p>
<!-- 底部信息 -->
<div class="card-footer">
<div class="footer-stat">
<span class="stat-icon">📅</span>
<span>{{ formatDate(project.created_at) }}</span>
</div>
<div class="footer-stat">
<span class="stat-icon">🕐</span>
<span>{{ formatDate(project.updated_at) }}</span>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="!store.loading && store.projects.length === 0" class="empty-state">
暂无项目点击右上角创建第一个项目
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useProjectStore } from '../stores/project'
import { formatDate } from '../utils/time'
const router = useRouter()
const store = useProjectStore()
// ── 状态映射 ──
const statusLabels: Record<string, string> = {
planning: '📐 规划中',
in_progress: '💻 进行中',
paused: '⏸ 已暂停',
completed: '✅ 已完成',
cancelled: '❌ 已取消',
}
const statusStageClass: Record<string, string> = {
planning: 'stage-design',
in_progress: 'stage-coding',
paused: 'stage-testing',
completed: 'stage-release',
cancelled: 'stage-design',
}
function statusLabel(status: string) {
return statusLabels[status] ?? status
}
function stageClass(status: string) {
return statusStageClass[status] ?? 'stage-design'
}
// formatDate 由 ../utils/time 提供(统一毫秒字符串解析,根治 Invalid Date
// ── 新建项目 ──
const showCreateModal = ref(false)
const newName = ref('')
const newDesc = ref('')
async function submitCreate() {
if (!newName.value.trim()) return
await store.createProject(newName.value.trim(), newDesc.value.trim())
showCreateModal.value = false
newName.value = ''
newDesc.value = ''
}
// ── 加载 ──
onMounted(() => {
store.loadProjects()
})
</script>
<style scoped>
.projects { padding: 16px 20px 20px; }
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--df-gap-page);
}
.page-header h1 { font-size: 24px; font-weight: 500; color: var(--df-text); }
.header-actions { display: flex; gap: 10px; }
/* ===== 按钮 ===== */
.btn {
padding: 8px 16px;
border: none;
border-radius: var(--df-radius-sm);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.btn-primary { background: var(--df-accent); color: #fff; }
.btn-primary:hover { background: var(--df-accent-hover); }
/* ===== 卡片网格 ===== */
.project-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--df-gap-grid);
}
.project-card {
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
padding: var(--df-pad-panel);
transition: all 0.15s;
cursor: pointer;
}
.project-card:hover {
border-color: var(--df-accent);
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.card-title-row {
display: flex;
align-items: center;
gap: 8px;
}
.card-icon { font-size: 22px; }
.card-name { font-size: 17px; font-weight: 500; color: var(--df-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stage-badge {
font-size: 12px; font-weight: 500;
padding: 3px 10px;
border-radius: var(--df-radius-sm);
}
.stage-design { background: rgba(100,181,246,0.15); color: var(--df-info); }
.stage-coding { background: rgba(108,99,255,0.15); color: var(--df-accent); }
.stage-testing { background: rgba(255,217,61,0.15); color: var(--df-warning); }
.stage-release { background: rgba(100,255,218,0.15); color: var(--df-success); }
.card-desc {
font-size: 13px;
color: var(--df-text-secondary);
line-height: 1.5;
margin-bottom: 16px;
}
/* ===== 进度条 ===== */
.card-progress { margin-bottom: 16px; }
.progress-label-row {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
}
.progress-stage { font-size: 12px; color: var(--df-text-dim); }
.progress-pct { font-size: 12px; font-weight: 500; color: var(--df-text); }
.progress-bar {
height: 6px;
background: var(--df-border);
border-radius: var(--df-radius-sm);
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--df-accent);
border-radius: var(--df-radius-sm);
transition: width 0.3s;
}
/* ===== 底部信息 ===== */
.card-footer {
display: flex;
gap: var(--df-gap-grid);
margin-bottom: 14px;
}
.footer-stat {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--df-text-dim);
}
.stat-icon { font-size: 13px; }
/* ===== 技术栈标签 ===== */
.card-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.tech-tag {
font-size: 11px;
padding: 2px 8px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.08);
color: var(--df-text-secondary);
border: 0.5px solid var(--df-border);
}
/* ===== 模态框 ===== */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.5);
display: flex; align-items: center; justify-content: center;
z-index: 100;
}
.modal-box {
background: var(--df-bg-card);
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-lg);
padding: 24px;
width: 420px;
max-width: 90vw;
}
.modal-title { font-size: 18px; font-weight: 500; color: var(--df-text); margin-bottom: 16px; }
.modal-field { margin-bottom: 14px; }
.modal-field label { display: block; font-size: 12px; color: var(--df-text-dim); margin-bottom: 4px; }
.modal-field input, .modal-field textarea {
width: 100%; padding: 8px 10px; border-radius: var(--df-radius-sm);
border: 0.5px solid var(--df-border); background: var(--df-bg);
color: var(--df-text); font-size: 13px; font-family: inherit;
box-sizing: border-box;
}
.modal-field input:focus, .modal-field textarea:focus {
outline: none; border-color: var(--df-accent);
}
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }
.btn-ghost { background: transparent; color: var(--df-text-secondary); border: 0.5px solid var(--df-border); }
.btn-ghost:hover { background: var(--df-bg-card); color: var(--df-text); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* ===== 空状态 ===== */
.empty-state {
text-align: center; padding: 60px 20px;
font-size: 14px; color: var(--df-text-dim);
}
/* ===== 响应式 ===== */
@media (max-width: 900px) {
.projects { padding: 16px; }
.project-grid {
grid-template-columns: 1fr;
}
}
</style>