feat: comprehensive documentation and demo updates

- Update READMEs and docs across multiple languages
- Enhance interactive demos for Agent, LLM, VLM, Audio, Image Gen, Terminal, and Web Basics
- Add new appendix sections for Database and IDE intros
- Update VitePress config, theme, and utility scripts
- Clean up unused assets and components
This commit is contained in:
sanbuphy
2026-01-16 19:10:21 +08:00
parent c8567ce23f
commit 73f4788d7e
150 changed files with 19530 additions and 13401 deletions
@@ -0,0 +1,60 @@
<template>
<div class="branch-demo">
<div class="panel">
<div class="controls">
<button @click="init" :disabled="inited" class="btn">初始化</button>
<button @click="commit" :disabled="!inited" class="btn">提交</button>
<button @click="branch" :disabled="!inited || hasBranch" class="btn">创建分支</button>
<button @click="merge" :disabled="!hasBranch" class="btn">合并</button>
<button @click="reset" class="btn secondary">重置</button>
</div>
<div class="graph">
<svg viewBox="0 0 400 120">
<line x1="50" y1="40" x2="350" y2="40" stroke="#3b82f6" stroke-width="3"/>
<line v-if="hasBranch" x1="150" y1="40" x2="150" y2="80" stroke="#10b981" stroke-width="3"/>
<line v-if="hasBranch" x1="150" y1="80" x2="300" y2="80" stroke="#10b981" stroke-width="3"/>
<circle v-for="(c,i) in main" :cx="60+i*50" cy="40" r="8" fill="#3b82f6"/>
<circle v-for="(c,i) in feat" :cx="180+i*50" cy="80" r="8" fill="#10b981"/>
</svg>
</div>
<div class="status">
<span>提交: {{ main.length }}</span>
<span>分支: {{ hasBranch ? 2 : 1 }}</span>
</div>
</div>
<div class="info-box">
<p><strong>💡 分支策略:</strong> 并行开发互不干扰最后合并</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const inited = ref(false)
const hasBranch = ref(false)
const main = ref([])
const feat = ref([])
const init = () => { inited.value = true; main.value = [1] }
const commit = () => { if(inited.value) main.value.push(1) }
const branch = () => { if(inited.value) { hasBranch.value = true; feat.value = [1] } }
const merge = () => { if(hasBranch.value) { main.value.push(1); hasBranch.value = false; feat.value = [] } }
const reset = () => { inited.value = false; hasBranch.value = false; main.value = []; feat.value = [] }
</script>
<style scoped>
.branch-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.btn { padding: 0.5rem 1rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
.btn:hover:not(:disabled) { background: var(--vp-c-brand); color: var(--vp-c-bg); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.secondary { border-color: var(--vp-c-divider); }
.graph { background: var(--vp-c-bg); border-radius: 8px; padding: 1rem; border: 1px solid var(--vp-c-divider); margin: 1rem 0; }
.graph svg { width: 100%; height: auto; }
.status { display: flex; gap: 2rem; }
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; margin-top: 1rem; }
.info-box p { margin: 0; color: var(--vp-c-text-1); }
</style>
@@ -0,0 +1,153 @@
<template>
<div class="command-demo">
<div class="panel">
<div class="terminal">
<div class="output">
<div v-for="(line, i) in output" :key="i" :class="line.type">
<span v-if="line.type === 'command'" class="prompt">$</span>
<span v-html="line.text"></span>
</div>
<div v-if="output.length === 0" class="welcome">
输入命令开始学习 Git
</div>
</div>
<div class="input-line">
<span class="prompt">$</span>
<input v-model="cmd" @keyup.enter="execute" placeholder="git status" class="cmd-input" />
<button @click="execute" class="run-btn">运行</button>
</div>
</div>
<div class="quick-cmds">
<button @click="runCmd('git init')" class="cmd-btn">初始化</button>
<button @click="runCmd('git status')" class="cmd-btn">状态</button>
<button @click="runCmd('git add .')" class="cmd-btn">添加</button>
<button @click="runCmd('git commit -m \'msg\'')" class="cmd-btn">提交</button>
</div>
</div>
<div class="info-box">
<p><strong>💡 常用命令:</strong> init status add commit</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const cmd = ref('')
const output = ref([])
const execute = () => {
const c = cmd.value.trim()
if (!c) return
output.value.push({ type: 'command', text: c })
if (c === 'git init') {
output.value.push({ type: 'success', text: 'Initialized empty Git repository' })
} else if (c === 'git status') {
output.value.push({ type: 'info', text: 'On branch main\nnothing to commit' })
} else if (c === 'git add .') {
output.value.push({ type: 'success', text: 'Files added to staging area' })
} else if (c.startsWith('git commit')) {
output.value.push({ type: 'success', text: '1 file committed' })
} else {
output.value.push({ type: 'error', text: 'Unknown command' })
}
cmd.value = ''
}
const runCmd = (c) => {
cmd.value = c
execute()
}
</script>
<style scoped>
.command-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
padding: 1.5rem;
margin: 1rem 0;
}
.terminal {
background: #1f2937;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
font-family: monospace;
}
.output {
min-height: 150px;
margin-bottom: 1rem;
color: #d1d5db;
}
.output .command { color: #10b981; }
.output .success { color: #10b981; }
.output .error { color: #ef4444; }
.output .info { color: #60a5fa; }
.output .welcome { color: #9ca3af; font-style: italic; }
.input-line {
display: flex;
gap: 0.5rem;
align-items: center;
}
.prompt { color: #10b981; }
.cmd-input {
flex: 1;
background: transparent;
border: none;
color: #d1d5db;
font-family: monospace;
font-size: 0.875rem;
}
.cmd-input:focus { outline: none; }
.run-btn {
padding: 0.25rem 0.75rem;
background: #10b981;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
}
.quick-cmds {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.cmd-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--vp-c-brand);
background: var(--vp-c-bg);
color: var(--vp-c-brand);
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
}
.cmd-btn:hover { background: var(--vp-c-brand); color: var(--vp-c-bg); }
.info-box {
padding: 1rem;
background: var(--vp-c-bg);
border-left: 4px solid var(--vp-c-brand);
border-radius: 4px;
margin-top: 1rem;
}
.info-box p { margin: 0; color: var(--vp-c-text-1); line-height: 1.6; }
</style>
@@ -0,0 +1,48 @@
<template>
<div class="conflict-demo">
<div class="panel">
<div class="editor">
<div class="line normal"><span class="ln">1</span>function greet() {</div>
<div class="line normal"><span class="ln">2</span> console.log('Hi');</div>
<div class="line conflict"><span class="ln">3</span>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</div>
<div class="line current"><span class="ln">4</span> console.log('Welcome') // 当前版本</div>
<div class="line conflict"><span class="ln">5</span>=======</div>
<div class="line incoming"><span class="ln">6</span> console.log('Greetings') // 传入版本</div>
<div class="line conflict"><span class="ln">7</span>&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature</div>
<div class="line normal"><span class="ln">8</span> console.log('Bye');</div>
</div>
<div class="actions">
<button @click="resolve('current')" class="action-btn">保留当前</button>
<button @click="resolve('incoming')" class="action-btn">保留传入</button>
<button @click="resolve('manual')" class="action-btn">手动合并</button>
</div>
</div>
<div class="info-box">
<p><strong>💡 解决冲突:</strong> 选择保留哪个版本或手动编辑合并</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const resolved = ref(false)
const resolve = (choice) => { resolved.value = true }
</script>
<style scoped>
.conflict-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
.editor { background: #1f2937; border-radius: 8px; padding: 1rem; font-family: monospace; margin-bottom: 1rem; }
.line { display: flex; gap: 0.5rem; line-height: 1.6; }
.ln { color: #6b7280; min-width: 2rem; }
.line.normal { color: #d1d5db; }
.line.conflict { color: #f59e0b; }
.line.current { color: #60a5fa; background: rgba(96,165,250,0.1); }
.line.incoming { color: #a78bfa; background: rgba(167,139,250,0.1); }
.actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.action-btn { padding: 0.625rem 1.25rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
.action-btn:hover { background: var(--vp-c-brand); color: var(--vp-c-bg); }
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; }
.info-box p { margin: 0; color: var(--vp-c-text-1); }
</style>
@@ -0,0 +1,81 @@
<template>
<div class="remote-demo">
<div class="panel">
<div class="repos">
<div class="repo">
<div class="header">💻 本地</div>
<div class="commits">
<div v-for="c in local" :key="c" class="commit-dot">
<span class="dot local"></span>
<span class="hash">{{ c.substring(0,6) }}</span>
</div>
<div v-if="local.length === 0" class="empty"></div>
</div>
</div>
<div class="sync"></div>
<div class="repo">
<div class="header"> 远程</div>
<div class="commits">
<div v-for="c in remote" :key="c" class="commit-dot">
<span class="dot remote"></span>
<span class="hash">{{ c.substring(0,6) }}</span>
</div>
<div v-if="remote.length === 0" class="empty"></div>
</div>
</div>
</div>
<div class="controls">
<button @click="localCommit" class="btn">本地提交</button>
<button @click="push" :disabled="local.length <= remote.length" class="btn">推送 Push</button>
<button @click="pull" :disabled="!hasRemote" class="btn">拉取 Pull</button>
<button @click="reset" class="btn secondary">重置</button>
</div>
</div>
<div class="info-box">
<p><strong>💡 远程协作:</strong> Push 上传Pull 下载保持同步</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const local = ref([])
const remote = ref([])
const hasRemote = ref(false)
const localCommit = () => { local.value.push(Math.random().toString(16).substr(2,7)) }
const push = () => { remote.value.push(...local.value.slice(remote.value.length)); hasRemote.value = false }
const pull = () => { if(hasRemote.value) local.value.push(Math.random().toString(16).substr(2,7)); hasRemote.value = false }
const reset = () => { local.value = []; remote.value = []; hasRemote.value = false }
</script>
<style scoped>
.remote-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
.repos { display: grid; grid-template-columns: 1fr auto 1fr; gap: 1rem; align-items: stretch; margin-bottom: 1rem; }
.repo { border: 1px solid var(--vp-c-divider); border-radius: 8px; padding: 1rem; background: var(--vp-c-bg); }
.header { font-weight: 600; margin-bottom: 0.5rem; }
.commits { min-height: 80px; }
.commit-dot { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.dot { width: 10px; height: 10px; border-radius: 50%; }
.dot.local { background: #3b82f6; }
.dot.remote { background: #10b981; }
.hash { font-family: monospace; font-size: 0.875rem; color: var(--vp-c-text-2); }
.sync { font-size: 2rem; text-align: center; }
.empty { color: var(--vp-c-text-3); text-align: center; padding: 1rem; }
.controls { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.btn { padding: 0.5rem 1rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
.btn:hover:not(:disabled) { background: var(--vp-c-brand); color: var(--vp-c-bg); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.secondary { border-color: var(--vp-c-divider); }
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; margin-top: 1rem; }
.info-box p { margin: 0; color: var(--vp-c-text-1); }
@media (max-width: 768px) {
.repos { grid-template-columns: 1fr; }
.sync { transform: rotate(90deg); }
}
</style>
@@ -0,0 +1,60 @@
<template>
<div class="stash-demo">
<div class="panel">
<div class="areas">
<div class="area">
<div class="header">💻 工作区 ({{ work.length }})</div>
<div v-for="f in work" :key="f" class="file">📄 {{ f }}</div>
<div v-if="work.length === 0" class="empty"></div>
</div>
<div class="area">
<div class="header">📚 Stash ({{ stash.length }})</div>
<div v-for="(s,i) in stash" :key="i" class="stash-item">
<span class="num">{{ i+1 }}</span>
<span class="msg">{{ s }}</span>
</div>
<div v-if="stash.length === 0" class="empty"></div>
</div>
</div>
<div class="controls">
<button @click="doWork" :disabled="work.length > 0" class="btn">修改</button>
<button @click="save" :disabled="work.length === 0 || stash.length >= 3" class="btn">保存</button>
<button @click="pop" :disabled="stash.length === 0" class="btn">恢复</button>
<button @click="reset" class="btn secondary">重置</button>
</div>
</div>
<div class="info-box">
<p><strong>💡 Stash 用途:</strong> 临时保存工作现场切换任务</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const work = ref([])
const stash = ref([])
const doWork = () => { work.value = ['file.js', 'style.css'] }
const save = () => { stash.value.push('WIP'); work.value = [] }
const pop = () => { if(stash.value.length) { stash.value.pop(); work.value = ['file.js'] } }
const reset = () => { work.value = []; stash.value = [] }
</script>
<style scoped>
.stash-demo { border: 1px solid var(--vp-c-divider); border-radius: 8px; background-color: var(--vp-c-bg-soft); padding: 1.5rem; margin: 1rem 0; }
.areas { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
.area { border: 1px solid var(--vp-c-divider); border-radius: 8px; padding: 1rem; background: var(--vp-c-bg); }
.header { font-weight: 600; margin-bottom: 0.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--vp-c-divider); }
.file, .stash-item { padding: 0.5rem; background: var(--vp-c-bg-soft); margin-bottom: 0.25rem; border-radius: 4px; font-size: 0.875rem; display: flex; gap: 0.5rem; align-items: center; }
.stash-item .num { width: 20px; height: 20px; background: var(--vp-c-brand); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; }
.empty { color: var(--vp-c-text-3); text-align: center; font-style: italic; padding: 1rem; }
.controls { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.btn { padding: 0.5rem 1rem; border: 1px solid var(--vp-c-brand); background: var(--vp-c-bg); color: var(--vp-c-brand); border-radius: 6px; cursor: pointer; }
.btn:hover:not(:disabled) { background: var(--vp-c-brand); color: var(--vp-c-bg); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.secondary { border-color: var(--vp-c-divider); }
.info-box { padding: 1rem; background: var(--vp-c-bg); border-left: 4px solid var(--vp-c-brand); border-radius: 4px; margin-top: 1rem; }
.info-box p { margin: 0; color: var(--vp-c-text-1); }
</style>
@@ -0,0 +1,137 @@
<template>
<div class="storage-demo">
<div class="panel">
<div class="comparison">
<div class="mode-selector">
<button @click="mode = 'full'" :class="{active: mode === 'full'}" class="mode-btn">完整备份</button>
<button @click="mode = 'git'" :class="{active: mode === 'git'}" class="mode-btn">Git 增量</button>
</div>
<div class="visualization">
<div class="bar-container">
<div class="bar full" :style="{height: fullSize + '%'}">
<span class="label">完整备份: {{ fullSize }}MB</span>
</div>
<div class="bar git" :style="{height: gitSize + '%'}">
<span class="label">Git 存储: {{ gitSize }}MB</span>
</div>
</div>
</div>
<div class="stats">
<div class="stat-item">
<span class="value">{{ savedPercent }}%</span>
<span class="label">节省空间</span>
</div>
<div class="stat-item">
<span class="value">{{ versionCount }}</span>
<span class="label">版本数</span>
</div>
</div>
</div>
</div>
<div class="info-box">
<p><strong>💡 Git 增量存储:</strong> 只保存变更部分大幅节省空间</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const mode = ref('git')
const versionCount = ref(5)
const fullSize = ref(500)
const gitSize = ref(50)
const savedPercent = computed(() => Math.round((1 - gitSize.value / fullSize.value) * 100))
</script>
<style scoped>
.storage-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
padding: 1.5rem;
margin: 1rem 0;
}
.mode-selector {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.mode-btn {
padding: 0.5rem 1rem;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
border-radius: 6px;
cursor: pointer;
}
.mode-btn.active {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
}
.bar-container {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 1.5rem 0;
}
.bar {
height: 60px;
border-radius: 6px;
display: flex;
align-items: center;
padding: 0 1rem;
color: white;
font-weight: 600;
transition: height 0.5s ease;
}
.bar.full { background: linear-gradient(135deg, #ef4444, #dc2626); }
.bar.git { background: linear-gradient(135deg, #10b981, #059669); }
.stats {
display: flex;
gap: 2rem;
margin-top: 1.5rem;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-item .value {
font-size: 1.5rem;
font-weight: 700;
color: var(--vp-c-brand);
}
.stat-item .label {
font-size: 0.875rem;
color: var(--vp-c-text-2);
}
.info-box {
padding: 1rem;
background: var(--vp-c-bg);
border-left: 4px solid var(--vp-c-brand);
border-radius: 4px;
margin-top: 1rem;
}
.info-box p {
margin: 0;
color: var(--vp-c-text-1);
line-height: 1.6;
}
</style>
@@ -0,0 +1,204 @@
<template>
<div class="three-areas-demo">
<div class="panel">
<div class="areas-container">
<div class="area working">
<div class="area-header">
<span class="area-icon">💻</span>
<span class="area-name">工作区</span>
<span class="area-count">{{ workingFiles.length }}</span>
</div>
<div class="file-list">
<div v-for="file in workingFiles" :key="file.name" class="file-item">
<span class="file-icon">📄</span>
<span class="file-name">{{ file.name }}</span>
<button @click="addToStaging(file)" class="mini-btn">+</button>
</div>
<div v-if="workingFiles.length === 0" class="empty">无文件</div>
</div>
</div>
<div class="area staging">
<div class="area-header">
<span class="area-icon">📋</span>
<span class="area-name">暂存区</span>
<span class="area-count">{{ stagedFiles.length }}</span>
</div>
<div class="file-list">
<div v-for="file in stagedFiles" :key="file.name" class="file-item">
<span class="file-icon">📄</span>
<span class="file-name">{{ file.name }}</span>
<button @click="commitFile(file)" class="mini-btn"></button>
</div>
<div v-if="stagedFiles.length === 0" class="empty">无文件</div>
</div>
</div>
<div class="area repo">
<div class="area-header">
<span class="area-icon">📦</span>
<span class="area-name">仓库</span>
<span class="area-count">{{ commits.length }}</span>
</div>
<div class="commit-list">
<div v-for="commit in commits.slice(-3).reverse()" :key="commit.hash" class="commit-item">
<span class="commit-hash">{{ commit.hash.substring(0, 6) }}</span>
<span class="commit-msg">{{ commit.message }}</span>
</div>
<div v-if="commits.length === 0" class="empty">无提交</div>
</div>
</div>
</div>
</div>
<div class="info-box">
<p><strong>💡 三区工作流:</strong> 工作区修改 添加到暂存区 提交到仓库</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const workingFiles = ref([
{ name: 'index.vue' },
{ name: 'style.css' }
])
const stagedFiles = ref([])
const commits = ref([])
const addToStaging = (file) => {
const index = workingFiles.value.findIndex(f => f.name === file.name)
if (index !== -1) {
workingFiles.value.splice(index, 1)
stagedFiles.value.push(file)
}
}
const commitFile = (file) => {
const index = stagedFiles.value.findIndex(f => f.name === file.name)
if (index !== -1) {
stagedFiles.value.splice(index, 1)
commits.value.push({
hash: Math.random().toString(16).substr(2, 7),
message: file.name
})
}
}
</script>
<style scoped>
.three-areas-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
padding: 1.5rem;
margin: 1rem 0;
}
.areas-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-bottom: 1rem;
}
.area {
border: 2px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg);
}
.area.working { border-color: #f59e0b; }
.area.staging { border-color: #3b82f6; }
.area.repo { border-color: #10b981; }
.area-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--vp-c-divider);
}
.area-icon { font-size: 1.25rem; }
.area-name { flex: 1; font-weight: 600; }
.area-count {
font-size: 0.75rem;
padding: 0.125rem 0.5rem;
background: var(--vp-c-bg-soft);
border-radius: 4px;
}
.file-list, .commit-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
min-height: 80px;
}
.file-item, .commit-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--vp-c-bg-soft);
border-radius: 4px;
}
.file-icon { font-size: 1rem; }
.file-name { flex: 1; font-size: 0.875rem; }
.mini-btn {
padding: 0.125rem 0.5rem;
border: 1px solid var(--vp-c-brand);
background: var(--vp-c-bg);
color: var(--vp-c-brand);
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
}
.mini-btn:hover { background: var(--vp-c-brand); color: var(--vp-c-bg); }
.commit-hash {
font-family: monospace;
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.commit-msg {
flex: 1;
font-size: 0.875rem;
}
.empty {
text-align: center;
color: var(--vp-c-text-3);
font-style: italic;
font-size: 0.875rem;
padding: 1rem;
}
.info-box {
padding: 1rem;
background: var(--vp-c-bg);
border-left: 4px solid var(--vp-c-brand);
border-radius: 4px;
}
.info-box p {
margin: 0;
color: var(--vp-c-text-1);
line-height: 1.6;
}
@media (max-width: 768px) {
.areas-container {
grid-template-columns: 1fr;
}
}
</style>
@@ -0,0 +1,222 @@
<!--
GitWorkflowDemo.vue
Git 工作流演示 - 简洁版
用途展示 Git 的基本工作流程
交互初始化提交创建分支合并
-->
<template>
<div class="git-workflow-demo">
<!-- 控制面板 -->
<div class="control-panel">
<button @click="initRepo" :disabled="inited" class="action-btn">
🎯 初始化仓库
</button>
<button @click="makeCommit" :disabled="!inited" class="action-btn">
提交
</button>
<button @click="createBranch" :disabled="!inited || hasBranch" class="action-btn">
🌿 创建分支
</button>
<button @click="mergeBranch" :disabled="!hasBranch || merging" class="action-btn">
🔀 合并分支
</button>
<button @click="reset" class="action-btn secondary">
🔄 重置
</button>
</div>
<!-- 提交历史可视化 -->
<div class="visualization">
<div class="graph-container">
<svg viewBox="0 0 400 150" class="git-graph">
<!-- 主分支线 -->
<line x1="50" y1="60" x2="350" y2="60" stroke="#3b82f6" stroke-width="3" />
<!-- 分支线 -->
<line v-if="hasBranch" x1="150" y1="60" x2="150" y2="100" stroke="#10b981" stroke-width="3" />
<line v-if="hasBranch" x1="150" y1="100" x2="300" y2="100" stroke="#10b981" stroke-width="3" />
<!-- 合并线 -->
<path v-if="merging" d="M 300 100 Q 320 80, 320 60" fill="none" stroke="#f59e0b" stroke-width="2" stroke-dasharray="5,5" />
<!-- 提交节点 -->
<circle v-for="(commit, i) in mainCommits" :key="'main-'+i" :cx="80 + i * 60" cy="60" r="10" fill="#3b82f6" />
<circle v-for="(commit, i) in branchCommits" :key="'branch-'+i" :cx="200 + i * 60" cy="100" r="10" fill="#10b981" />
</svg>
</div>
</div>
<!-- 状态信息 -->
<div class="status-panel">
<div class="status-item">
<span class="label">提交数:</span>
<span class="value">{{ mainCommits.length }}</span>
</div>
<div class="status-item">
<span class="label">分支:</span>
<span class="value">{{ hasBranch ? '2' : '1' }}</span>
</div>
<div class="status-item">
<span class="label">状态:</span>
<span class="value">{{ status }}</span>
</div>
</div>
<!-- 说明 -->
<div class="info-box">
<p><strong>💡 工作流程:</strong> 初始化 提交 创建分支 开发 合并</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const inited = ref(false)
const hasBranch = ref(false)
const merging = ref(false)
const mainCommits = ref([])
const branchCommits = ref([])
const status = computed(() => {
if (merging) return '合并中...'
if (hasBranch) return '分支已创建'
if (inited) return '已初始化'
return '未初始化'
})
const initRepo = () => {
inited.value = true
mainCommits.value = [{ hash: 'abc123' }]
}
const makeCommit = () => {
if (inited.value) {
mainCommits.value.push({ hash: Math.random().toString(16).substr(2, 6) })
}
}
const createBranch = () => {
if (inited.value && !hasBranch.value) {
hasBranch.value = true
branchCommits.value = [{ hash: 'def456' }]
}
}
const mergeBranch = () => {
if (hasBranch.value) {
merging.value = true
setTimeout(() => {
mainCommits.value.push({ hash: Math.random().toString(16).substr(2, 6) })
hasBranch.value = false
branchCommits.value = []
merging.value = false
}, 1000)
}
}
const reset = () => {
inited.value = false
hasBranch.value = false
merging.value = false
mainCommits.value = []
branchCommits.value = []
}
</script>
<style scoped>
.git-workflow-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background-color: var(--vp-c-bg-soft);
padding: 1.5rem;
margin: 1rem 0;
}
.control-panel {
display: flex;
gap: 0.75rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.action-btn {
padding: 0.625rem 1.25rem;
border: 2px solid var(--vp-c-brand);
background: var(--vp-c-bg);
color: var(--vp-c-brand);
border-radius: 6px;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover:not(:disabled) {
background: var(--vp-c-brand);
color: var(--vp-c-bg);
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
border-color: var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.action-btn.secondary {
border-color: var(--vp-c-divider);
}
.visualization {
margin: 1.5rem 0;
}
.graph-container {
background: var(--vp-c-bg);
border-radius: 8px;
padding: 1rem;
border: 1px solid var(--vp-c-divider);
}
.git-graph {
width: 100%;
height: auto;
}
.status-panel {
display: flex;
gap: 2rem;
margin: 1.5rem 0;
flex-wrap: wrap;
}
.status-item {
display: flex;
gap: 0.5rem;
}
.status-item .label {
color: var(--vp-c-text-2);
}
.status-item .value {
font-weight: 600;
color: var(--vp-c-brand);
}
.info-box {
padding: 1rem;
background: var(--vp-c-bg);
border-left: 4px solid var(--vp-c-brand);
border-radius: 4px;
margin-top: 1rem;
}
.info-box p {
margin: 0;
color: var(--vp-c-text-1);
line-height: 1.6;
}
</style>