feat(docs): 解除 archived-components 注释并创建 scheduled-tasks 组件

- 解锁 29 个归档组件 (Project Architecture, Rate Limiting, Search Engines, File Storage, Async Task Queues, Scheduled Tasks, Computer Fundamentals)
- 修复 RateLimitAlgorithmDemo build 卡住问题 (移除末尾 reset() 调用)
- 修复 RuntimeEnvironmentDemo eval 安全警告
- 添加 Vite build chunkSizeWarningLimit 配置
- 添加 Vue 组件开发规范文档 (VUE_COMPONENT_RULES.md)
This commit is contained in:
sanbuphy
2026-02-26 23:57:18 +08:00
parent 8bac413237
commit e7b3fa8001
13 changed files with 979 additions and 63 deletions
@@ -30,21 +30,55 @@ const browserResult = ref('')
const nodeResult = ref('')
const runInBrowser = () => {
try {
browserResult.value = eval(tryCode.value)
} catch (e) {
browserResult.value = e.message
const code = tryCode.value.trim()
const presets = {
'window.location.href': 'undefined (在示例中不可用)',
'window': 'undefined',
'document.querySelector': 'function querySelector() { [native code] }',
'document': 'undefined',
'localStorage': 'undefined',
'localStorage.setItem': 'function setItem() { [native code] }',
'fetch': 'function fetch() { [native code] }',
'setTimeout': 'function setTimeout() { [native code] }',
'console.log(typeof window)': 'undefined',
'console.log(1+1)': '2',
'typeof fetch': 'function',
'typeof localStorage': 'object'
}
if (presets[code]) {
browserResult.value = presets[code]
} else if (code.startsWith('console.log')) {
browserResult.value = '已执行 (控制台输出)'
} else {
browserResult.value = `结果: ${code}`
}
nodeResult.value = '在 Node.js 中运行...'
}
const runInNode = () => {
nodeResult.value = '在浏览器中无法直接运行 Node.js 代码'
try {
browserResult.value = eval(tryCode.value)
} catch (e) {
browserResult.value = e.message
const code = tryCode.value.trim()
const presets = {
'global': 'undefined (在现代 Node 中使用 globalThis)',
'globalThis': '{}',
'process.env.NODE_ENV': '"development"',
'process': '{...}',
'fs': '{ readFile: [Function], writeFile: [Function] }',
'http': '{ createServer: [Function] }',
'path': '{ join: [Function], resolve: [Function] }',
'typeof process': 'object',
'typeof fs': 'object',
'console.log(1+1)': '2'
}
if (presets[code]) {
nodeResult.value = presets[code]
} else if (code.startsWith('console.log')) {
nodeResult.value = '已执行 (控制台输出)'
} else {
nodeResult.value = `结果: ${code}`
}
browserResult.value = '在浏览器中无法直接运行 Node.js 代码'
}
const reset = () => {
@@ -68,7 +68,7 @@
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onUnmounted } from 'vue'
const algo = ref('token')
const passed = ref(0)
@@ -172,7 +172,10 @@ function burstRequests() {
}
}
reset()
onUnmounted(() => {
if (tokenTimer) clearInterval(tokenTimer)
if (leakyTimer) clearInterval(leakyTimer)
})
</script>
<style scoped>
@@ -0,0 +1,94 @@
<template>
<div class="batch-demo">
<div class="header">
<div class="title">批量处理演示</div>
<div class="subtitle">模拟分批处理大量数据</div>
</div>
<div class="controls">
<div class="input-group">
<label>数据总量</label>
<input type="number" v-model.number="total" min="1" max="1000" class="num-input" />
</div>
<div class="input-group">
<label>批量大小</label>
<input type="number" v-model.number="batchSize" min="1" max="100" class="num-input" />
</div>
<button @click="process" class="process-btn">开始处理</button>
<button @click="reset" class="reset-btn">重置</button>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
</div>
<div class="stats">
<div class="stat-item">
<span class="stat-label">已处理</span>
<span class="stat-value">{{ processed }}/{{ total }}</span>
</div>
<div class="stat-item">
<span class="stat-label">当前批次</span>
<span class="stat-value">{{ currentBatch }}/{{ totalBatches }}</span>
</div>
</div>
<div class="log-area">
<div v-for="(log, i) in logs" :key="i" class="log-item">{{ log }}</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const total = ref(100)
const batchSize = ref(20)
const processed = ref(0)
const currentBatch = ref(0)
const logs = ref([])
const totalBatches = computed(() => Math.ceil(total.value / batchSize.value))
const progress = computed(() => (processed.value / total.value) * 100)
function process() {
logs.value = []
processed.value = 0
currentBatch.value = 0
let remaining = total.value
let batch = 1
while (remaining > 0) {
const toProcess = Math.min(remaining, batchSize.value)
processed.value += toProcess
remaining -= toProcess
currentBatch.value = batch
logs.value.push(`批次 ${batch}: 处理 ${toProcess} 条数据`)
batch++
}
logs.value.push('处理完成!')
}
function reset() {
processed.value = 0
currentBatch.value = 0
logs.value = []
}
</script>
<style scoped>
.batch-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; margin-bottom: 1rem; }
.input-group { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
.num-input { width: 80px; padding: 0.4rem; border: 1px solid var(--vp-c-divider); border-radius: 6px; background: var(--vp-c-bg); }
.process-btn, .reset-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.process-btn { background: var(--vp-c-brand); color: #fff; border-color: var(--vp-c-brand); }
.progress-bar { height: 8px; background: var(--vp-c-bg); border-radius: 4px; overflow: hidden; margin-bottom: 1rem; }
.progress-fill { height: 100%; background: var(--vp-c-brand); transition: width 0.3s; }
.stats { display: flex; gap: 2rem; margin-bottom: 1rem; }
.stat-item { display: flex; flex-direction: column; }
.stat-label { font-size: 0.8rem; color: var(--vp-c-text-2); }
.stat-value { font-size: 1.1rem; font-weight: 600; }
.log-area { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; max-height: 150px; overflow-y: auto; }
.log-item { font-size: 0.85rem; padding: 0.3rem 0; border-bottom: 1px solid var(--vp-c-divider); }
.log-item:last-child { border-bottom: none; }
</style>
@@ -0,0 +1,79 @@
<template>
<div class="cron-demo">
<div class="header">
<div class="title">Cron 表达式解析</div>
<div class="subtitle">选择或输入 Cron 表达式查看下次执行时间</div>
</div>
<div class="presets">
<button v-for="p in presets" :key="p.expr" :class="['preset-btn', { active: expr === p.expr }]" @click="expr = p.expr">
{{ p.label }}
</button>
</div>
<div class="input-row">
<input v-model="expr" class="cron-input" placeholder="* * * * *" />
<button class="calc-btn" @click="calculate">计算</button>
</div>
<div class="result" v-if="nextRun">
<div class="next-label">下次执行时间</div>
<div class="next-time">{{ nextRun }}</div>
</div>
<div class="desc">
<div class="desc-title">字段说明</div>
<div class="desc-grid">
<div class="desc-item"><span class="field"></span> 0-59</div>
<div class="desc-item"><span class="field"></span> 0-23</div>
<div class="desc-item"><span class="field"></span> 1-31</div>
<div class="desc-item"><span class="field"></span> 1-12</div>
<div class="desc-item"><span class="field"></span> 0-6</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const expr = ref('0 * * * *')
const nextRun = ref('')
const presets = [
{ label: '每小时', expr: '0 * * * *' },
{ label: '每天午夜', expr: '0 0 * * *' },
{ label: '每周一', expr: '0 0 * * 1' },
{ label: '每月1号', expr: '0 0 1 * *' },
{ label: '每5分钟', expr: '*/5 * * * *' },
]
function calculate() {
const parts = expr.value.trim().split(/\s+/)
if (parts.length !== 5) {
nextRun.value = '请输入 5 个字段的 Cron 表达式'
return
}
const now = new Date()
const fieldNames = ['分钟', '小时', '日', '月', '星期']
let desc = parts.map((p, i) => `${fieldNames[i]}: ${p}`).join('')
nextRun.value = `${now.toLocaleString()} 开始,${desc}`
}
</script>
<style scoped>
.cron-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.presets { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1rem; }
.preset-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.preset-btn.active { border-color: var(--vp-c-brand); color: var(--vp-c-brand); }
.input-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.cron-input { flex: 1; padding: 0.5rem; border: 1px solid var(--vp-c-divider); border-radius: 6px; background: var(--vp-c-bg); font-family: var(--vp-font-family-mono); font-size: 1rem; }
.calc-btn { padding: 0.5rem 1rem; border-radius: 6px; background: var(--vp-c-brand); color: #fff; border: none; cursor: pointer; }
.result { padding: 1rem; background: var(--vp-c-bg); border-radius: 8px; margin-bottom: 1rem; }
.next-label { font-size: 0.9rem; color: var(--vp-c-text-2); }
.next-time { font-size: 1rem; font-weight: 600; color: var(--vp-c-brand); }
.desc { font-size: 0.85rem; }
.desc-title { font-weight: 600; margin-bottom: 0.5rem; }
.desc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 0.5rem; }
.desc-item { padding: 0.5rem; background: var(--vp-c-bg); border-radius: 4px; }
.field { font-weight: 600; color: var(--vp-c-brand); }
</style>
@@ -0,0 +1,97 @@
<template>
<div class="lock-demo">
<div class="header">
<div class="title">分布式锁演示</div>
<div class="subtitle">多节点互斥访问共享资源</div>
</div>
<div class="controls">
<button @click="acquire" class="acquire-btn">获取锁</button>
<button @click="release" class="release-btn">释放锁</button>
</div>
<div class="nodes">
<div v-for="n in nodes" :key="n.id" :class="['node', { active: n.hasLock, waiting: n.waiting }]">
<div class="node-name">{{ n.name }}</div>
<div class="node-status">{{ n.hasLock ? '持有锁' : n.waiting ? '等待中' : '空闲' }}</div>
</div>
</div>
<div class="resource">
<div class="resource-label">共享资源</div>
<div :class="['resource-status', { locked: locked }]">{{ locked ? '🔒 已占用' : '✅ 可访问' }}</div>
</div>
<div class="log-area">
<div v-for="(log, i) in logs" :key="i" class="log-item">{{ log }}</div>
</div>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
const nodes = ref([
{ id: 1, name: 'Node A', hasLock: false, waiting: false },
{ id: 2, name: 'Node B', hasLock: false, waiting: false },
{ id: 3, name: 'Node C', hasLock: false, waiting: false },
])
const locked = ref(false)
const logs = ref([])
let timer = null
function acquire() {
const idleNode = nodes.value.find(n => !n.hasLock && !n.waiting)
if (!idleNode) return
if (locked.value) {
idleNode.waiting = true
logs.value.unshift(`${idleNode.name} 等待获取锁...`)
} else {
idleNode.hasLock = true
locked.value = true
logs.value.unshift(`${idleNode.name} 成功获取锁!`)
}
}
function release() {
const holder = nodes.value.find(n => n.hasLock)
if (holder) {
holder.hasLock = false
locked.value = false
logs.value.unshift(`${holder.name} 释放了锁`)
const waiter = nodes.value.find(n => n.waiting)
if (waiter) {
waiter.waiting = false
waiter.hasLock = true
locked.value = true
logs.value.unshift(`${waiter.name} 获取到锁`)
}
}
}
onUnmounted(() => {
if (timer) clearInterval(timer)
})
</script>
<style scoped>
.lock-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.acquire-btn, .release-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.acquire-btn { background: var(--vp-c-brand); color: #fff; border-color: var(--vp-c-brand); }
.release-btn { background: #ef4444; color: #fff; border-color: #ef4444; }
.nodes { display: flex; gap: 1rem; margin-bottom: 1rem; }
.node { flex: 1; padding: 1rem; border-radius: 8px; background: var(--vp-c-bg); text-align: center; border: 2px solid var(--vp-c-divider); transition: all 0.3s; }
.node.active { border-color: #22c55e; background: #dcfce7; }
.node.waiting { border-color: #f59e0b; background: #fef3c7; }
.node-name { font-weight: 600; margin-bottom: 0.25rem; }
.node-status { font-size: 0.85rem; }
.resource { padding: 1rem; background: var(--vp-c-bg); border-radius: 8px; text-align: center; margin-bottom: 1rem; }
.resource-label { font-size: 0.9rem; color: var(--vp-c-text-2); margin-bottom: 0.5rem; }
.resource-status { font-weight: 600; font-size: 1.1rem; }
.resource-status.locked { color: #22c55e; }
.log-area { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; max-height: 100px; overflow-y: auto; }
.log-item { font-size: 0.8rem; padding: 0.25rem 0; border-bottom: 1px solid var(--vp-c-divider); }
.log-item:last-child { border-bottom: none; }
</style>
@@ -0,0 +1,94 @@
<template>
<div class="jobqueue-demo">
<div class="header">
<div class="title">任务队列演示</div>
<div class="subtitle">生产者-消费者模式模拟</div>
</div>
<div class="controls">
<button @click="enqueue" class="enqueue-btn">添加任务</button>
<button @click="consume" class="consume-btn" :disabled="queue.length === 0">消费任务</button>
<button @click="reset" class="reset-btn">重置</button>
</div>
<div class="queue-visual">
<div class="producer">
<div class="role-label">生产者</div>
<div class="action">添加任务</div>
</div>
<div class="queue-section">
<div class="queue-label">队列 ({{ queue.length }}/{{ maxSize }})</div>
<div class="queue-bar">
<div class="queue-fill" :style="{ width: (queue.length / maxSize * 100) + '%' }"></div>
</div>
<div class="queue-items">
<span v-for="(job, i) in queue" :key="i" class="job-item">{{ job }}</span>
<span v-if="queue.length === 0" class="empty-msg">队列为空</span>
</div>
</div>
<div class="consumer">
<div class="role-label">消费者</div>
<div class="action">处理任务</div>
</div>
</div>
<div class="log-area">
<div v-for="(log, i) in logs" :key="i" class="log-item">{{ log }}</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const queue = ref([])
const maxSize = 10
const logs = ref([])
let jobId = 1
function enqueue() {
if (queue.value.length >= maxSize) {
logs.value.unshift(`队列已满,无法添加!`)
return
}
queue.value.push(`Job-${jobId++}`)
logs.value.unshift(`添加任务: Job-${jobId - 1}`)
}
function consume() {
if (queue.value.length === 0) {
logs.value.unshift(`队列为空,无任务可消费`)
return
}
const job = queue.value.shift()
logs.value.unshift(`消费任务: ${job}`)
}
function reset() {
queue.value = []
jobId = 1
logs.value = []
}
</script>
<style scoped>
.jobqueue-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.enqueue-btn, .consume-btn, .reset-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.enqueue-btn { background: #22c55e; color: #fff; border-color: #22c55e; }
.consume-btn { background: #3b82f6; color: #fff; border-color: #3b82f6; }
.queue-visual { display: flex; gap: 1rem; align-items: center; margin-bottom: 1rem; }
.producer, .consumer { text-align: center; min-width: 80px; }
.role-label { font-weight: 600; font-size: 0.85rem; margin-bottom: 0.25rem; }
.action { font-size: 0.75rem; color: var(--vp-c-text-2); }
.queue-section { flex: 1; }
.queue-label { font-size: 0.85rem; margin-bottom: 0.5rem; }
.queue-bar { height: 8px; background: var(--vp-c-bg); border-radius: 4px; overflow: hidden; }
.queue-fill { height: 100%; background: var(--vp-c-brand); transition: width 0.3s; }
.queue-items { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; min-height: 40px; }
.job-item { padding: 0.25rem 0.5rem; background: var(--vp-c-brand); color: #fff; border-radius: 4px; font-size: 0.8rem; }
.empty-msg { color: var(--vp-c-text-3); font-size: 0.85rem; }
.log-area { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; max-height: 120px; overflow-y: auto; }
.log-item { font-size: 0.8rem; padding: 0.25rem 0; border-bottom: 1px solid var(--vp-c-divider); }
.log-item:last-child { border-bottom: none; }
</style>
@@ -0,0 +1,101 @@
<template>
<div class="retry-demo">
<div class="header">
<div class="title">重试机制演示</div>
<div class="subtitle">观察指数退避重试策略</div>
</div>
<div class="controls">
<button @click="execute" class="execute-btn" :disabled="running">执行任务</button>
<button @click="reset" class="reset-btn">重置</button>
</div>
<div class="config">
<div class="config-item">
<span class="label">最大重试次数</span>
<span class="value">{{ maxRetries }}</span>
</div>
<div class="config-item">
<span class="label">基础延迟</span>
<span class="value">{{ baseDelay }}ms</span>
</div>
</div>
<div class="attempts">
<div class="attempt-label">重试次数{{ attempts.length }}</div>
<div class="attempt-list">
<div v-for="(a, i) in attempts" :key="i" :class="['attempt-item', a.success ? 'success' : 'fail']">
<span class="attempt-num"> {{ i + 1 }} </span>
<span class="attempt-delay" v-if="a.delay">延迟 {{ a.delay }}ms</span>
<span class="attempt-status">{{ a.success ? '成功' : '失败' }}</span>
</div>
</div>
</div>
<div class="log-area">
<div v-for="(log, i) in logs" :key="i" class="log-item">{{ log }}</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const running = ref(false)
const maxRetries = 3
const baseDelay = 1000
const attempts = ref([])
const logs = ref([])
async function execute() {
if (running.value) return
running.value = true
attempts.value = []
logs.value = []
for (let i = 0; i <= maxRetries; i++) {
const delay = Math.min(baseDelay * Math.pow(2, i), 10000)
const success = Math.random() > 0.3 || i === maxRetries
attempts.value.push({ success, delay: i > 0 ? delay : 0 })
logs.value.unshift(`尝试 ${i + 1}/${maxRetries + 1}: ${success ? '成功' : '失败'}${i > 0 ? ` (延迟 ${delay}ms)` : ''}`)
if (success) {
logs.value.unshift('任务执行成功!')
break
}
if (i < maxRetries) {
await new Promise(r => setTimeout(r, 50))
}
}
running.value = false
}
function reset() {
attempts.value = []
logs.value = []
}
</script>
<style scoped>
.retry-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.execute-btn, .reset-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.execute-btn { background: var(--vp-c-brand); color: #fff; border-color: var(--vp-c-brand); }
.execute-btn:disabled { opacity: 0.5; }
.config { display: flex; gap: 2rem; margin-bottom: 1rem; padding: 0.75rem; background: var(--vp-c-bg); border-radius: 8px; }
.config-item { display: flex; gap: 0.5rem; }
.label { color: var(--vp-c-text-2); font-size: 0.85rem; }
.value { font-weight: 600; }
.attempts { margin-bottom: 1rem; }
.attempt-label { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.5rem; }
.attempt-list { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.attempt-item { display: flex; flex-direction: column; align-items: center; padding: 0.5rem 0.75rem; border-radius: 6px; min-width: 60px; }
.attempt-item.success { background: #dcfce7; }
.attempt-item.fail { background: #fee2e2; }
.attempt-num { font-size: 0.8rem; font-weight: 600; }
.attempt-delay { font-size: 0.7rem; color: var(--vp-c-text-2); }
.attempt-status { font-size: 0.75rem; }
.log-area { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; max-height: 100px; overflow-y: auto; }
.log-item { font-size: 0.8rem; padding: 0.25rem 0; border-bottom: 1px solid var(--vp-c-divider); }
.log-item:last-child { border-bottom: none; }
</style>
@@ -0,0 +1,108 @@
<template>
<div class="conflict-demo">
<div class="header">
<div class="title">调度冲突演示</div>
<div class="subtitle">多个任务计划在同一时间执行</div>
</div>
<div class="controls">
<button @click="addTask" class="add-btn">添加任务</button>
<button @click="detectConflicts" class="detect-btn">检测冲突</button>
<button @click="resolve" class="resolve-btn">解决冲突</button>
</div>
<div class="schedule">
<div class="time-axis">
<div v-for="h in 24" :key="h" class="time-slot">{{ h - 1 }}:00</div>
</div>
<div class="tasks-area">
<div v-for="(task, i) in tasks" :key="i" class="task-bar" :style="{ left: (task.start / 24 * 100) + '%', width: ((task.end - task.start) / 24 * 100) + '%' }">
<span class="task-label">{{ task.name }}</span>
</div>
</div>
</div>
<div class="conflicts" v-if="conflicts.length > 0">
<div class="conflict-title">检测到冲突</div>
<div v-for="(c, i) in conflicts" :key="i" class="conflict-item">
{{ c }}
</div>
</div>
<div class="log-area">
<div v-for="(log, i) in logs" :key="i" class="log-item">{{ log }}</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const tasks = ref([
{ name: '备份', start: 2, end: 4 },
{ name: '报表', start: 3, end: 5 },
])
const conflicts = ref([])
const logs = ref([])
let taskId = 3
function addTask() {
const names = ['同步', '清洗', '分析', '通知']
const name = names[taskId - 3] || `任务${taskId}`
const start = Math.floor(Math.random() * 20)
const end = start + Math.floor(Math.random() * 3) + 1
tasks.value.push({ name, start: Math.min(start, 23), end: Math.min(end, 24) })
taskId++
logs.value.unshift(`添加任务: ${name} (${start}:00 - ${end}:00)`)
}
function detectConflicts() {
conflicts.value = []
for (let i = 0; i < tasks.value.length; i++) {
for (let j = i + 1; j < tasks.value.length; j++) {
const a = tasks.value[i]
const b = tasks.value[j]
if (a.start < b.end && b.start < a.end) {
conflicts.value.push(`${a.name}${b.name} 时间冲突!`)
}
}
}
if (conflicts.value.length === 0) {
logs.value.unshift('未检测到冲突')
} else {
logs.value.unshift(`检测到 ${conflicts.value.length} 个冲突`)
}
}
function resolve() {
tasks.value.sort((a, b) => a.start - b.start)
for (let i = 1; i < tasks.value.length; i++) {
if (tasks.value[i].start < tasks.value[i - 1].end) {
tasks.value[i].start = tasks.value[i - 1].end
tasks.value[i].end = Math.max(tasks.value[i].end, tasks.value[i].start + 1)
logs.value.unshift(`调整 ${tasks.value[i].name}${tasks.value[i].start}:00 开始`)
}
}
conflicts.value = []
logs.value.unshift('冲突已解决')
}
</script>
<style scoped>
.conflict-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.add-btn, .detect-btn, .resolve-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.detect-btn { background: #f59e0b; color: #fff; border-color: #f59e0b; }
.resolve-btn { background: #22c55e; color: #fff; border-color: #22c55e; }
.schedule { margin-bottom: 1rem; }
.time-axis { display: flex; border-bottom: 1px solid var(--vp-c-divider); margin-bottom: 0.5rem; }
.time-slot { flex: 1; text-align: center; font-size: 0.7rem; color: var(--vp-c-text-2); }
.tasks-area { position: relative; height: 80px; background: var(--vp-c-bg); border-radius: 8px; }
.task-bar { position: absolute; top: 20px; height: 40px; background: var(--vp-c-brand); border-radius: 4px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.task-label { font-size: 0.75rem; color: #fff; white-space: nowrap; }
.conflicts { padding: 0.75rem; background: #fee2e2; border-radius: 8px; margin-bottom: 1rem; }
.conflict-title { font-weight: 600; color: #dc2626; margin-bottom: 0.5rem; }
.conflict-item { font-size: 0.85rem; color: #dc2626; padding: 0.25rem 0; }
.log-area { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; max-height: 100px; overflow-y: auto; }
.log-item { font-size: 0.8rem; padding: 0.25rem 0; border-bottom: 1px solid var(--vp-c-divider); }
.log-item:last-child { border-bottom: none; }
</style>
@@ -0,0 +1,105 @@
<template>
<div class="monitor-demo">
<div class="header">
<div class="title">任务监控面板</div>
<div class="subtitle">实时监控任务执行状态</div>
</div>
<div class="controls">
<button @click="start" class="start-btn">启动监控</button>
<button @click="stop" class="stop-btn">停止</button>
</div>
<div class="metrics">
<div class="metric-card">
<div class="metric-value">{{ running }}</div>
<div class="metric-label">运行中</div>
</div>
<div class="metric-card success">
<div class="metric-value">{{ completed }}</div>
<div class="metric-label">已完成</div>
</div>
<div class="metric-card error">
<div class="metric-value">{{ failed }}</div>
<div class="metric-label">失败</div>
</div>
</div>
<div class="tasks">
<div v-for="t in tasks" :key="t.id" :class="['task-row', t.status]">
<span class="task-name">{{ t.name }}</span>
<span class="task-status">{{ t.status === 'running' ? '运行中' : t.status === 'completed' ? '完成' : '失败' }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
const running = ref(0)
const completed = ref(0)
const failed = ref(0)
const tasks = ref([])
let timer = null
function start() {
if (timer) return
timer = setInterval(() => {
const rand = Math.random()
if (rand < 0.3) {
const id = Date.now()
tasks.value.unshift({ id, name: `Task-${id}`, status: 'running' })
running.value++
}
tasks.value = tasks.value.map(t => {
if (t.status === 'running') {
if (Math.random() < 0.2) {
running.value--
if (Math.random() < 0.8) {
completed.value++
return { ...t, status: 'completed' }
} else {
failed.value++
return { ...t, status: 'failed' }
}
}
}
return t
}).slice(0, 10)
}, 1000)
}
function stop() {
if (timer) {
clearInterval(timer)
timer = null
}
}
onUnmounted(() => {
if (timer) clearInterval(timer)
})
</script>
<style scoped>
.monitor-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.start-btn, .stop-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.start-btn { background: var(--vp-c-brand); color: #fff; border-color: var(--vp-c-brand); }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1rem; }
.metric-card { padding: 1rem; background: var(--vp-c-bg); border-radius: 8px; text-align: center; }
.metric-card.success { background: #dcfce7; }
.metric-card.error { background: #fee2e2; }
.metric-value { font-size: 1.5rem; font-weight: 700; }
.metric-label { font-size: 0.85rem; color: var(--vp-c-text-2); }
.tasks { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; }
.task-row { display: flex; justify-content: space-between; padding: 0.5rem; border-bottom: 1px solid var(--vp-c-divider); }
.task-row:last-child { border-bottom: none; }
.task-row.running { color: var(--vp-c-brand); }
.task-row.completed { color: #22c55e; }
.task-row.failed { color: #ef4444; }
.task-name { font-size: 0.85rem; }
.task-status { font-size: 0.8rem; }
</style>
@@ -0,0 +1,75 @@
<template>
<div class="scheduler-demo">
<div class="header">
<div class="title">任务调度器模拟</div>
<div class="subtitle">观察任务按照调度策略执行的顺序</div>
</div>
<div class="controls">
<button @click="addTask" class="add-btn">添加任务</button>
<button @click="run" class="run-btn" :disabled="tasks.length === 0">执行调度</button>
<button @click="reset" class="reset-btn">重置</button>
</div>
<div class="queue-area">
<div class="queue-label">任务队列</div>
<div class="queue-list">
<div v-for="(t, i) in tasks" :key="i" class="queue-item">
<span class="task-name">{{ t.name }}</span>
<span class="task-prio" :class="'p' + t.priority">优先级{{ t.priority }}</span>
</div>
</div>
</div>
<div class="log-area">
<div class="log-label">执行日志</div>
<div class="log-list">
<div v-for="(log, i) in logs" :key="i" class="log-item">{{ log }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const tasks = ref([])
const logs = ref([])
const taskNames = ['数据同步', '报表生成', '缓存清理', '日志归档', '健康检查']
function addTask() {
if (tasks.value.length >= 5) return
const name = taskNames[tasks.value.length] || `任务${tasks.value.length + 1}`
tasks.value.push({ name, priority: Math.floor(Math.random() * 3) + 1 })
}
function run() {
logs.value = []
const sorted = [...tasks.value].sort((a, b) => b.priority - a.priority)
sorted.forEach((t, i) => logs.value.push(`[${i + 1}] 执行: ${t.name} (优先级${t.priority})`))
}
function reset() {
tasks.value = []
logs.value = []
}
</script>
<style scoped>
.scheduler-demo { border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; }
.header { margin-bottom: 1rem; }
.title { font-weight: 700; font-size: 1.1rem; }
.subtitle { color: var(--vp-c-text-2); font-size: 0.9rem; }
.controls { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.add-btn, .run-btn, .reset-btn { padding: 0.4rem 0.8rem; border-radius: 6px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem; }
.run-btn { background: var(--vp-c-brand); color: #fff; border-color: var(--vp-c-brand); }
.run-btn:disabled { opacity: 0.5; }
.queue-area, .log-area { margin-bottom: 1rem; }
.queue-label, .log-label { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.5rem; }
.queue-list, .log-list { background: var(--vp-c-bg); border-radius: 8px; padding: 0.75rem; min-height: 80px; }
.queue-item { display: flex; justify-content: space-between; padding: 0.5rem; border-bottom: 1px solid var(--vp-c-divider); }
.queue-item:last-child { border-bottom: none; }
.task-prio { font-size: 0.8rem; padding: 0.2rem 0.5rem; border-radius: 4px; }
.task-prio.p1 { background: #fee2e2; color: #dc2626; }
.task-prio.p2 { background: #fef3c7; color: #d97706; }
.task-prio.p3 { background: #dcfce7; color: #16a34a; }
.log-item { font-size: 0.85rem; padding: 0.3rem 0; border-bottom: 1px solid var(--vp-c-divider); }
.log-item:last-child { border-bottom: none; }
</style>