sillyspec 3.20.3 → 3.20.4
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 +21 -17
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
- package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -17
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
- package/.husky/pre-push +13 -0
- package/CLAUDE.md +18 -0
- package/README.md +198 -186
- package/SKILL.md +90 -87
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -0
- package/docs/plan-execute-contract.md +123 -0
- package/docs/platform-scan-protocol.md +298 -0
- package/docs/revision-mode.md +115 -0
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
- package/docs/sillyspec/file-lifecycle.md +125 -0
- package/docs/workflow-contract-regression.md +106 -0
- package/docs/worktree-isolation.md +252 -0
- package/package.json +40 -34
- package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -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/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -508
- package/packages/dashboard/server/parser.js +526 -458
- package/packages/dashboard/server/watcher.js +350 -342
- package/packages/dashboard/src/App.vue +558 -325
- package/packages/dashboard/src/components/ActionBar.vue +93 -84
- package/packages/dashboard/src/components/CommandPalette.vue +96 -92
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/DocPreview.vue +105 -8
- package/packages/dashboard/src/components/DocTree.vue +75 -19
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -75
- package/packages/dashboard/src/components/PipelineView.vue +156 -129
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
- package/packages/dashboard/src/components/StageBadge.vue +67 -53
- package/packages/dashboard/src/components/StepCard.vue +94 -89
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +192 -164
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -0
- package/src/change-list.js +52 -0
- package/src/change-risk-profile.js +352 -0
- package/src/classify-change.js +73 -0
- package/src/constants.js +70 -0
- package/src/contract-matrix.js +278 -0
- package/src/db.js +201 -0
- package/src/endpoint-extractor.js +315 -0
- package/src/hooks/claude-pre-tool-use.cjs +125 -0
- package/src/hooks/worktree-guard.js +761 -0
- package/src/index.js +922 -252
- package/src/init.js +431 -375
- package/src/knowledge-match.js +130 -0
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -0
- package/src/progress.js +1734 -508
- package/src/run.js +3465 -652
- package/src/scan-postcheck.js +387 -0
- package/src/setup.js +398 -460
- package/src/stage-contract.js +700 -0
- package/src/stages/archive.js +160 -54
- package/src/stages/brainstorm-auto.js +229 -0
- package/src/stages/brainstorm.js +645 -239
- package/src/stages/doctor.js +365 -312
- package/src/stages/execute.js +625 -264
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +29 -35
- package/src/stages/knowledge.js +498 -0
- package/src/stages/plan-postcheck.js +511 -0
- package/src/stages/plan.js +582 -279
- package/src/stages/propose.js +174 -115
- package/src/stages/quick.js +102 -63
- package/src/stages/scan.js +558 -141
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -135
- package/src/sync.js +497 -0
- package/src/task-review.js +346 -0
- package/src/workflow.js +785 -0
- package/src/worktree-apply.js +549 -0
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -0
- package/templates/workflows/archive-impact.yaml +79 -0
- package/templates/workflows/scan-docs.yaml +132 -0
- package/test/brainstorm-plan-contract.test.mjs +273 -0
- package/test/check-syntax.mjs +26 -0
- package/test/cli-top-level-aliases.test.mjs +174 -0
- package/test/contract-artifacts.test.mjs +323 -0
- package/test/decision-supersede.test.mjs +277 -0
- package/test/knowledge-match.test.mjs +231 -0
- package/test/plan-execute-contract.test.mjs +330 -0
- package/test/plan-optimization.test.mjs +572 -0
- package/test/platform-artifacts.test.mjs +190 -0
- package/test/platform-failure-samples.test.mjs +199 -0
- package/test/platform-recovery-chain.test.mjs +179 -0
- package/test/platform-recovery.test.mjs +167 -0
- package/test/platform-scan-p0.test.mjs +175 -0
- package/test/revision-v1.test.mjs +1145 -0
- package/test/run-sanitize-project-name.test.mjs +51 -0
- package/test/run-scan-postcheck-fail.test.mjs +64 -0
- package/test/run-scan-project-parse.test.mjs +200 -0
- package/test/run-tests.mjs +48 -0
- package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
- package/test/scan-knowledge.test.mjs +175 -0
- package/test/scan-paths.test.mjs +68 -0
- package/test/scan-postcheck-project-priority.test.mjs +85 -0
- package/test/scan-postcheck.test.mjs +197 -0
- package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
- package/test/spec-dir.test.mjs +206 -0
- package/test/stage-contract-failed-post-check.test.mjs +102 -0
- package/test/stage-contract.test.mjs +299 -0
- package/test/stage-definitions.test.mjs +39 -0
- package/test/wait-gates.test.mjs +501 -0
- package/test/workflow-spec-base.test.mjs +142 -0
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +136 -0
- package/test/worktree-native-overlay.test.mjs +188 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
- package/.sillyspec/changes/auto-mode/design.md +0 -50
- package/.sillyspec/changes/auto-mode/proposal.md +0 -19
- package/.sillyspec/changes/auto-mode/requirements.md +0 -21
- package/.sillyspec/changes/auto-mode/tasks.md +0 -7
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
- package/.sillyspec/changes/run-command-design/design.md +0 -1230
- package/.sillyspec/changes/unified-docs-design/design.md +0 -199
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +0 -8
- package/.sillyspec/knowledge/uncategorized.md +0 -3
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- 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/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
- package/src/derive.js +0 -147
- package/src/step.js +0 -543
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
:class="['flex flex-col transition-all duration-300', isOpen ? 'w-[340px]' : 'w-0 opacity-0 overflow-hidden']"
|
|
4
|
-
style="background: #F5F5F7;"
|
|
5
|
-
>
|
|
6
|
-
<!-- Header -->
|
|
7
|
-
<div class="px-4 py-3 flex items-center justify-between flex-shrink-0" style="border-bottom: 1px solid #F0F0F3;">
|
|
8
|
-
<h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ detailType ? detailTitle : '详情' }}</h2>
|
|
9
|
-
<n-button quaternary size="tiny" @click="handleClose">
|
|
10
|
-
<template #icon>
|
|
11
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
13
|
-
</svg>
|
|
14
|
-
</template>
|
|
15
|
-
</n-button>
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
<!-- Content -->
|
|
19
|
-
<div class="flex-1 overflow-y-auto">
|
|
20
|
-
<!-- Empty state -->
|
|
21
|
-
<n-empty v-if="!activeStep && !detailType" description="选择一个步骤" style="margin: auto; padding: 48px 0;" />
|
|
22
|
-
|
|
23
|
-
<!-- Detail views -->
|
|
24
|
-
<template v-if="detailType === 'git'">
|
|
25
|
-
<GitDetail :data="detailData" />
|
|
26
|
-
</template>
|
|
27
|
-
<template v-else-if="detailType === 'tech'">
|
|
28
|
-
<TechDetail :data="detailData" />
|
|
29
|
-
</template>
|
|
30
|
-
<template v-else-if="detailType === 'docs'">
|
|
31
|
-
<DocsDetail :data="detailData" @open-file="$emit('open-doc-file', $event)" />
|
|
32
|
-
</template>
|
|
33
|
-
|
|
34
|
-
<!-- Step detail (original) -->
|
|
35
|
-
<div v-else-if="activeStep">
|
|
36
|
-
<!-- Title -->
|
|
37
|
-
<div class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
38
|
-
<h3 class="text-[13px] font-semibold font-[JetBrains_Mono,monospace]" style="color: #D97706;">
|
|
39
|
-
{{ activeStep.title || activeStep.name }}
|
|
40
|
-
</h3>
|
|
41
|
-
<div v-if="activeStep.status" class="mt-2">
|
|
42
|
-
<StageBadge :status="activeStep.status" />
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<!-- Description -->
|
|
47
|
-
<div v-if="activeStep.description || activeStep.summary" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
48
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">描述</h4>
|
|
49
|
-
<p class="text-[11px] leading-relaxed" style="color: #636366;">{{ activeStep.description || activeStep.summary }}</p>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
<!-- Conclusion -->
|
|
53
|
-
<div v-if="activeStep.conclusion" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
54
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">结论</h4>
|
|
55
|
-
<p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.conclusion }}</p>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
<!-- Decision -->
|
|
59
|
-
<div v-if="activeStep.decision" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
60
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">决策</h4>
|
|
61
|
-
<p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.decision }}</p>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<!-- User Query -->
|
|
65
|
-
<div v-if="activeStep.userQuery" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
66
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">用户提问</h4>
|
|
67
|
-
<div class="px-3 py-2 rounded-md" style="background: #F5F5F7; border: 1px solid #F0F0F3;">
|
|
68
|
-
<p class="text-[11px] italic" style="color: #636366;">"{{ activeStep.userQuery }}"</p>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<!-- Metadata -->
|
|
73
|
-
<div v-if="activeStep.duration || activeStep.timestamp" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
74
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">元信息</h4>
|
|
75
|
-
<div class="space-y-1 text-[11px]" style="color: #6B7280;">
|
|
76
|
-
<div v-if="activeStep.duration"><span style="color: #636366;">耗时:</span> {{ activeStep.duration }}</div>
|
|
77
|
-
<div v-if="activeStep.timestamp"><span style="color: #636366;">时间:</span> {{ formatTimestamp(activeStep.timestamp) }}</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<!-- Output -->
|
|
82
|
-
<div v-if="activeStep.output || activeStep.files" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
83
|
-
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">输出</h4>
|
|
84
|
-
<n-code v-if="activeStep.output" :code="activeStep.output" language="text" word-wrap style="max-height: 300px; overflow-y: auto; padding: 8px; border-radius: 4px; background: #F5F5F7;" />
|
|
85
|
-
<div v-if="activeStep.files" class="mt-2 space-y-1">
|
|
86
|
-
<div v-for="(file, i) in activeStep.files" :key="i" class="flex items-center gap-2 text-[10px]" style="color: #6B7280;">
|
|
87
|
-
<svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
88
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
89
|
-
</svg>
|
|
90
|
-
<span class="truncate">{{ file }}</span>
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<!-- Log Stream -->
|
|
98
|
-
<div class="flex-shrink-0" style="height: 200px; border-top: 1px solid #F0F0F3;">
|
|
99
|
-
<LogStream :logs="logs" @clear="$emit('clear-logs')" />
|
|
100
|
-
</div>
|
|
101
|
-
</div>
|
|
102
|
-
</template>
|
|
103
|
-
|
|
104
|
-
<script setup>
|
|
105
|
-
import { computed } from 'vue'
|
|
106
|
-
import StageBadge from './StageBadge.vue'
|
|
107
|
-
import LogStream from './LogStream.vue'
|
|
108
|
-
import GitDetail from './detail/GitDetail.vue'
|
|
109
|
-
import TechDetail from './detail/TechDetail.vue'
|
|
110
|
-
import DocsDetail from './detail/DocsDetail.vue'
|
|
111
|
-
|
|
112
|
-
const props = defineProps({
|
|
113
|
-
isOpen: { type: Boolean, default: true },
|
|
114
|
-
activeStep: { type: Object, default: null },
|
|
115
|
-
logs: { type: Array, default: () => [] },
|
|
116
|
-
detailType: { type: String, default: null },
|
|
117
|
-
detailData: { type: [Object, Array], default: null }
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
const emit = defineEmits(['close', 'clear-logs', 'open-doc-file'])
|
|
121
|
-
|
|
122
|
-
const detailTitle = computed(() => {
|
|
123
|
-
if (props.detailType === 'git') return 'Git 详情'
|
|
124
|
-
if (props.detailType === 'tech') return '技术栈详情'
|
|
125
|
-
if (props.detailType === 'docs') return '文档列表'
|
|
126
|
-
return '详情'
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
function handleClose() {
|
|
130
|
-
emit('close')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function formatTimestamp(ts) {
|
|
134
|
-
if (!ts) return ''
|
|
135
|
-
return new Date(ts).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
136
|
-
}
|
|
137
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="['flex flex-col transition-all duration-300', isOpen ? 'w-[340px]' : 'w-0 opacity-0 overflow-hidden']"
|
|
4
|
+
style="background: #F5F5F7;"
|
|
5
|
+
>
|
|
6
|
+
<!-- Header -->
|
|
7
|
+
<div class="px-4 py-3 flex items-center justify-between flex-shrink-0" style="border-bottom: 1px solid #F0F0F3;">
|
|
8
|
+
<h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ detailType ? detailTitle : '详情' }}</h2>
|
|
9
|
+
<n-button quaternary size="tiny" @click="handleClose">
|
|
10
|
+
<template #icon>
|
|
11
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
13
|
+
</svg>
|
|
14
|
+
</template>
|
|
15
|
+
</n-button>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- Content -->
|
|
19
|
+
<div class="flex-1 overflow-y-auto">
|
|
20
|
+
<!-- Empty state -->
|
|
21
|
+
<n-empty v-if="!activeStep && !detailType" description="选择一个步骤" style="margin: auto; padding: 48px 0;" />
|
|
22
|
+
|
|
23
|
+
<!-- Detail views -->
|
|
24
|
+
<template v-if="detailType === 'git'">
|
|
25
|
+
<GitDetail :data="detailData" />
|
|
26
|
+
</template>
|
|
27
|
+
<template v-else-if="detailType === 'tech'">
|
|
28
|
+
<TechDetail :data="detailData" />
|
|
29
|
+
</template>
|
|
30
|
+
<template v-else-if="detailType === 'docs'">
|
|
31
|
+
<DocsDetail :data="detailData" @open-file="$emit('open-doc-file', $event)" />
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<!-- Step detail (original) -->
|
|
35
|
+
<div v-else-if="activeStep">
|
|
36
|
+
<!-- Title -->
|
|
37
|
+
<div class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
38
|
+
<h3 class="text-[13px] font-semibold font-[JetBrains_Mono,monospace]" style="color: #D97706;">
|
|
39
|
+
{{ activeStep.title || activeStep.name }}
|
|
40
|
+
</h3>
|
|
41
|
+
<div v-if="activeStep.status" class="mt-2">
|
|
42
|
+
<StageBadge :status="activeStep.status" />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Description -->
|
|
47
|
+
<div v-if="activeStep.description || activeStep.summary" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
48
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">描述</h4>
|
|
49
|
+
<p class="text-[11px] leading-relaxed" style="color: #636366;">{{ activeStep.description || activeStep.summary }}</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Conclusion -->
|
|
53
|
+
<div v-if="activeStep.conclusion" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
54
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">结论</h4>
|
|
55
|
+
<p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.conclusion }}</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Decision -->
|
|
59
|
+
<div v-if="activeStep.decision" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
60
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">决策</h4>
|
|
61
|
+
<p class="text-[11px] leading-relaxed" style="color: #1C1C1E;">{{ activeStep.decision }}</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- User Query -->
|
|
65
|
+
<div v-if="activeStep.userQuery" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
66
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">用户提问</h4>
|
|
67
|
+
<div class="px-3 py-2 rounded-md" style="background: #F5F5F7; border: 1px solid #F0F0F3;">
|
|
68
|
+
<p class="text-[11px] italic" style="color: #636366;">"{{ activeStep.userQuery }}"</p>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<!-- Metadata -->
|
|
73
|
+
<div v-if="activeStep.duration || activeStep.timestamp" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
74
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">元信息</h4>
|
|
75
|
+
<div class="space-y-1 text-[11px]" style="color: #6B7280;">
|
|
76
|
+
<div v-if="activeStep.duration"><span style="color: #636366;">耗时:</span> {{ activeStep.duration }}</div>
|
|
77
|
+
<div v-if="activeStep.timestamp"><span style="color: #636366;">时间:</span> {{ formatTimestamp(activeStep.timestamp) }}</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Output -->
|
|
82
|
+
<div v-if="activeStep.output || activeStep.files" class="px-4 py-3" style="border-bottom: 1px solid #F0F0F3;">
|
|
83
|
+
<h4 class="text-[9px] font-semibold uppercase tracking-[0.2em] mb-1.5 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">输出</h4>
|
|
84
|
+
<n-code v-if="activeStep.output" :code="activeStep.output" language="text" word-wrap style="max-height: 300px; overflow-y: auto; padding: 8px; border-radius: 4px; background: #F5F5F7;" />
|
|
85
|
+
<div v-if="activeStep.files" class="mt-2 space-y-1">
|
|
86
|
+
<div v-for="(file, i) in activeStep.files" :key="i" class="flex items-center gap-2 text-[10px]" style="color: #6B7280;">
|
|
87
|
+
<svg class="w-3 h-3 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
88
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
89
|
+
</svg>
|
|
90
|
+
<span class="truncate">{{ file }}</span>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Log Stream -->
|
|
98
|
+
<div class="flex-shrink-0" style="height: 200px; border-top: 1px solid #F0F0F3;">
|
|
99
|
+
<LogStream :logs="logs" @clear="$emit('clear-logs')" />
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
<script setup>
|
|
105
|
+
import { computed } from 'vue'
|
|
106
|
+
import StageBadge from './StageBadge.vue'
|
|
107
|
+
import LogStream from './LogStream.vue'
|
|
108
|
+
import GitDetail from './detail/GitDetail.vue'
|
|
109
|
+
import TechDetail from './detail/TechDetail.vue'
|
|
110
|
+
import DocsDetail from './detail/DocsDetail.vue'
|
|
111
|
+
|
|
112
|
+
const props = defineProps({
|
|
113
|
+
isOpen: { type: Boolean, default: true },
|
|
114
|
+
activeStep: { type: Object, default: null },
|
|
115
|
+
logs: { type: Array, default: () => [] },
|
|
116
|
+
detailType: { type: String, default: null },
|
|
117
|
+
detailData: { type: [Object, Array], default: null }
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const emit = defineEmits(['close', 'clear-logs', 'open-doc-file'])
|
|
121
|
+
|
|
122
|
+
const detailTitle = computed(() => {
|
|
123
|
+
if (props.detailType === 'git') return 'Git 详情'
|
|
124
|
+
if (props.detailType === 'tech') return '技术栈详情'
|
|
125
|
+
if (props.detailType === 'docs') return '文档列表'
|
|
126
|
+
return '详情'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
function handleClose() {
|
|
130
|
+
emit('close')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatTimestamp(ts) {
|
|
134
|
+
if (!ts) return ''
|
|
135
|
+
return new Date(ts).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
|
136
|
+
}
|
|
137
|
+
</script>
|
|
@@ -1,29 +1,59 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<div v-if="!content && !loading" class="
|
|
4
|
-
<p
|
|
2
|
+
<div class="doc-preview-shell">
|
|
3
|
+
<div v-if="!content && !loading" class="doc-empty">
|
|
4
|
+
<p>选择一个文档查看内容</p>
|
|
5
5
|
</div>
|
|
6
|
-
<div v-else-if="loading" class="
|
|
7
|
-
<p
|
|
6
|
+
<div v-else-if="loading" class="doc-empty">
|
|
7
|
+
<p>加载中...</p>
|
|
8
8
|
</div>
|
|
9
|
-
<
|
|
9
|
+
<template v-else>
|
|
10
|
+
<div class="doc-toolbar">
|
|
11
|
+
<div class="doc-title" :title="fileTitle">{{ fileTitle }}</div>
|
|
12
|
+
<n-button size="tiny" type="primary" @click="isModalOpen = true">弹窗阅读</n-button>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="doc-preview-scroll">
|
|
15
|
+
<div class="doc-preview" v-html="renderedContent"></div>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<n-modal
|
|
20
|
+
v-model:show="isModalOpen"
|
|
21
|
+
preset="card"
|
|
22
|
+
:title="fileTitle"
|
|
23
|
+
style="width: min(920px, calc(100vw - 48px));"
|
|
24
|
+
>
|
|
25
|
+
<div class="doc-modal-body">
|
|
26
|
+
<div class="doc-preview doc-preview-large" v-html="renderedContent"></div>
|
|
27
|
+
</div>
|
|
28
|
+
</n-modal>
|
|
10
29
|
</div>
|
|
11
30
|
</template>
|
|
12
31
|
|
|
13
32
|
<script setup>
|
|
14
|
-
import { computed } from 'vue'
|
|
33
|
+
import { computed, ref, watch } from 'vue'
|
|
15
34
|
import { marked } from 'marked'
|
|
16
35
|
|
|
17
36
|
const props = defineProps({
|
|
18
37
|
content: { type: String, default: '' },
|
|
19
|
-
loading: { type: Boolean, default: false }
|
|
38
|
+
loading: { type: Boolean, default: false },
|
|
39
|
+
selectedFile: { type: Object, default: null }
|
|
20
40
|
})
|
|
21
41
|
|
|
42
|
+
const isModalOpen = ref(false)
|
|
43
|
+
|
|
22
44
|
marked.setOptions({
|
|
23
45
|
breaks: true,
|
|
24
46
|
gfm: true
|
|
25
47
|
})
|
|
26
48
|
|
|
49
|
+
watch(() => props.selectedFile?.path, () => {
|
|
50
|
+
isModalOpen.value = false
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const fileTitle = computed(() => {
|
|
54
|
+
return props.selectedFile?.title || props.selectedFile?.name || '文档'
|
|
55
|
+
})
|
|
56
|
+
|
|
27
57
|
const renderedContent = computed(() => {
|
|
28
58
|
if (!props.content) return ''
|
|
29
59
|
return marked.parse(props.content)
|
|
@@ -31,10 +61,69 @@ const renderedContent = computed(() => {
|
|
|
31
61
|
</script>
|
|
32
62
|
|
|
33
63
|
<style scoped>
|
|
64
|
+
.doc-preview-shell {
|
|
65
|
+
height: 100%;
|
|
66
|
+
min-height: 0;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
background: #FFFFFF;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.doc-empty {
|
|
73
|
+
height: 100%;
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
padding: 24px;
|
|
78
|
+
text-align: center;
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
color: #636366;
|
|
81
|
+
font-family: 'JetBrains Mono', monospace;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.doc-toolbar {
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: space-between;
|
|
89
|
+
gap: 12px;
|
|
90
|
+
padding: 10px 14px;
|
|
91
|
+
border-bottom: 1px solid #F0F0F3;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.doc-title {
|
|
95
|
+
min-width: 0;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
text-overflow: ellipsis;
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
color: #1C1C1E;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.doc-preview-scroll {
|
|
105
|
+
flex: 1;
|
|
106
|
+
min-height: 0;
|
|
107
|
+
overflow: auto;
|
|
108
|
+
padding: 14px 16px 22px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.doc-modal-body {
|
|
112
|
+
max-height: min(72vh, 760px);
|
|
113
|
+
overflow: auto;
|
|
114
|
+
padding-right: 4px;
|
|
115
|
+
}
|
|
116
|
+
|
|
34
117
|
.doc-preview {
|
|
35
118
|
font-size: 13px;
|
|
36
119
|
line-height: 1.7;
|
|
37
120
|
color: #374151;
|
|
121
|
+
overflow-wrap: anywhere;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.doc-preview-large {
|
|
125
|
+
font-size: 14px;
|
|
126
|
+
line-height: 1.75;
|
|
38
127
|
}
|
|
39
128
|
|
|
40
129
|
.doc-preview :deep(h1) {
|
|
@@ -46,6 +135,10 @@ const renderedContent = computed(() => {
|
|
|
46
135
|
padding-bottom: 8px;
|
|
47
136
|
}
|
|
48
137
|
|
|
138
|
+
.doc-preview-large :deep(h1) {
|
|
139
|
+
font-size: 22px;
|
|
140
|
+
}
|
|
141
|
+
|
|
49
142
|
.doc-preview :deep(h2) {
|
|
50
143
|
color: #1C1C1E;
|
|
51
144
|
font-size: 15px;
|
|
@@ -53,6 +146,10 @@ const renderedContent = computed(() => {
|
|
|
53
146
|
margin: 20px 0 10px;
|
|
54
147
|
}
|
|
55
148
|
|
|
149
|
+
.doc-preview-large :deep(h2) {
|
|
150
|
+
font-size: 17px;
|
|
151
|
+
}
|
|
152
|
+
|
|
56
153
|
.doc-preview :deep(h3) {
|
|
57
154
|
color: #D97706;
|
|
58
155
|
font-size: 14px;
|
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
2
|
+
<div class="doc-tree">
|
|
3
|
+
<div class="doc-tree-search">
|
|
4
|
+
<n-input
|
|
5
|
+
v-model:value="query"
|
|
6
|
+
size="small"
|
|
7
|
+
clearable
|
|
8
|
+
placeholder="搜索文档"
|
|
9
|
+
/>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
3
12
|
<n-empty v-if="groups.length === 0" description="暂无文档" style="margin: auto;" />
|
|
13
|
+
<n-empty v-else-if="treeData.length === 0" description="无匹配文档" style="margin: auto;" />
|
|
4
14
|
<n-tree
|
|
5
15
|
v-else
|
|
16
|
+
class="doc-tree-list"
|
|
6
17
|
:data="treeData"
|
|
7
18
|
:selected-keys="selectedKeys"
|
|
8
19
|
:default-expand-all="true"
|
|
9
20
|
selectable
|
|
10
21
|
block-line
|
|
11
22
|
@update:selected-keys="handleSelect"
|
|
12
|
-
style="padding: 8px 12px;"
|
|
13
23
|
/>
|
|
14
24
|
</div>
|
|
15
25
|
</template>
|
|
16
26
|
|
|
17
27
|
<script setup>
|
|
18
|
-
import { computed,
|
|
19
|
-
import { NIcon } from 'naive-ui'
|
|
28
|
+
import { computed, ref } from 'vue'
|
|
20
29
|
|
|
21
30
|
const props = defineProps({
|
|
22
31
|
groups: { type: Array, default: () => [] },
|
|
@@ -24,30 +33,48 @@ const props = defineProps({
|
|
|
24
33
|
})
|
|
25
34
|
|
|
26
35
|
const emit = defineEmits(['select-file'])
|
|
36
|
+
const query = ref('')
|
|
27
37
|
|
|
28
38
|
const selectedKeys = computed(() => {
|
|
29
39
|
return props.selectedFile?.path ? [props.selectedFile.path] : []
|
|
30
40
|
})
|
|
31
41
|
|
|
32
42
|
const groupIcons = {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
design: '📋',
|
|
44
|
+
plan: '🧾',
|
|
45
|
+
archive: '📦',
|
|
46
|
+
changes: '⚙️',
|
|
47
|
+
scan: '🔍',
|
|
48
|
+
quicklog: '⚡'
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
const treeData = computed(() => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
const keyword = query.value.trim().toLowerCase()
|
|
53
|
+
|
|
54
|
+
return props.groups
|
|
55
|
+
.map(group => {
|
|
56
|
+
const groupLabel = group.project ? `${group.label} (${group.project})` : group.label
|
|
57
|
+
const groupMatched = !keyword || groupLabel.toLowerCase().includes(keyword)
|
|
58
|
+
const files = group.files.filter(file => {
|
|
59
|
+
if (groupMatched) return true
|
|
60
|
+
return `${file.title || ''} ${file.name || ''}`.toLowerCase().includes(keyword)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (files.length === 0) return null
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
key: `group-${group.key}`,
|
|
67
|
+
label: groupLabel,
|
|
68
|
+
prefix: () => groupIcons[group.key] || '📄',
|
|
69
|
+
children: files.map(file => ({
|
|
70
|
+
key: file.path,
|
|
71
|
+
label: file.title || file.name,
|
|
72
|
+
prefix: () => '📄',
|
|
73
|
+
data: file
|
|
74
|
+
}))
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
.filter(Boolean)
|
|
51
78
|
})
|
|
52
79
|
|
|
53
80
|
function handleSelect(keys, option) {
|
|
@@ -56,3 +83,32 @@ function handleSelect(keys, option) {
|
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
</script>
|
|
86
|
+
|
|
87
|
+
<style scoped>
|
|
88
|
+
.doc-tree {
|
|
89
|
+
height: 100%;
|
|
90
|
+
min-height: 0;
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
background: #FFFFFF;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.doc-tree-search {
|
|
97
|
+
flex-shrink: 0;
|
|
98
|
+
padding: 10px 12px 8px;
|
|
99
|
+
border-bottom: 1px solid #F0F0F3;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.doc-tree-list {
|
|
103
|
+
flex: 1;
|
|
104
|
+
min-height: 0;
|
|
105
|
+
overflow: auto;
|
|
106
|
+
padding: 8px 12px 14px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.doc-tree-list :deep(.n-tree-node-content__text) {
|
|
110
|
+
white-space: normal;
|
|
111
|
+
line-height: 1.35;
|
|
112
|
+
overflow-wrap: anywhere;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="h-resize-handle"
|
|
4
|
+
:class="{ 'dragging': isDragging }"
|
|
5
|
+
@mousedown="handleMouseDown"
|
|
6
|
+
></div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup>
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
isDragging: { type: Boolean, default: false },
|
|
12
|
+
colIndex: { type: Number, required: true }
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits(['drag-start', 'drag-end', 'resize'])
|
|
16
|
+
|
|
17
|
+
function handleMouseDown(e) {
|
|
18
|
+
e.preventDefault()
|
|
19
|
+
emit('drag-start', { type: 'horizontal', colIndex: props.colIndex, startX: e.clientX })
|
|
20
|
+
|
|
21
|
+
const onMove = (ev) => {
|
|
22
|
+
emit('resize', { deltaX: ev.clientX - e.clientX })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const onUp = () => {
|
|
26
|
+
emit('drag-end')
|
|
27
|
+
window.removeEventListener('mousemove', onMove)
|
|
28
|
+
window.removeEventListener('mouseup', onUp)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
window.addEventListener('mousemove', onMove)
|
|
32
|
+
window.addEventListener('mouseup', onUp)
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<style scoped>
|
|
37
|
+
.h-resize-handle {
|
|
38
|
+
width: 4px;
|
|
39
|
+
background: #E5E7EB;
|
|
40
|
+
cursor: col-resize;
|
|
41
|
+
transition: background 0.2s;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.h-resize-handle:hover,
|
|
45
|
+
.h-resize-handle.dragging {
|
|
46
|
+
background: #D97706;
|
|
47
|
+
}
|
|
48
|
+
</style>
|