sillyspec 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-archive/SKILL.md +17 -0
- package/.claude/skills/sillyspec-auto/SKILL.md +78 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +17 -0
- package/{templates/commit.md → .claude/skills/sillyspec-commit/SKILL.md} +32 -47
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -0
- package/.claude/skills/sillyspec-doctor/SKILL.md +27 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +17 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +105 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +170 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +17 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +17 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/.claude/skills/sillyspec-scan/SKILL.md +17 -0
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +17 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +17 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/README.md +19 -11
- package/SKILL.md +15 -10
- package/package.json +7 -9
- package/packages/dashboard/dist/assets/index-BcM2J-hv.css +1 -0
- package/packages/dashboard/dist/assets/index-DpLHK4jv.js +7446 -0
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/package-lock.json +226 -6
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +341 -113
- package/packages/dashboard/server/parser.js +442 -30
- package/packages/dashboard/server/watcher.js +214 -134
- package/packages/dashboard/src/App.vue +475 -71
- package/packages/dashboard/src/components/ActionBar.vue +36 -43
- package/packages/dashboard/src/components/CommandPalette.vue +45 -66
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +257 -0
- package/packages/dashboard/src/components/DocTree.vue +114 -0
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/LogStream.vue +13 -33
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +99 -45
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectList.vue +103 -45
- package/packages/dashboard/src/components/ProjectOverview.vue +152 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +15 -15
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/composables/useDashboard.js +48 -6
- package/packages/dashboard/src/composables/useKeyboard.js +6 -4
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +17 -17
- package/src/index.js +141 -22
- package/src/init.js +93 -231
- package/src/migrate.js +117 -0
- package/src/progress.js +460 -0
- package/src/run.js +635 -0
- package/src/setup.js +2 -72
- package/src/stages/archive.js +54 -0
- package/src/stages/brainstorm.js +264 -0
- package/src/stages/doctor.js +303 -0
- package/src/stages/execute.js +287 -0
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +28 -0
- package/src/stages/plan.js +354 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/quick.js +64 -0
- package/src/stages/scan.js +141 -0
- package/src/stages/status.js +65 -0
- package/src/stages/verify.js +135 -0
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/specs/2026-04-05-dashboard-design.md +0 -206
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/docs/.vitepress/config.mts +0 -45
- package/docs/.vitepress/dist/404.html +0 -25
- package/docs/.vitepress/dist/assets/app.YytxICdd.js +0 -1
- package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +0 -19
- package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +0 -1
- package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +0 -1
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +0 -15
- package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +0 -4
- package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +0 -5
- package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +0 -28
- package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +0 -1
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +0 -30
- package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +0 -1
- package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +0 -1
- package/docs/.vitepress/dist/hashmap.json +0 -1
- package/docs/.vitepress/dist/index.html +0 -28
- package/docs/.vitepress/dist/sillyspec/commands.html +0 -42
- package/docs/.vitepress/dist/sillyspec/dashboard.html +0 -31
- package/docs/.vitepress/dist/sillyspec/file-io.html +0 -28
- package/docs/.vitepress/dist/sillyspec/getting-started.html +0 -31
- package/docs/.vitepress/dist/sillyspec/install.html +0 -32
- package/docs/.vitepress/dist/sillyspec/lifecycle.html +0 -55
- package/docs/.vitepress/dist/sillyspec/structure.html +0 -57
- package/docs/.vitepress/dist/vp-icons.css +0 -1
- package/docs/index.md +0 -34
- package/docs/sillyspec/commands.md +0 -218
- package/docs/sillyspec/dashboard.md +0 -51
- package/docs/sillyspec/file-io.md +0 -34
- package/docs/sillyspec/getting-started.md +0 -61
- package/docs/sillyspec/install.md +0 -51
- package/docs/sillyspec/lifecycle.md +0 -146
- package/docs/sillyspec/structure.md +0 -62
- package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +0 -1
- package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +0 -17
- package/src/step.js +0 -543
- package/templates/archive.md +0 -120
- package/templates/brainstorm.md +0 -170
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -304
- package/templates/explore.md +0 -59
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -146
- package/templates/quick.md +0 -135
- package/templates/scan-quick.md +0 -49
- package/templates/scan.md +0 -156
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -75
- package/templates/verify.md +0 -236
- package/templates/workspace-sync.md +0 -99
- package/templates/workspace.md +0 -70
- /package/{docs/.vitepress/dist/logo.jpg → logo.jpg} +0 -0
- /package/{docs/.vitepress → packages/dashboard}/dist/favicon.jpg +0 -0
- /package/{docs/public → packages/dashboard/dist}/logo.jpg +0 -0
- /package/{docs → packages/dashboard}/public/favicon.jpg +0 -0
|
@@ -1,53 +1,77 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col h-full" style="background: #
|
|
3
|
-
<!-- Header -->
|
|
4
|
-
<div class="px-6 pt-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<div class="flex flex-col h-full" style="background: #F5F5F7;">
|
|
3
|
+
<!-- Header with Tabs -->
|
|
4
|
+
<div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #F0F0F3;">
|
|
5
|
+
<div class="flex items-center gap-6">
|
|
6
|
+
<h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
7
|
+
{{ project?.name || '项目' }}
|
|
8
|
+
</h2>
|
|
9
|
+
<n-tabs :value="activeTab" type="segment" size="small" @update:value="$emit('switch-tab', $event)" style="max-width: 200px;">
|
|
10
|
+
<n-tab name="pipeline">流水线</n-tab>
|
|
11
|
+
<n-tab name="docs">文档</n-tab>
|
|
12
|
+
</n-tabs>
|
|
13
|
+
</div>
|
|
11
14
|
</div>
|
|
12
15
|
|
|
13
|
-
<!--
|
|
14
|
-
<div class="flex
|
|
16
|
+
<!-- Pipeline Tab -->
|
|
17
|
+
<div v-if="activeTab === 'pipeline'" class="flex flex-col flex-1 overflow-hidden">
|
|
18
|
+
<div v-if="project && currentStage" class="px-6 pt-4 pb-2">
|
|
19
|
+
<p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">
|
|
20
|
+
<span style="color: #D97706;">{{ currentStage }}</span>
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
15
24
|
<!-- Empty state -->
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
<n-empty v-if="!project || !project.state" description="选择一个项目查看流水线" style="margin: auto;" />
|
|
26
|
+
|
|
27
|
+
<!-- Stages -->
|
|
28
|
+
<div v-else class="flex-1 overflow-y-auto px-6 pb-5 space-y-5">
|
|
29
|
+
<PipelineStage name="scan" title="代码扫描" :steps="getStageSteps('scan')" :status="getStageStatus('scan')" :is-active="currentStage === 'scan'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
30
|
+
<div v-if="hasStage('brainstorm')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
|
|
31
|
+
<PipelineStage name="brainstorm" title="头脑风暴" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
32
|
+
<div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
|
|
33
|
+
<PipelineStage name="plan" title="规划" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
34
|
+
<div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
|
|
35
|
+
<PipelineStage name="execute" title="执行" :steps="getStageSteps('execute')" :status="getStageStatus('execute')" :is-active="currentStage === 'execute'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
36
|
+
<div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
|
|
37
|
+
<PipelineStage name="verify" title="验证" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
38
|
+
<div v-if="hasStage('archive')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
|
|
39
|
+
<PipelineStage name="archive" title="归档" :steps="getStageSteps('archive')" :status="getStageStatus('archive')" :is-active="currentStage === 'archive'" :active-step="activeStep" @select-step="handleSelectStep" />
|
|
25
40
|
</div>
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<div
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<div
|
|
34
|
-
|
|
42
|
+
<!-- Activity Log -->
|
|
43
|
+
<div v-if="project?.state?.progress" style="border-top: 1px solid #F0F0F3; background: rgba(0,0,0,0.03);">
|
|
44
|
+
<div class="px-6 py-2.5 flex items-center justify-between">
|
|
45
|
+
<div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">活动日志</div>
|
|
46
|
+
<div class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ activityLogs.length }}</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="px-6 pb-3 max-h-32 overflow-y-auto">
|
|
49
|
+
<n-timeline size="small">
|
|
50
|
+
<n-timeline-item
|
|
51
|
+
v-for="(log, i) in activityLogs"
|
|
52
|
+
:key="i"
|
|
53
|
+
:type="log.status === 'completed' ? 'success' : 'warning'"
|
|
54
|
+
>
|
|
55
|
+
<template #default>
|
|
56
|
+
<span class="text-[11px]" style="color: #636366;">{{ log.description }}</span>
|
|
57
|
+
</template>
|
|
58
|
+
<template #time>
|
|
59
|
+
<span class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ log.time }}</span>
|
|
60
|
+
</template>
|
|
61
|
+
</n-timeline-item>
|
|
62
|
+
</n-timeline>
|
|
63
|
+
<div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无活动记录</div>
|
|
64
|
+
</div>
|
|
35
65
|
</div>
|
|
36
66
|
</div>
|
|
37
67
|
|
|
38
|
-
<!--
|
|
39
|
-
<div v-if="
|
|
40
|
-
<div class="
|
|
41
|
-
<
|
|
42
|
-
<div class="text-[10px] font-mono-log" style="color: #3A3A3D;">{{ activityLogs.length }}</div>
|
|
68
|
+
<!-- Docs Tab -->
|
|
69
|
+
<div v-if="activeTab === 'docs'" class="docs-panel flex-1 flex overflow-hidden">
|
|
70
|
+
<div class="docs-tree-pane flex-shrink-0 overflow-hidden" style="border-right: 1px solid #F0F0F3;">
|
|
71
|
+
<DocTree :groups="docs.groups" :selected-file="selectedDocFile" @select-file="$emit('select-doc-file', $event)" />
|
|
43
72
|
</div>
|
|
44
|
-
<div class="
|
|
45
|
-
<
|
|
46
|
-
<span class="w-10 font-mono-log flex-shrink-0" style="color: #3A3A3D;">{{ log.time }}</span>
|
|
47
|
-
<span :style="{ color: log.status === 'completed' ? '#34D399' : '#FBBF24' }">›</span>
|
|
48
|
-
<span style="color: #8B8B8E;">{{ log.description }}</span>
|
|
49
|
-
</div>
|
|
50
|
-
<div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">No activity</div>
|
|
73
|
+
<div class="flex-1 overflow-hidden">
|
|
74
|
+
<DocPreview :content="docContent" :loading="docLoading" :selected-file="selectedDocFile" />
|
|
51
75
|
</div>
|
|
52
76
|
</div>
|
|
53
77
|
</div>
|
|
@@ -56,24 +80,43 @@
|
|
|
56
80
|
<script setup>
|
|
57
81
|
import { computed } from 'vue'
|
|
58
82
|
import PipelineStage from './PipelineStage.vue'
|
|
83
|
+
import DocTree from './DocTree.vue'
|
|
84
|
+
import DocPreview from './DocPreview.vue'
|
|
59
85
|
|
|
60
86
|
const props = defineProps({
|
|
61
87
|
project: { type: Object, default: null },
|
|
62
|
-
activeStep: { type: Object, default: null }
|
|
88
|
+
activeStep: { type: Object, default: null },
|
|
89
|
+
activeTab: { type: String, default: 'pipeline' },
|
|
90
|
+
docs: { type: Object, default: () => ({ groups: [] }) },
|
|
91
|
+
selectedDocFile: { type: Object, default: null },
|
|
92
|
+
docContent: { type: String, default: '' },
|
|
93
|
+
docLoading: { type: Boolean, default: false }
|
|
63
94
|
})
|
|
64
95
|
|
|
65
|
-
const emit = defineEmits(['select-step'])
|
|
96
|
+
const emit = defineEmits(['select-step', 'switch-tab', 'select-doc-file'])
|
|
66
97
|
|
|
67
|
-
const currentStage = computed(() => props.project?.state?.currentStage || '
|
|
98
|
+
const currentStage = computed(() => props.project?.state?.currentStage || '')
|
|
68
99
|
const progress = computed(() => props.project?.state?.progress || {})
|
|
69
100
|
const stages = computed(() => progress.value.stages || {})
|
|
70
101
|
|
|
102
|
+
const stageNameMap = {
|
|
103
|
+
scan: '代码扫描',
|
|
104
|
+
brainstorm: '头脑风暴',
|
|
105
|
+
plan: '规划',
|
|
106
|
+
execute: '执行',
|
|
107
|
+
verify: '验证',
|
|
108
|
+
archive: '归档',
|
|
109
|
+
quick: '快速任务',
|
|
110
|
+
explore: '自由探索'
|
|
111
|
+
}
|
|
112
|
+
|
|
71
113
|
const activityLogs = computed(() => {
|
|
72
114
|
if (!props.project?.state) return []
|
|
73
115
|
const logs = []
|
|
74
116
|
for (const [name, data] of Object.entries(progress.value.stages || {})) {
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
const label = stageNameMap[name] || name
|
|
118
|
+
if (data.status === 'completed') logs.push({ time: data.completedAt ? formatTime(data.completedAt) : '--:--', status: 'completed', description: `${label} 已完成` })
|
|
119
|
+
else if (data.status === 'in-progress') logs.push({ time: '--:--', status: 'in-progress', description: `${label} 运行中` })
|
|
77
120
|
}
|
|
78
121
|
return logs.reverse()
|
|
79
122
|
})
|
|
@@ -92,3 +135,14 @@ function getStageStatus(n) {
|
|
|
92
135
|
}
|
|
93
136
|
function handleSelectStep(step) { emit('select-step', step) }
|
|
94
137
|
</script>
|
|
138
|
+
|
|
139
|
+
<style scoped>
|
|
140
|
+
.docs-panel {
|
|
141
|
+
min-height: 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.docs-tree-pane {
|
|
145
|
+
width: clamp(220px, 42%, 320px);
|
|
146
|
+
min-width: 180px;
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="project-card"
|
|
4
|
+
:class="{ selected: isSelected }"
|
|
5
|
+
@click="$emit('select', project)"
|
|
6
|
+
>
|
|
7
|
+
<!-- 卡片头部 -->
|
|
8
|
+
<div class="card-header">
|
|
9
|
+
<span class="project-name">{{ project.name }}</span>
|
|
10
|
+
<span class="last-active">{{ formatTime(project.lastActive) }}</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- 阶段标签 -->
|
|
14
|
+
<div>
|
|
15
|
+
<span class="stage-badge" :class="stageClass">{{ stageLabel }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- 进度条 -->
|
|
19
|
+
<div class="progress-section">
|
|
20
|
+
<div class="progress-bar">
|
|
21
|
+
<div class="progress-fill" :class="stageClass" :style="{ width: progressPercent + '%' }"></div>
|
|
22
|
+
</div>
|
|
23
|
+
<span class="progress-text">{{ progressPercent }}%</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { computed } from 'vue'
|
|
30
|
+
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
project: { type: Object, required: true },
|
|
33
|
+
isSelected: { type: Boolean, default: false }
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
defineEmits(['select'])
|
|
37
|
+
|
|
38
|
+
const stageLabel = computed(() => {
|
|
39
|
+
const stage = props.project?.state?.currentStage
|
|
40
|
+
if (!stage) return '未开始'
|
|
41
|
+
const stageNames = {
|
|
42
|
+
brainstorm: '需求探索',
|
|
43
|
+
plan: '实现计划',
|
|
44
|
+
execute: '波次执行',
|
|
45
|
+
verify: '验证确认'
|
|
46
|
+
}
|
|
47
|
+
return stageNames[stage] || stage
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const stageClass = computed(() => {
|
|
51
|
+
const stage = props.project?.state?.currentStage
|
|
52
|
+
if (!stage) return 'pending'
|
|
53
|
+
const progress = props.project?.state?.progress?.stages?.[stage]
|
|
54
|
+
if (progress?.status === 'completed') return 'completed'
|
|
55
|
+
if (progress?.status === 'in-progress') return 'in-progress'
|
|
56
|
+
return 'pending'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const progressPercent = computed(() => {
|
|
60
|
+
const stage = props.project?.state?.currentStage
|
|
61
|
+
if (!stage) return 0
|
|
62
|
+
const progress = props.project?.state?.progress?.stages?.[stage]
|
|
63
|
+
if (!progress) return 0
|
|
64
|
+
if (progress.completedSteps !== undefined && progress.steps !== undefined) {
|
|
65
|
+
return Math.round((progress.completedSteps / progress.steps) * 100)
|
|
66
|
+
}
|
|
67
|
+
if (progress.status === 'completed') return 100
|
|
68
|
+
if (progress.status === 'in-progress') return 50
|
|
69
|
+
return 0
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
function formatTime(iso) {
|
|
73
|
+
if (!iso) return '昨天'
|
|
74
|
+
const d = new Date(iso)
|
|
75
|
+
const now = new Date()
|
|
76
|
+
const diffMs = now - d
|
|
77
|
+
if (diffMs < 60000) return '刚刚'
|
|
78
|
+
if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}分钟前`
|
|
79
|
+
if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)}小时前`
|
|
80
|
+
return '昨天'
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style scoped>
|
|
85
|
+
.project-card {
|
|
86
|
+
width: 280px;
|
|
87
|
+
height: 120px;
|
|
88
|
+
background: white;
|
|
89
|
+
border: 2px solid #E5E7EB;
|
|
90
|
+
border-radius: 12px;
|
|
91
|
+
padding: 16px;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
justify-content: space-between;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
transition: all 0.2s;
|
|
97
|
+
flex-shrink: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.project-card:hover {
|
|
101
|
+
border-color: #D97706;
|
|
102
|
+
box-shadow: 0 4px 12px rgba(217, 119, 6, 0.15);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.project-card.selected {
|
|
106
|
+
border-color: #D97706;
|
|
107
|
+
box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.card-header {
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: space-between;
|
|
113
|
+
align-items: flex-start;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.project-name {
|
|
117
|
+
font-size: 16px;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
color: #1A1A1A;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.last-active {
|
|
123
|
+
font-size: 11px;
|
|
124
|
+
color: #9CA3AF;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.stage-badge {
|
|
128
|
+
display: inline-block;
|
|
129
|
+
padding: 4px 10px;
|
|
130
|
+
border-radius: 12px;
|
|
131
|
+
font-size: 12px;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.stage-badge.in-progress {
|
|
136
|
+
background: #DBEAFE;
|
|
137
|
+
color: #1D4ED8;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.stage-badge.completed {
|
|
141
|
+
background: #D1FAE5;
|
|
142
|
+
color: #047857;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.stage-badge.pending {
|
|
146
|
+
background: #F3F4F6;
|
|
147
|
+
color: #6B7280;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.progress-section {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 8px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.progress-bar {
|
|
157
|
+
flex: 1;
|
|
158
|
+
height: 6px;
|
|
159
|
+
background: #E5E7EB;
|
|
160
|
+
border-radius: 3px;
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.progress-fill {
|
|
165
|
+
height: 100%;
|
|
166
|
+
border-radius: 3px;
|
|
167
|
+
transition: width 0.3s;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.progress-fill.in-progress {
|
|
171
|
+
background: #3B82F6;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.progress-fill.completed {
|
|
175
|
+
background: #10B981;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.progress-fill.pending {
|
|
179
|
+
background: #9CA3AF;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.progress-text {
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
color: #374151;
|
|
186
|
+
}
|
|
187
|
+
</style>
|
|
@@ -3,84 +3,109 @@
|
|
|
3
3
|
<!-- Header -->
|
|
4
4
|
<div class="relative z-10 px-5 pt-5 pb-4">
|
|
5
5
|
<div class="flex items-center gap-3">
|
|
6
|
-
<div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #
|
|
6
|
+
<div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
|
|
7
7
|
<span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
|
|
8
8
|
</div>
|
|
9
|
-
<
|
|
10
|
-
|
|
9
|
+
<img src="/logo.jpg" style="width:28px;height:28px;border-radius:6px;margin-right:8px;">
|
|
10
|
+
<div class="flex-1">
|
|
11
|
+
<h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">
|
|
11
12
|
SillySpec
|
|
12
13
|
</h1>
|
|
13
|
-
<p class="text-[10px] tracking-widest uppercase" style="color: #
|
|
14
|
+
<p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
|
|
14
15
|
</div>
|
|
16
|
+
<!-- Scan paths gear button -->
|
|
17
|
+
<n-button quaternary size="tiny" @click="showScanPanel = !showScanPanel" :type="showScanPanel ? 'primary' : 'default'">
|
|
18
|
+
<template #icon>
|
|
19
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
20
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
|
21
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
22
|
+
</svg>
|
|
23
|
+
</template>
|
|
24
|
+
</n-button>
|
|
15
25
|
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Scan paths panel (inline) -->
|
|
28
|
+
<Transition name="slide">
|
|
29
|
+
<div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #FFFFFF; border: 1px solid #F0F0F3;">
|
|
30
|
+
<div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">扫描路径</div>
|
|
31
|
+
|
|
32
|
+
<div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无自定义路径</div>
|
|
33
|
+
<div v-else class="space-y-1 mb-2">
|
|
34
|
+
<div v-for="(p, i) in scanPaths" :key="i" class="flex items-center gap-2 text-[10px] group">
|
|
35
|
+
<span class="flex-1 truncate font-mono-log" style="color: #636366;">{{ p }}</span>
|
|
36
|
+
<n-button quaternary size="tiny" type="error" @click="removePath(p)">✕</n-button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Add path -->
|
|
41
|
+
<div v-if="showAddInput" class="flex items-center gap-2">
|
|
42
|
+
<n-input v-model:value="newPath" size="tiny" placeholder="输入目录路径..." @keydown.enter="addPath" @keydown.escape="showAddInput = false" ref="pathInput" />
|
|
43
|
+
<n-button size="tiny" type="primary" @click="addPath">添加</n-button>
|
|
44
|
+
</div>
|
|
45
|
+
<n-button v-else size="tiny" dashed @click="showAddInput = true">+ 添加目录</n-button>
|
|
46
|
+
</div>
|
|
47
|
+
</Transition>
|
|
16
48
|
</div>
|
|
17
49
|
|
|
18
50
|
<!-- Divider -->
|
|
19
|
-
<div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #
|
|
51
|
+
<div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
|
|
20
52
|
|
|
21
53
|
<!-- Projects List -->
|
|
22
54
|
<div class="flex-1 overflow-y-auto py-3 relative z-10">
|
|
23
55
|
<!-- Loading skeleton -->
|
|
24
56
|
<div v-if="isLoading" class="px-4 space-y-2">
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
<
|
|
28
|
-
</
|
|
29
|
-
<p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #
|
|
30
|
-
|
|
57
|
+
<n-card v-for="i in 4" :key="i" size="small" :bordered="false">
|
|
58
|
+
<n-skeleton text :width="80" size="small" />
|
|
59
|
+
<n-skeleton text :width="140" size="small" style="margin-top: 6px;" />
|
|
60
|
+
</n-card>
|
|
61
|
+
<p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
|
|
62
|
+
正在扫描项目...
|
|
31
63
|
</p>
|
|
32
64
|
</div>
|
|
33
65
|
|
|
34
66
|
<!-- Empty state -->
|
|
35
|
-
<
|
|
36
|
-
<div class="w-10 h-10 mx-auto mb-3 rounded-full flex items-center justify-center" style="border: 1px dashed #2A2A2D;">
|
|
37
|
-
<svg class="w-4 h-4" style="color: #525252;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
38
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
|
39
|
-
</svg>
|
|
40
|
-
</div>
|
|
41
|
-
<p class="text-[11px]" style="color: #8B8B8E;">No projects found</p>
|
|
42
|
-
<p class="text-[10px] mt-1" style="color: #525252;">Looking for .sillyspec dirs</p>
|
|
43
|
-
</div>
|
|
67
|
+
<n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
|
|
44
68
|
|
|
45
69
|
<!-- Projects -->
|
|
46
70
|
<div v-else class="px-3 space-y-0.5">
|
|
47
71
|
<div
|
|
48
72
|
v-for="project in projects"
|
|
49
|
-
:key="project.
|
|
50
|
-
:class="[
|
|
51
|
-
'relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group',
|
|
52
|
-
]"
|
|
73
|
+
:key="project.path"
|
|
74
|
+
:class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
|
|
53
75
|
:style="{
|
|
54
|
-
background: isActive(project) ? 'rgba(
|
|
55
|
-
borderLeft: isActive(project) ? '2px solid #
|
|
76
|
+
background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
|
|
77
|
+
borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
|
|
56
78
|
}"
|
|
57
|
-
@mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(
|
|
58
|
-
@mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(
|
|
79
|
+
@mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.08)' : 'rgba(255,255,255,0.02)'"
|
|
80
|
+
@mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent'"
|
|
59
81
|
@click="$emit('select', project)"
|
|
60
82
|
>
|
|
61
83
|
<div class="px-3 py-2.5">
|
|
62
84
|
<div class="flex items-center justify-between gap-2">
|
|
63
85
|
<div class="flex-1 min-w-0">
|
|
64
86
|
<h3
|
|
65
|
-
|
|
66
|
-
:style="{ color: isActive(project) ? '#
|
|
87
|
+
class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
|
|
88
|
+
:style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
|
|
67
89
|
>
|
|
68
90
|
{{ project.name }}
|
|
69
91
|
</h3>
|
|
70
|
-
<p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #
|
|
92
|
+
<p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
|
|
71
93
|
{{ project.path }}
|
|
72
94
|
</p>
|
|
73
95
|
</div>
|
|
74
|
-
<
|
|
96
|
+
<n-tag
|
|
75
97
|
v-if="project.state?.currentStage"
|
|
76
|
-
:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
98
|
+
:type="statusTagType(getProjectStatus(project))"
|
|
99
|
+
size="small"
|
|
100
|
+
:bordered="false"
|
|
101
|
+
round
|
|
102
|
+
>
|
|
103
|
+
{{ stageLabel(project) }}
|
|
104
|
+
</n-tag>
|
|
80
105
|
</div>
|
|
81
106
|
|
|
82
107
|
<!-- Progress -->
|
|
83
|
-
<div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #
|
|
108
|
+
<div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
|
|
84
109
|
<div
|
|
85
110
|
class="h-full rounded-full transition-all duration-500 progress-gradient"
|
|
86
111
|
:style="{ width: getProjectProgress(project) + '%' }"
|
|
@@ -92,29 +117,43 @@
|
|
|
92
117
|
</div>
|
|
93
118
|
|
|
94
119
|
<!-- Footer -->
|
|
95
|
-
<div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #
|
|
120
|
+
<div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
|
|
96
121
|
<div class="flex items-center justify-between">
|
|
97
|
-
<span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #
|
|
98
|
-
<kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #
|
|
122
|
+
<span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ projects.length }} 个项目</span>
|
|
123
|
+
<kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #6B7280; background: #FFFFFF; border: 1px solid #E5E5EA;">⌘K</kbd>
|
|
99
124
|
</div>
|
|
100
125
|
</div>
|
|
101
126
|
</div>
|
|
102
127
|
</template>
|
|
103
128
|
|
|
104
129
|
<script setup>
|
|
105
|
-
import {
|
|
106
|
-
import StageBadge from './StageBadge.vue'
|
|
130
|
+
import { ref, nextTick, watch } from 'vue'
|
|
107
131
|
|
|
108
132
|
const props = defineProps({
|
|
109
133
|
projects: { type: Array, default: () => [] },
|
|
110
134
|
activeProject: { type: Object, default: null },
|
|
111
|
-
isLoading: { type: Boolean, default: false }
|
|
135
|
+
isLoading: { type: Boolean, default: false },
|
|
136
|
+
scanPaths: { type: Array, default: () => [] }
|
|
112
137
|
})
|
|
113
138
|
|
|
114
|
-
const emit = defineEmits(['select'])
|
|
139
|
+
const emit = defineEmits(['select', 'scan:add-path', 'scan:remove-path'])
|
|
140
|
+
|
|
141
|
+
const showScanPanel = ref(false)
|
|
142
|
+
const showAddInput = ref(false)
|
|
143
|
+
const newPath = ref('')
|
|
144
|
+
const pathInput = ref(null)
|
|
145
|
+
|
|
146
|
+
watch(showAddInput, (v) => {
|
|
147
|
+
if (v) nextTick(() => { pathInput.value?.focus() })
|
|
148
|
+
})
|
|
115
149
|
|
|
116
150
|
function isActive(project) {
|
|
117
|
-
return props.activeProject?.
|
|
151
|
+
return props.activeProject?.path === project.path
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function statusTagType(status) {
|
|
155
|
+
const map = { 'in-progress': 'warning', 'completed': 'success', 'failed': 'error', 'blocked': 'warning' }
|
|
156
|
+
return map[status] || 'default'
|
|
118
157
|
}
|
|
119
158
|
|
|
120
159
|
function getProjectStatus(project) {
|
|
@@ -149,4 +188,23 @@ function getProjectProgress(project) {
|
|
|
149
188
|
}
|
|
150
189
|
return total === 0 ? 0 : Math.round((done / total) * 100)
|
|
151
190
|
}
|
|
191
|
+
|
|
192
|
+
function addPath() {
|
|
193
|
+
const p = newPath.value.trim()
|
|
194
|
+
if (p) {
|
|
195
|
+
emit('scan:add-path', p)
|
|
196
|
+
newPath.value = ''
|
|
197
|
+
showAddInput.value = false
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function removePath(p) {
|
|
202
|
+
emit('scan:remove-path', p)
|
|
203
|
+
}
|
|
152
204
|
</script>
|
|
205
|
+
|
|
206
|
+
<style scoped>
|
|
207
|
+
.slide-enter-active, .slide-leave-active { transition: all 200ms ease; }
|
|
208
|
+
.slide-enter-from, .slide-leave-to { opacity: 0; max-height: 0; overflow: hidden; }
|
|
209
|
+
.slide-enter-to, .slide-leave-from { max-height: 300px; }
|
|
210
|
+
</style>
|