df51f84ab5
README 更新: - 移除顶部 header.png 横幅图片 - 新增「附录知识库」板块,以 3×3 网格展示 9 大知识领域精选内容 - 附录链接指向部署版网站 (datawhalechina.github.io) - 阶段表格新增「附录」行,突出 80+ 交互式专题 - 章节标题「新手入门 & PM」简化为「零基础入门」 - News 新增 2026-02-25 附录知识库更新条目 新增交互组件: - 异步任务队列 (async-task-queues) 演示组件 - 文件存储 (file-storage) 演示组件 - 项目架构 (project-architecture) 演示组件 - 限流与背压 (rate-limiting) 演示组件 - 搜索引擎 (search-engines) 演示组件 - 计算机基础: AppLaunch/BiosUefi/OSBoot 等启动流程演示组件 新增附录文档: - 前端项目架构 (frontend-project-architecture.md) - 后端项目架构 (backend-project-architecture.md) 内容优化: - 算法思维、数据结构、编程语言、调试艺术等多篇附录内容更新 - HTML/CSS 布局、请求旅程等前后端文档完善 - 附录索引页 (index.md) 同步更新
191 lines
6.7 KiB
Vue
191 lines
6.7 KiB
Vue
<!--
|
||
TaskWorkerDemo.vue
|
||
Worker 工作池演示:展示任务分发和消费过程
|
||
-->
|
||
<template>
|
||
<div class="worker-demo">
|
||
<div class="header">
|
||
<div class="title">Worker 工作池模型</div>
|
||
<div class="subtitle">观察任务如何被分发到不同 Worker 处理</div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button class="ctrl-btn" @click="addTask" :disabled="running">添加任务</button>
|
||
<button class="ctrl-btn primary" @click="startProcessing" :disabled="running || queue.length === 0">开始处理</button>
|
||
<button class="ctrl-btn" @click="resetAll">重置</button>
|
||
<div class="worker-count">
|
||
Worker 数量:
|
||
<button class="small-btn" @click="workerCount = Math.max(1, workerCount - 1)" :disabled="running">-</button>
|
||
<span>{{ workerCount }}</span>
|
||
<button class="small-btn" @click="workerCount = Math.min(5, workerCount + 1)" :disabled="running">+</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pool-layout">
|
||
<div class="queue-section">
|
||
<div class="section-title">任务队列 ({{ queue.length }})</div>
|
||
<div class="queue-list">
|
||
<div v-for="task in queue" :key="task.id" class="queue-item">
|
||
{{ task.name }}
|
||
</div>
|
||
<div v-if="queue.length === 0" class="empty">队列为空</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="arrow-section">→</div>
|
||
|
||
<div class="workers-section">
|
||
<div class="section-title">Workers</div>
|
||
<div class="workers-grid">
|
||
<div v-for="w in workers" :key="w.id" :class="['worker-card', w.status]">
|
||
<div class="worker-name">Worker {{ w.id }}</div>
|
||
<div class="worker-status">
|
||
<template v-if="w.status === 'idle'">💤 空闲</template>
|
||
<template v-else>⚙️ {{ w.currentTask }}</template>
|
||
</div>
|
||
<div class="worker-count-label">已完成: {{ w.completed }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="arrow-section">→</div>
|
||
|
||
<div class="done-section">
|
||
<div class="section-title">已完成 ({{ doneList.length }})</div>
|
||
<div class="done-list">
|
||
<div v-for="task in doneList" :key="task.id" class="done-item">
|
||
✅ {{ task.name }}
|
||
</div>
|
||
<div v-if="doneList.length === 0" class="empty">暂无</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
|
||
const workerCount = ref(3)
|
||
const running = ref(false)
|
||
let taskId = 0
|
||
|
||
const taskTypes = ['发送邮件', '生成报表', '图片压缩', '数据同步', '推送通知', '日志归档', 'PDF 导出', '缓存预热']
|
||
|
||
const queue = ref([])
|
||
const doneList = ref([])
|
||
|
||
const workers = computed(() => {
|
||
const arr = []
|
||
for (let i = 1; i <= workerCount.value; i++) {
|
||
arr.push(workerState.value[i] || { id: i, status: 'idle', currentTask: '', completed: 0 })
|
||
}
|
||
return arr
|
||
})
|
||
|
||
const workerState = ref({})
|
||
|
||
function addTask() {
|
||
const name = taskTypes[taskId % taskTypes.length]
|
||
queue.value.push({ id: ++taskId, name: `${name} #${taskId}` })
|
||
}
|
||
|
||
function resetAll() {
|
||
running.value = false
|
||
queue.value = []
|
||
doneList.value = []
|
||
workerState.value = {}
|
||
taskId = 0
|
||
}
|
||
|
||
async function sleep(ms) {
|
||
return new Promise(r => setTimeout(r, ms))
|
||
}
|
||
|
||
async function startProcessing() {
|
||
running.value = true
|
||
// Initialize worker states
|
||
for (let i = 1; i <= workerCount.value; i++) {
|
||
workerState.value[i] = { id: i, status: 'idle', currentTask: '', completed: 0 }
|
||
}
|
||
|
||
const workerPromises = []
|
||
for (let i = 1; i <= workerCount.value; i++) {
|
||
workerPromises.push(runWorker(i))
|
||
}
|
||
await Promise.all(workerPromises)
|
||
running.value = false
|
||
}
|
||
|
||
async function runWorker(wid) {
|
||
while (queue.value.length > 0) {
|
||
const task = queue.value.shift()
|
||
if (!task) break
|
||
workerState.value = {
|
||
...workerState.value,
|
||
[wid]: { ...workerState.value[wid], status: 'busy', currentTask: task.name }
|
||
}
|
||
await sleep(600 + Math.random() * 800)
|
||
doneList.value.push(task)
|
||
workerState.value = {
|
||
...workerState.value,
|
||
[wid]: { ...workerState.value[wid], status: 'idle', currentTask: '', completed: workerState.value[wid].completed + 1 }
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.worker-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; align-items: center; flex-wrap: wrap; margin-bottom: 1rem; }
|
||
.ctrl-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;
|
||
}
|
||
.ctrl-btn.primary { background: var(--vp-c-brand); color: #fff; border-color: var(--vp-c-brand); }
|
||
.ctrl-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
.small-btn {
|
||
width: 24px; height: 24px; border-radius: 4px; border: 1px solid var(--vp-c-divider);
|
||
background: var(--vp-c-bg); cursor: pointer; font-size: 0.85rem;
|
||
}
|
||
.small-btn:disabled { opacity: 0.5; }
|
||
.worker-count { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; margin-left: auto; }
|
||
.pool-layout { display: flex; gap: 0.75rem; align-items: flex-start; }
|
||
.arrow-section { font-size: 1.5rem; color: var(--vp-c-text-3); padding-top: 2rem; flex-shrink: 0; }
|
||
.queue-section, .done-section { flex: 1; min-width: 0; }
|
||
.workers-section { flex: 1.5; min-width: 0; }
|
||
.section-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 0.5rem; }
|
||
.queue-list, .done-list { display: flex; flex-direction: column; gap: 0.25rem; max-height: 200px; overflow-y: auto; }
|
||
.queue-item {
|
||
padding: 0.4rem 0.6rem; background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3);
|
||
border-radius: 4px; font-size: 0.8rem;
|
||
}
|
||
.done-item {
|
||
padding: 0.4rem 0.6rem; background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.2);
|
||
border-radius: 4px; font-size: 0.8rem;
|
||
}
|
||
.empty { color: var(--vp-c-text-3); font-size: 0.8rem; padding: 0.5rem; }
|
||
.workers-grid { display: flex; flex-direction: column; gap: 0.5rem; }
|
||
.worker-card {
|
||
padding: 0.5rem 0.75rem; border-radius: 8px; background: var(--vp-c-bg);
|
||
border: 1px solid var(--vp-c-divider);
|
||
}
|
||
.worker-card.busy { border-color: var(--vp-c-brand); background: rgba(var(--vp-c-brand-rgb), 0.05); }
|
||
.worker-name { font-weight: 600; font-size: 0.85rem; }
|
||
.worker-status { font-size: 0.8rem; color: var(--vp-c-text-2); margin: 0.25rem 0; }
|
||
.worker-count-label { font-size: 0.75rem; color: var(--vp-c-text-3); }
|
||
@media (max-width: 640px) {
|
||
.pool-layout { flex-direction: column; }
|
||
.arrow-section { transform: rotate(90deg); align-self: center; padding: 0; }
|
||
}
|
||
</style>
|