Files
test-repo/docs/.vitepress/theme/components/appendix/backend-evolution/MonolithReleaseRiskDemo.vue
T
sanbuphy d35211071a style: update border-radius and padding values across components
- standardize border-radius from 8px to 6px for consistent styling
- adjust padding values from 1rem to 0.75rem for better visual hierarchy
- remove redundant overflow-y properties for cleaner code
2026-02-14 20:23:34 +08:00

264 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
MonolithReleaseRiskDemo.vue
单体发布的影响面与风险演示
-->
<template>
<div class="release-demo">
<div class="header">
<div class="title">单体发布牵一发而动全身</div>
<div class="subtitle">选择修改范围看看爆炸半径</div>
</div>
<div class="content">
<div class="modules">
<div class="section-title">本次改动涉及</div>
<div class="module-grid">
<button
v-for="module in modules"
:key="module.key"
class="module-btn"
:class="{ active: module.active }"
@click="toggleModule(module.key)"
>
{{ module.label }}
</button>
</div>
<div class="slider">
<label>
改动规模<strong>{{ changeSizeLabel }}</strong>
</label>
<input v-model="changeSize" type="range" min="1" max="5" step="1" />
</div>
</div>
<div class="result">
<div class="risk-meter">
<div class="risk-title">故障概率</div>
<div class="risk-value">{{ riskPercent }}%</div>
<div class="meter">
<div class="bar" :style="{ width: riskPercent + '%' }"></div>
</div>
</div>
<button class="deploy-btn" @click="deployRelease">模拟发布</button>
<div class="status" :class="deployStatusClass">
{{ deployStatus }}
</div>
<div class="history">
<div class="section-title">最近 3 次发布</div>
<ul>
<li v-for="(item, index) in deployHistory" :key="index">
{{ item }}
</li>
<li v-if="deployHistory.length === 0">暂无记录</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const modules = ref([
{ key: 'user', label: '用户', active: true },
{ key: 'order', label: '订单', active: true },
{ key: 'payment', label: '支付', active: false },
{ key: 'product', label: '商品', active: false }
])
const changeSize = ref(3)
const deployHistory = ref([])
const deployStatus = ref('等待发布...')
const deployStatusClass = ref('idle')
const activeModules = computed(
() => modules.value.filter((m) => m.active).length
)
const riskPercent = computed(() => {
const base = 8
const moduleRisk = activeModules.value * 12
const changeRisk = changeSize.value * 6
return Math.min(95, base + moduleRisk + changeRisk)
})
const changeSizeLabel = computed(() => {
const labels = ['很小', '小', '中等', '大', '特大']
return labels[changeSize.value - 1] || '中等'
})
const toggleModule = (key) => {
const target = modules.value.find((m) => m.key === key)
if (!target) return
target.active = !target.active
}
const deployRelease = () => {
const roll = Math.random() * 100
if (roll < riskPercent.value) {
deployStatus.value = `发布失败:全站回滚,用时 ${8 + changeSize.value * 4} 分钟`
deployStatusClass.value = 'fail'
} else {
deployStatus.value = '发布成功:流量切换完成'
deployStatusClass.value = 'success'
}
const summary = `${new Date().toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})} - ${deployStatus.value}`
deployHistory.value.unshift(summary)
deployHistory.value = deployHistory.value.slice(0, 3)
}
</script>
<style scoped>
.release-demo {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.header {
margin-bottom: 1.25rem;
}
.title {
font-weight: 700;
font-size: 1.05rem;
}
.subtitle {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.25rem;
}
.section-title {
font-weight: 600;
margin-bottom: 0.5rem;
}
.module-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
}
.module-btn {
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
padding: 0.5rem;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s ease;
}
.module-btn.active {
background: rgba(59, 130, 246, 0.15);
border-color: #3b82f6;
color: #1d4ed8;
}
.slider label {
display: block;
margin: 1rem 0 0.5rem;
font-size: 0.9rem;
}
.slider input[type='range'] {
width: 100%;
}
.risk-meter {
background: var(--vp-c-bg);
border-radius: 10px;
padding: 0.75rem;
border: 1px solid var(--vp-c-divider);
}
.risk-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.risk-value {
font-size: 1.5rem;
font-weight: 700;
margin: 0.35rem 0 0.75rem;
}
.meter {
height: 8px;
background: var(--vp-c-bg-soft);
border-radius: 999px;
overflow: hidden;
}
.bar {
height: 100%;
background: linear-gradient(90deg, #f59e0b, #ef4444);
}
.deploy-btn {
margin-top: 1rem;
width: 100%;
border: none;
border-radius: 6px;
padding: 0.6rem;
background: var(--vp-c-brand);
color: #fff;
cursor: pointer;
font-weight: 600;
}
.status {
margin-top: 0.75rem;
font-size: 0.9rem;
padding: 0.5rem 0.75rem;
border-radius: 6px;
background: var(--vp-c-bg);
border: 1px dashed var(--vp-c-divider);
}
.status.success {
color: #16a34a;
border-color: rgba(22, 163, 74, 0.4);
}
.status.fail {
color: #ef4444;
border-color: rgba(239, 68, 68, 0.4);
}
.history {
margin-top: 1rem;
}
.history ul {
padding-left: 1rem;
margin: 0.25rem 0 0;
color: var(--vp-c-text-2);
font-size: 0.85rem;
}
@media (max-width: 720px) {
.module-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>