f35cddeb8b
## 新增组件 (14个) - CodeSmellDemo.vue: 代码异味识别演示 - DecisionMatrixDemo.vue: 决策矩阵工具 - DesignPatternCatalogDemo.vue: 设计模式目录 - DocStructureDemo.vue: 文档结构示例 - LicenseComparisonDemo.vue: 开源许可证对比 - OpenSourceWorkflowDemo.vue: 开源协作流程 - PatternPlaygroundDemo.vue: 设计模式演练场 - RefactoringDemo.vue: 重构实战演示 - SecurityChecklistDemo.vue: 安全检查清单 - TDDCycleDemo.vue: TDD 循环演示 - TechRadarDemo.vue: 技术雷达图 - TechWritingPracticeDemo.vue: 技术写作实践 - TestPyramidDemo.vue: 测试金字塔 - WebSecurityDemo.vue: Web 安全演示 ## 文档更新 (7篇) - code-quality-refactoring.md: 代码质量与重构 - design-patterns.md: 设计模式 - open-source-collaboration.md: 开源协作 - security-thinking.md: 安全思维 - technical-writing.md: 技术写作 - technology-selection.md: 技术选型 - testing-strategies.md: 测试策略 ## 其他变更 - 将 browser-as-os.md 内容合并到 computer-networks.md - 更新 .gitignore 和 theme/index.js
138 lines
5.9 KiB
Vue
138 lines
5.9 KiB
Vue
<template>
|
||
<div class="tech-radar-demo">
|
||
<div class="demo-label">技术雷达 ── 点击技术点查看详情</div>
|
||
|
||
<div class="radar-container">
|
||
<div class="radar-rings">
|
||
<div v-for="ring in rings" :key="ring.name" class="ring" :class="ring.cls">
|
||
<span class="ring-label">{{ ring.name }}</span>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-for="tech in techs"
|
||
:key="tech.name"
|
||
class="tech-dot"
|
||
:class="[tech.category, { active: selected === tech.name }]"
|
||
:style="tech.pos"
|
||
@click="selected = selected === tech.name ? '' : tech.name"
|
||
>
|
||
<span class="dot-label">{{ tech.name }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="legend">
|
||
<span v-for="c in cats" :key="c.cls" class="legend-item">
|
||
<span class="legend-dot" :class="c.cls"></span>{{ c.name }}
|
||
</span>
|
||
</div>
|
||
|
||
<Transition name="fade">
|
||
<div v-if="selectedTech" class="info-card">
|
||
<h4>{{ selectedTech.name }}</h4>
|
||
<div class="info-ring">环位:{{ selectedTech.ring }}</div>
|
||
<p>{{ selectedTech.desc }}</p>
|
||
</div>
|
||
</Transition>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
const selected = ref('')
|
||
const selectedTech = computed(() => techs.find(t => t.name === selected.value))
|
||
|
||
const rings = [
|
||
{ name: '采纳', cls: 'adopt' },
|
||
{ name: '试验', cls: 'trial' },
|
||
{ name: '评估', cls: 'assess' },
|
||
{ name: '暂缓', cls: 'hold' }
|
||
]
|
||
|
||
const cats = [
|
||
{ name: '语言', cls: 'lang' },
|
||
{ name: '框架', cls: 'framework' },
|
||
{ name: '工具', cls: 'tool' },
|
||
{ name: '平台', cls: 'platform' }
|
||
]
|
||
|
||
const techs = [
|
||
{ name: 'TypeScript', category: 'lang', ring: '采纳', pos: { top: '42%', left: '30%' }, desc: '类型安全的 JavaScript 超集,已成为前端项目标配。' },
|
||
{ name: 'React', category: 'framework', ring: '采纳', pos: { top: '35%', left: '55%' }, desc: '生态最丰富的前端框架,适合大型团队和复杂应用。' },
|
||
{ name: 'Vue', category: 'framework', ring: '采纳', pos: { top: '50%', left: '45%' }, desc: '渐进式框架,学习曲线平缓,中文社区活跃。' },
|
||
{ name: 'Go', category: 'lang', ring: '采纳', pos: { top: '55%', left: '32%' }, desc: '高并发后端首选,编译快、部署简单。' },
|
||
{ name: 'Rust', category: 'lang', ring: '试验', pos: { top: '30%', left: '22%' }, desc: '内存安全无 GC,适合系统编程和高性能场景,学习曲线陡峭。' },
|
||
{ name: 'Svelte', category: 'framework', ring: '试验', pos: { top: '25%', left: '60%' }, desc: '编译时框架,无虚拟 DOM,包体积极小。' },
|
||
{ name: 'Bun', category: 'tool', ring: '评估', pos: { top: '18%', left: '42%' }, desc: '新一代 JS 运行时,速度极快但生态尚在完善。' },
|
||
{ name: 'Deno', category: 'platform', ring: '评估', pos: { top: '15%', left: '55%' }, desc: '安全优先的 JS/TS 运行时,内置工具链。' },
|
||
{ name: 'jQuery', category: 'framework', ring: '暂缓', pos: { top: '8%', left: '38%' }, desc: '历史功臣,但现代框架已全面替代,新项目不建议使用。' }
|
||
]
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tech-radar-demo {
|
||
border: 1px solid var(--vp-c-divider);
|
||
border-radius: 8px;
|
||
background: var(--vp-c-bg-soft);
|
||
padding: 1rem 1.2rem;
|
||
margin: 1rem 0;
|
||
}
|
||
.demo-label { font-size: 0.78rem; font-weight: bold; color: var(--vp-c-text-2); margin-bottom: 1rem; text-align: center; }
|
||
|
||
.radar-container { position: relative; width: 100%; padding-top: 70%; margin-bottom: 1rem; }
|
||
|
||
.radar-rings { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
||
|
||
.ring {
|
||
border-radius: 50%;
|
||
position: absolute;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
padding-top: 4px;
|
||
}
|
||
.ring .ring-label { font-size: 0.68rem; font-weight: 600; opacity: 0.6; }
|
||
.ring.adopt { width: 90%; height: 90%; background: #d1fae520; border: 1px dashed #6ee7b7; }
|
||
.ring.trial { width: 66%; height: 66%; background: #dbeafe20; border: 1px dashed #93c5fd; }
|
||
.ring.assess { width: 42%; height: 42%; background: #fef3c720; border: 1px dashed #fcd34d; }
|
||
.ring.hold { width: 20%; height: 20%; background: #fee2e220; border: 1px dashed #fca5a5; }
|
||
|
||
.tech-dot {
|
||
position: absolute;
|
||
padding: 3px 8px;
|
||
border-radius: 12px;
|
||
font-size: 0.72rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
white-space: nowrap;
|
||
}
|
||
.tech-dot:hover { transform: scale(1.1); }
|
||
.tech-dot.active { box-shadow: 0 0 0 2px var(--vp-c-brand); transform: scale(1.15); }
|
||
|
||
.tech-dot.lang { background: #dbeafe; color: #1e40af; }
|
||
.tech-dot.framework { background: #d1fae5; color: #065f46; }
|
||
.tech-dot.tool { background: #fef3c7; color: #92400e; }
|
||
.tech-dot.platform { background: #fae8ff; color: #86198f; }
|
||
|
||
:root.dark .tech-dot.lang { background: #172554; color: #93c5fd; }
|
||
:root.dark .tech-dot.framework { background: #022c22; color: #6ee7b7; }
|
||
:root.dark .tech-dot.tool { background: #451a03; color: #fcd34d; }
|
||
:root.dark .tech-dot.platform { background: #4a044e; color: #f0abfc; }
|
||
|
||
.legend { display: flex; justify-content: center; gap: 1rem; font-size: 0.75rem; color: var(--vp-c-text-3); margin-bottom: 0.8rem; flex-wrap: wrap; }
|
||
.legend-item { display: flex; align-items: center; gap: 4px; }
|
||
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
|
||
.legend-dot.lang { background: #3b82f6; }
|
||
.legend-dot.framework { background: #10b981; }
|
||
.legend-dot.tool { background: #f59e0b; }
|
||
.legend-dot.platform { background: #d946ef; }
|
||
|
||
.info-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; padding: 1rem; background: var(--vp-c-bg); }
|
||
.info-card h4 { margin: 0 0 4px; font-size: 1rem; }
|
||
.info-ring { font-size: 0.75rem; color: var(--vp-c-text-3); margin-bottom: 6px; }
|
||
.info-card p { font-size: 0.85rem; color: var(--vp-c-text-2); margin: 0; }
|
||
|
||
.fade-enter-active, .fade-leave-active { transition: opacity 0.25s; }
|
||
.fade-enter-from, .fade-leave-to { opacity: 0; }
|
||
</style>
|