feat(docs): restructure API design guide with interactive demos and practical examples
refactor(components): replace static API design components with interactive demos - Add ApiRequestDemo, RestfulUrlDemo, StatusCodeDemo, ErrorHandlingDemo, and ApiVersioningDemo - Remove outdated ResourceAnalogy, RequestStructureDemo, and VersioningStrategyDemo docs(api-design): completely rewrite API design chapter with restaurant analogy - Add clear problem scenarios and solutions - Include practical e-commerce API examples - Add terminology glossary - Improve error handling and versioning sections style(ai-history): enhance FoundationDemo with better visual hierarchy - Add section blocks for core theories and early breakthroughs - Improve typography and highlighting chore: remove unused components (CpuArchitectureDemo, EvolutionFlowDemo)
This commit is contained in:
@@ -1,389 +1,370 @@
|
||||
<!--
|
||||
ErrorHandlingDemo.vue - 错误处理演示组件
|
||||
展示错误处理的正确和错误示例对比
|
||||
-->
|
||||
<template>
|
||||
<div class="demo">
|
||||
<div class="header">
|
||||
<span class="icon">🚨</span>
|
||||
<span class="title">错误处理:优雅地"拒绝"</span>
|
||||
</div>
|
||||
|
||||
<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 class="eh-root">
|
||||
<div class="eh-terminal">
|
||||
<div class="term-bar">
|
||||
<span class="dot r" /><span class="dot y" /><span class="dot g" />
|
||||
<span class="term-title">错误处理演示</span>
|
||||
</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 ref="termEl" class="term-body">
|
||||
<div v-for="(l, i) in lines" :key="i" class="t-line">
|
||||
<span v-if="l.kind === 'cmd'" class="t-ps">> </span>
|
||||
<span :class="'t-' + l.kind">{{ l.text }}</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 正确示范 -->
|
||||
<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="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 class="t-line">
|
||||
<span class="t-ps">> </span>
|
||||
<span class="t-typing">{{ typing }}<span class="t-cur">▋</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="eh-btns">
|
||||
<button
|
||||
v-for="op in ops"
|
||||
:key="op.id"
|
||||
:disabled="running || !op.ok()"
|
||||
:class="['eh-btn', { 'eh-btn--on': active === op.id, 'eh-btn--dim': !op.ok() }]"
|
||||
@click="run(op)"
|
||||
>
|
||||
<code>{{ op.cmd }}</code>
|
||||
</button>
|
||||
<button class="eh-btn eh-btn--reset" :disabled="running" @click="reset">重置</button>
|
||||
</div>
|
||||
|
||||
<div class="eh-response">
|
||||
<div class="res-header">
|
||||
<span class="res-label">响应结构</span>
|
||||
<span class="res-status" :class="responseStatus">{{ responseStatus }}</span>
|
||||
</div>
|
||||
<div class="res-body">
|
||||
<pre v-if="responseData">{{ responseData }}</pre>
|
||||
<div v-else class="res-empty">点击上方按钮查看错误响应示例</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hint" class="eh-hint">💡 {{ hint }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, nextTick } from 'vue'
|
||||
|
||||
const selectedTab = ref('bad')
|
||||
const termEl = ref(null)
|
||||
const lines = ref([{ kind: 'dim', text: '// 对比好的和差的错误处理方式' }])
|
||||
const typing = ref('')
|
||||
const running = ref(false)
|
||||
const active = ref(null)
|
||||
const hint = ref('点击按钮,对比"好的"和"差的"错误响应设计。')
|
||||
const responseData = ref('')
|
||||
const responseStatus = ref('')
|
||||
|
||||
const goodResponse = {
|
||||
code: 20003,
|
||||
message: '密码强度不足',
|
||||
errors: [
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
||||
|
||||
const ops = [
|
||||
{
|
||||
id: 'bad1',
|
||||
cmd: '❌ 差: 所有错误都 200',
|
||||
ok: () => true,
|
||||
output: [
|
||||
{ kind: 'dim', text: '// HTTP 200 但业务失败' },
|
||||
{ kind: 'yel', text: 'HTTP/1.1 200 OK' },
|
||||
{ kind: 'dim', text: '' },
|
||||
{ kind: 'yel', text: '{ "error": "出错了" }' },
|
||||
],
|
||||
hint: '问题:HTTP 状态码说"成功",但业务说"出错"。缓存层会缓存这个"成功"响应,监控系统也发现不了问题。',
|
||||
do: () => {
|
||||
responseStatus.value = '200 (错误)'
|
||||
responseData.value = `{
|
||||
"error": "出错了"
|
||||
}`
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'bad2',
|
||||
cmd: '❌ 差: 错误信息太笼统',
|
||||
ok: () => true,
|
||||
output: [
|
||||
{ kind: 'dim', text: '// 错误信息没有帮助' },
|
||||
{ kind: 'red', text: 'HTTP/1.1 400 Bad Request' },
|
||||
{ kind: 'dim', text: '' },
|
||||
{ kind: 'red', text: '{ "message": "参数错误" }' },
|
||||
],
|
||||
hint: '问题:客户端不知道哪个参数错了、为什么错。用户只能看到"参数错误",无法修正。',
|
||||
do: () => {
|
||||
responseStatus.value = '400'
|
||||
responseData.value = `{
|
||||
"message": "参数错误"
|
||||
}`
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'bad3',
|
||||
cmd: '❌ 差: 暴露敏感信息',
|
||||
ok: () => true,
|
||||
output: [
|
||||
{ kind: 'dim', text: '// 500 错误暴露堆栈' },
|
||||
{ kind: 'red', text: 'HTTP/1.1 500 Internal Server Error' },
|
||||
{ kind: 'dim', text: '' },
|
||||
{ kind: 'red', text: '{ "error": "TypeError: Cannot read property..." }' },
|
||||
{ kind: 'red', text: '{ "stack": "at UserService.login..." }' },
|
||||
{ kind: 'red', text: '{ "sql": "SELECT * FROM users WHERE..." }' },
|
||||
],
|
||||
hint: '危险!暴露了代码结构、数据库查询。攻击者可以利用这些信息进行攻击。',
|
||||
do: () => {
|
||||
responseStatus.value = '500'
|
||||
responseData.value = `{
|
||||
"error": "TypeError: Cannot read property 'id' of undefined",
|
||||
"stack": "at UserService.login (src/service.js:45)",
|
||||
"sql": "SELECT * FROM users WHERE email='...'"
|
||||
}`
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'good1',
|
||||
cmd: '✅ 好: 正确的状态码',
|
||||
ok: () => true,
|
||||
output: [
|
||||
{ kind: 'dim', text: '// HTTP 状态码准确表达错误类型' },
|
||||
{ kind: 'grn', text: 'HTTP/1.1 404 Not Found' },
|
||||
{ kind: 'dim', text: '' },
|
||||
{ kind: 'grn', text: '{ "code": 10002, "message": "用户不存在" }' },
|
||||
],
|
||||
hint: '正确!404 表示资源不存在,客户端一看就知道问题所在。',
|
||||
do: () => {
|
||||
responseStatus.value = '404'
|
||||
responseData.value = `{
|
||||
"code": 10002,
|
||||
"message": "用户不存在",
|
||||
"request_id": "req-550e8400"
|
||||
}`
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'good2',
|
||||
cmd: '✅ 好: 详细的错误信息',
|
||||
ok: () => true,
|
||||
output: [
|
||||
{ kind: 'dim', text: '// 错误信息帮助定位问题' },
|
||||
{ kind: 'grn', text: 'HTTP/1.1 422 Unprocessable Entity' },
|
||||
{ kind: 'dim', text: '' },
|
||||
{ kind: 'grn', text: '{ "code": 20003, "message": "密码强度不足" }' },
|
||||
{ kind: 'grn', text: '{ "errors": [{ "field": "password", ... }] }' },
|
||||
],
|
||||
hint: '正确!提供了错误码、字段级别的错误详情,前端可以精确提示用户。',
|
||||
do: () => {
|
||||
responseStatus.value = '422'
|
||||
responseData.value = `{
|
||||
"code": 20003,
|
||||
"message": "密码强度不足",
|
||||
"errors": [
|
||||
{
|
||||
field: 'password',
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: '密码必须包含至少 1 个大写字母、1 个小写字母、1 个数字,且长度至少 8 位'
|
||||
"field": "password",
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "密码必须包含至少 1 个大写字母、1 个小写字母、1 个数字"
|
||||
}
|
||||
],
|
||||
request_id: 'req-550e8400-e29b-41d4-a716-44665544000',
|
||||
timestamp: '2024-01-15T09:30:00.000Z',
|
||||
help_url: 'https://docs.example.com/errors/20003'
|
||||
"request_id": "req-550e8400"
|
||||
}`
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'good3',
|
||||
cmd: '✅ 好: 安全的错误响应',
|
||||
ok: () => true,
|
||||
output: [
|
||||
{ kind: 'dim', text: '// 500 只返回错误 ID' },
|
||||
{ kind: 'grn', text: 'HTTP/1.1 500 Internal Server Error' },
|
||||
{ kind: 'dim', text: '' },
|
||||
{ kind: 'grn', text: '{ "code": 10000, "message": "服务器错误" }' },
|
||||
{ kind: 'grn', text: '{ "error_id": "err-a1b2c3d4" }' },
|
||||
],
|
||||
hint: '正确!只返回错误 ID,详细日志记录在服务器。用户反馈错误 ID,技术人员可以快速定位。',
|
||||
do: () => {
|
||||
responseStatus.value = '500'
|
||||
responseData.value = `{
|
||||
"code": 10000,
|
||||
"message": "服务器内部错误,请联系管理员",
|
||||
"error_id": "err-a1b2c3d4",
|
||||
"request_id": "req-550e8400",
|
||||
"help_url": "https://docs.example.com/errors/10000"
|
||||
}`
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
async function run(op) {
|
||||
if (running.value) return
|
||||
running.value = true
|
||||
active.value = op.id
|
||||
hint.value = ''
|
||||
typing.value = ''
|
||||
responseData.value = ''
|
||||
responseStatus.value = ''
|
||||
|
||||
for (const ch of op.cmd) {
|
||||
typing.value += ch
|
||||
await sleep(15)
|
||||
}
|
||||
await sleep(80)
|
||||
lines.value.push({ kind: 'cmd', text: op.cmd })
|
||||
typing.value = ''
|
||||
await nextTick()
|
||||
scroll()
|
||||
await sleep(150)
|
||||
|
||||
for (const l of op.output) {
|
||||
lines.value.push(l)
|
||||
await nextTick()
|
||||
scroll()
|
||||
await sleep(50)
|
||||
}
|
||||
|
||||
op.do()
|
||||
await sleep(120)
|
||||
hint.value = op.hint
|
||||
running.value = false
|
||||
}
|
||||
|
||||
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: '余额不足' }
|
||||
]
|
||||
function scroll() {
|
||||
if (termEl.value) termEl.value.scrollTop = termEl.value.scrollHeight
|
||||
}
|
||||
|
||||
function reset() {
|
||||
lines.value = [{ kind: 'dim', text: '// 对比好的和差的错误处理方式' }]
|
||||
active.value = null
|
||||
hint.value = '点击按钮,对比"好的"和"差的"错误响应设计。'
|
||||
typing.value = ''
|
||||
running.value = false
|
||||
responseData.value = ''
|
||||
responseStatus.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo {
|
||||
.eh-root {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 24px 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: var(--vp-c-bg-soft);
|
||||
margin: 1rem 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 16px 20px;
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
color: white;
|
||||
.eh-terminal { background: #141420; }
|
||||
.term-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 5px;
|
||||
padding: 7px 12px;
|
||||
background: #1e1e2e;
|
||||
}
|
||||
.dot { width: 11px; height: 11px; border-radius: 50%; }
|
||||
.dot.r { background: #ff5f57; }
|
||||
.dot.y { background: #febc2e; }
|
||||
.dot.g { background: #28c840; }
|
||||
.term-title { margin-left: 8px; font-size: 0.72rem; color: #666; font-family: monospace; }
|
||||
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.comparison-tabs {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 2px solid;
|
||||
border-radius: 6px;
|
||||
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: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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-size: 13px;
|
||||
}
|
||||
|
||||
.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;
|
||||
.term-body {
|
||||
min-height: 100px;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.response-body code {
|
||||
font-family: monospace;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.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;
|
||||
padding: 0.7rem 1rem;
|
||||
font-family: 'Menlo', 'Monaco', monospace;
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
.t-line { display: flex; min-width: min-content; }
|
||||
.t-ps { color: #89b4fa; flex-shrink: 0; }
|
||||
.t-cmd { color: #cdd6f4; }
|
||||
.t-dim { color: #585b70; }
|
||||
.t-grn { color: #a6e3a1; }
|
||||
.t-red { color: #f38ba8; }
|
||||
.t-yel { color: #f9e2af; }
|
||||
.t-typing { color: #cdd6f4; }
|
||||
.t-cur { animation: blink 1s step-end infinite; }
|
||||
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
||||
|
||||
.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 {
|
||||
.eh-btns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
background: #0d0d1a;
|
||||
border-top: 1px solid #2a2a3e;
|
||||
}
|
||||
.eh-btn {
|
||||
background: #1e1e2e;
|
||||
border: 1px solid #313244;
|
||||
border-radius: 5px;
|
||||
padding: 4px 9px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.eh-btn code { font-size: 0.68rem; color: #7f849c; font-family: monospace; white-space: nowrap; }
|
||||
.eh-btn:hover:not(:disabled) { border-color: var(--vp-c-brand); }
|
||||
.eh-btn--on { border-color: var(--vp-c-brand) !important; }
|
||||
.eh-btn--on code { color: var(--vp-c-brand); }
|
||||
.eh-btn--dim { opacity: 0.3; cursor: not-allowed; }
|
||||
.eh-btn--reset {
|
||||
background: transparent;
|
||||
border-color: #313244;
|
||||
margin-left: auto;
|
||||
}
|
||||
.eh-btn--reset code { display: none; }
|
||||
.eh-btn--reset::after { content: '重置'; font-size: 0.7rem; color: #585b70; }
|
||||
|
||||
.code-item {
|
||||
.eh-response {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
.res-header {
|
||||
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);
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.code-badge {
|
||||
.res-label { font-weight: 700; font-size: 0.8rem; color: var(--vp-c-text-1); }
|
||||
.res-status {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
padding: 4px 8px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
.res-status\.200\ \(错误\) { background: #f9e2af22; color: #d97706; }
|
||||
.res-status\.400 { background: #f59e0b22; color: #d97706; }
|
||||
.res-status\.404 { background: #3b82f622; color: #3b82f6; }
|
||||
.res-status\.422 { background: #8b5cf622; color: #8b5cf6; }
|
||||
.res-status\.500 { background: #ef444422; color: #ef4444; }
|
||||
|
||||
.res-body {
|
||||
padding: 12px;
|
||||
min-height: 80px;
|
||||
}
|
||||
.res-body pre {
|
||||
margin: 0;
|
||||
font-family: 'Menlo', 'Monaco', monospace;
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.5;
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.res-empty {
|
||||
color: var(--vp-c-text-3);
|
||||
font-style: italic;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.code-desc {
|
||||
font-size: 13px;
|
||||
.eh-hint {
|
||||
padding: 10px 12px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
font-size: 0.82rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.comparison-tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.status-line {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user