feat: enhance demo components with consistent styling and info boxes

- Add standardized header and info box components to all demo files
- Improve visual consistency with theme colors and spacing
- Add max-height and overflow-y for better content containment
- Update package.json build script with --force flag
- Add .gitignore entries for REFACTORING files
- Fix table formatting in audio-intro.md
This commit is contained in:
sanbuphy
2026-02-14 12:14:07 +08:00
parent cd2ce9e661
commit ebe2bf6109
70 changed files with 12307 additions and 10445 deletions
@@ -1,97 +1,379 @@
<!--
ErrorHandlingDemo.vue - 错误处理演示组件
展示错误处理的正确和错误示例对比
-->
<template>
<div class="demo-container">
<div class="demo-header">
<h4>错误处理演示</h4>
<p class="hint">展示RESTful API中的错误处理机制</p>
<div class="demo">
<div class="header">
<span class="icon">🚨</span>
<span class="title">错误处理优雅地"拒绝"</span>
</div>
<div class="demo-content">
<div class="error-types">
<div class="error-item">
<span class="code">400</span>
<span class="name">Bad Request</span>
<span class="desc">请求参数错误</span>
<div class="content">
<div class="comparison-tabs">
<button
class="tab-btn bad"
:class="{ active: selectedTab === 'bad' }"
@click="selectedTab = 'bad'"
>
错误示范
</button>
<button
class="tab-btn good"
:class="{ active: selectedTab === 'good' }"
@click="selectedTab = 'good'"
>
正确示范
</button>
</div>
<!-- 错误示范 -->
<div v-if="selectedTab === 'bad'" class="comparison bad">
<div class="response-preview">
<div class="status-line bad">
<span>HTTP/1.1</span>
<span class="code">200 OK</span>
</div>
<div class="response-body">
<pre><code>{
"error": "出错了"
}</code></pre>
</div>
</div>
<div class="error-item">
<span class="code">401</span>
<span class="name">Unauthorized</span>
<span class="desc">未授权访问</span>
<div class="problems">
<h4>问题分析</h4>
<ul>
<li>
<span class="icon"></span>
HTTP 状态码说"成功"但业务说"出错" - 前后端状态不一致
</li>
<li>
<span class="icon"></span>
错误信息太笼统无法定位问题
</li>
<li>
<span class="icon"></span>
没有错误代码难以程序化判断
</li>
<li>
<span class="icon"></span>
浏览器和 CDN 会缓存这个"成功的"响应
</li>
</ul>
</div>
<div class="error-item">
<span class="code">404</span>
<span class="name">Not Found</span>
<span class="desc">资源不存在</span>
</div>
<!-- 正确示范 -->
<div v-if="selectedTab === 'good'" class="comparison good">
<div class="response-preview">
<div class="status-line">
<span>HTTP/1.1</span>
<span class="code">422 Unprocessable Entity</span>
</div>
<div class="response-body">
<pre><code>{{ JSON.stringify(goodResponse, null, 2) }}</code></pre>
</div>
</div>
<div class="error-item">
<span class="code">500</span>
<span class="name">Server Error</span>
<span class="desc">服务器内部错误</span>
<div class="highlights">
<h4>正确做法</h4>
<ul>
<li>
<span class="icon"></span>
<strong>正确的 HTTP 状态码</strong>: 422 表示语义错误
</li>
<li>
<span class="icon"></span>
<strong>业务错误码</strong>: `code: 20003` 可用于程序判断
</li>
<li>
<span class="icon"></span>
<strong>详细错误信息</strong>: `errors` 数组包含具体字段和原因
</li>
<li>
<span class="icon"></span>
<strong>可追踪性</strong>: `request_id` 用于日志查询
</li>
<li>
<span class="icon"></span>
<strong>帮助链接</strong>: `help_url` 引导用户查看文档
</li>
</ul>
</div>
<div class="error-codes">
<h4>错误码体系</h4>
<div class="code-list">
<div v-for="item in errorCodeItems" :key="item.code" class="code-item">
<span class="code-badge">{{ item.code }}</span>
<span class="code-desc">{{ item.desc }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const selectedTab = ref('bad')
const goodResponse = {
code: 20003,
message: '密码强度不足',
errors: [
{
field: 'password',
code: 'VALIDATION_ERROR',
message: '密码必须包含至少 1 个大写字母、1 个小写字母、1 个数字,且长度至少 8 位'
}
],
request_id: 'req-550e8400-e29b-41d4-a716-44665544000',
timestamp: '2024-01-15T09:30:00.000Z',
help_url: 'https://docs.example.com/errors/20003'
}
const errorCodeItems = [
{ code: '1XXYY', desc: '通用错误(第1位固定为1' },
{ code: '10001', desc: '参数错误' },
{ code: '10010', desc: '用户不存在' },
{ code: '10018', desc: 'Token 已过期' },
{ code: '10021', desc: '权限不足' },
{ code: '20003', desc: '密码强度不足' },
{ code: '20014', desc: '余额不足' }
]
</script>
<style scoped>
.demo-container {
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 20px;
border-radius: 12px;
background: var(--vp-c-bg-soft);
margin: 24px 0;
overflow: hidden;
}
.demo-header {
margin-bottom: 20px;
}
.demo-header h4 {
margin: 0 0 8px 0;
color: var(--vp-c-text-1);
}
.hint {
margin: 0;
font-size: 14px;
color: var(--vp-c-text-2);
}
.demo-content {
.header {
padding: 16px 20px;
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
display: flex;
flex-direction: column;
gap: 16px;
}
.error-types {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.error-item {
.icon {
font-size: 24px;
}
.title {
font-weight: 600;
font-size: 16px;
}
.content {
padding: 24px;
}
.comparison-tabs {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 16px;
gap: 12px;
margin-bottom: 20px;
}
.tab-btn {
flex: 1;
padding: 12px;
border: 2px solid;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.tab-btn.bad {
border-color: #ef4444;
background: var(--vp-c-bg);
color: #ef4444;
}
.tab-btn.bad:hover {
background: #fef2f2;
}
.tab-btn.bad.active {
background: #ef4444;
color: white;
}
.tab-btn.good {
border-color: #22c55e;
background: var(--vp-c-bg);
color: #22c55e;
}
.tab-btn.good:hover {
background: #f0fdf4;
}
.tab-btn.good.active {
background: #22c55e;
color: white;
}
.comparison {
background: var(--vp-c-bg);
border-radius: 8px;
border-left: 4px solid #f56c6c;
overflow: hidden;
}
.error-item .code {
.response-preview {
margin-bottom: 20px;
}
.status-line {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--vp-c-bg-soft);
border-bottom: 1px solid var(--vp-c-divider);
font-family: monospace;
font-weight: 600;
color: #f56c6c;
font-size: 16px;
min-width: 50px;
font-size: 13px;
}
.error-item .name {
font-weight: 500;
.status-line.bad .code {
color: #ef4444;
font-weight: 700;
}
.status-line:not(.bad) .code {
color: #d97706;
font-weight: 700;
}
.response-body {
padding: 16px;
}
.response-body pre {
margin: 0;
background: var(--vp-c-bg-alt);
padding: 16px;
border-radius: 6px;
font-size: 12px;
line-height: 1.5;
overflow-x: auto;
}
.response-body code {
font-family: monospace;
color: var(--vp-c-text-1);
min-width: 120px;
}
.error-item .desc {
color: var(--vp-c-text-2);
.problems, .highlights {
padding: 16px;
}
.problems h4, .highlights h4 {
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-1);
margin: 0 0 12px 0;
}
.problems ul, .highlights ul {
margin: 0;
padding-left: 0;
list-style: none;
}
.problems li, .highlights li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px;
margin-bottom: 8px;
border-radius: 6px;
line-height: 1.6;
font-size: 13px;
}
.problems li {
background: #fef2f2;
color: #991b1b;
}
.highlights li {
background: #f0fdf4;
color: #166534;
}
.problems li .icon, .highlights li .icon {
font-size: 16px;
flex-shrink: 0;
}
.problems li strong, .highlights li strong {
font-weight: 600;
}
.error-codes {
padding: 16px;
border-top: 1px solid var(--vp-c-divider);
}
.error-codes h4 {
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-1);
margin: 0 0 12px 0;
}
.code-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.code-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
background: var(--vp-c-bg-soft);
border-radius: 6px;
border-left: 3px solid var(--vp-c-brand);
}
.code-badge {
font-family: monospace;
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
background: var(--vp-c-brand);
color: white;
border-radius: 4px;
min-width: 70px;
text-align: center;
}
.code-desc {
font-size: 13px;
color: var(--vp-c-text-2);
}
@media (max-width: 640px) {
.comparison-tabs {
flex-direction: column;
}
.status-line {
flex-wrap: wrap;
}
}
</style>