Files
test-repo/docs/.vitepress/theme/components/appendix/api-design/ResponseStructureDemo.vue
T
sanbuphy 0eba9e87e9 fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier)
- Relaxed strict Vue/JS rules for demo code compatibility
- Fix syntax errors in ApiPlayground and VoiceCloningDemo
- Fix duplicate else-if condition in ApiPlayground
- Fix Promise executor async pattern in AutoregressiveAudioDemo
- Add TypeScript file support to ESLint config

Warnings reduced from 295 to 251 problems.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 17:38:10 +08:00

407 lines
9.2 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.
<!--
ResponseStructureDemo.vue - HTTP 响应结构演示组件
展示标准化 API 响应结构和分页响应
-->
<template>
<div class="demo">
<div class="header">
<span class="icon">📦</span>
<span class="title">HTTP 响应结构标准化的数据契约</span>
</div>
<div class="content">
<div class="response-tabs">
<button
v-for="tab in tabs"
:key="tab.id"
class="tab-btn"
:class="{ active: selectedTab === tab.id }"
@click="selectedTab = tab.id"
>
{{ tab.name }}
</button>
</div>
<div class="response-detail">
<div class="response-header">
<div class="status-line">
<span class="http-version">HTTP/1.1</span>
<span
class="status-code"
:class="getStatusClass(currentResponse.status)"
>{{ currentResponse.status }}</span>
<span class="status-text">{{ currentResponse.statusText }}</span>
</div>
</div>
<div class="response-headers">
<div class="header-item">
<span class="header-key">Content-Type:</span>
<span class="header-value">application/json</span>
</div>
<div class="header-item">
<span class="header-key">X-Request-ID:</span>
<span class="header-value">req-550e8400-e29b-41d4</span>
</div>
<div class="header-item">
<span class="header-key">X-Response-Time:</span>
<span class="header-value">45ms</span>
</div>
</div>
<div class="response-body">
<pre><code>{{ JSON.stringify(currentResponse.body, null, 2) }}</code></pre>
</div>
<div class="field-descriptions">
<h4>字段说明</h4>
<div class="field-list">
<div
v-for="field in currentResponse.fields"
:key="field.name"
class="field-item"
>
<div class="field-name">
<code>{{ field.name }}</code>
<span class="field-type">{{ field.type }}</span>
</div>
<div class="field-desc">
{{ field.description }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const tabs = [
{ id: 'success', name: '成功响应' },
{ id: 'pagination', name: '分页响应' },
{ id: 'error', name: '错误响应' }
]
const responses = {
success: {
status: 200,
statusText: 'OK',
body: {
code: 0,
message: 'success',
data: {
id: 123,
name: '张三',
email: 'zhangsan@example.com',
phone: '13800138000',
created_at: '2024-01-15T10:30:00.000Z'
},
request_id: 'req-550e8400-e29b-41d4-a716-446655440000',
timestamp: '2024-01-15T10:30:00.000Z'
},
fields: [
{ name: 'code', type: 'integer', description: '业务状态码,0 表示成功' },
{ name: 'message', type: 'string', description: '状态描述,成功时为 "success"' },
{ name: 'data', type: 'object', description: '业务数据,成功时返回具体数据' },
{ name: 'request_id', type: 'string', description: '请求唯一标识,用于问题追踪' },
{ name: 'timestamp', type: 'string', description: '响应时间戳,ISO 8601 格式' }
]
},
pagination: {
status: 200,
statusText: 'OK',
body: {
code: 0,
message: 'success',
data: {
list: [
{ id: 1, name: '商品A', price: 100 },
{ id: 2, name: '商品B', price: 200 }
],
pagination: {
page: 1,
page_size: 20,
total: 156,
total_pages: 8,
has_next: true,
has_prev: false
}
},
request_id: 'req-550e8400-e29b-41d4-a716-446655440000',
timestamp: '2024-01-15T10:30:00.000Z'
},
fields: [
{ name: 'list', type: 'array', description: '数据列表' },
{ name: 'pagination', type: 'object', description: '分页信息对象' },
{ name: 'page', type: 'integer', description: '当前页码' },
{ name: 'page_size', type: 'integer', description: '每页数量' },
{ name: 'total', type: 'integer', description: '总记录数' },
{ name: 'total_pages', type: 'integer', description: '总页数' },
{ name: 'has_next', type: 'boolean', description: '是否有下一页' },
{ name: 'has_prev', type: 'boolean', description: '是否有上一页' }
]
},
error: {
status: 422,
statusText: 'Unprocessable Entity',
body: {
code: 20003,
message: '密码强度不足',
errors: [
{
field: 'password',
code: 'VALIDATION_ERROR',
message: '密码必须包含至少 1 个大写字母、1 个小写字母、1 个数字,且长度至少 8 位'
}
],
request_id: 'req-550e8400-e29b-41d4-a716-446655440000',
timestamp: '2024-01-15T10:30:00.000Z',
help_url: 'https://docs.example.com/errors/20003'
},
fields: [
{ name: 'code', type: 'integer', description: '错误码,非 0 表示失败' },
{ name: 'message', type: 'string', description: '错误描述,供用户阅读' },
{ name: 'errors', type: 'array', description: '详细错误信息数组(可选)' },
{ name: 'help_url', type: 'string', description: '错误文档链接(可选)' }
]
}
}
const selectedTab = ref('success')
const currentResponse = computed(() => responses[selectedTab.value])
function getStatusClass(status) {
const prefix = Math.floor(status / 100)
switch (prefix) {
case 2: return 'success'
case 3: return 'redirect'
case 4: return 'client-error'
case 5: return 'server-error'
default: return ''
}
}
</script>
<style scoped>
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
margin: 24px 0;
overflow: hidden;
}
.header {
padding: 16px 20px;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
color: white;
display: flex;
align-items: center;
gap: 12px;
}
.icon {
font-size: 24px;
}
.title {
font-weight: 600;
font-size: 16px;
}
.content {
padding: 24px;
}
.response-tabs {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
.tab-btn {
padding: 8px 16px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
}
.tab-btn:hover {
transform: translateY(-1px);
}
.tab-btn.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
transform: scale(1.05);
}
.response-detail {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
overflow: hidden;
}
.response-header {
padding: 16px;
background: var(--vp-c-bg-soft);
border-bottom: 1px solid var(--vp-c-divider);
}
.status-line {
display: flex;
align-items: center;
gap: 12px;
}
.http-version {
font-family: monospace;
font-size: 13px;
color: var(--vp-c-text-2);
}
.status-code {
padding: 4px 8px;
border-radius: 4px;
font-weight: 700;
font-size: 13px;
font-family: monospace;
}
.status-code.success {
background: #dcfce7;
color: #16a34a;
}
.status-code.client-error {
background: #fef3c7;
color: #d97706;
}
.status-text {
font-size: 13px;
color: var(--vp-c-text-2);
}
.response-headers {
padding: 12px 16px;
border-bottom: 1px solid var(--vp-c-divider);
}
.header-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
font-size: 12px;
}
.header-key {
font-family: monospace;
font-weight: 600;
color: var(--vp-c-text-2);
min-width: 120px;
}
.header-value {
font-family: monospace;
color: var(--vp-c-text-1);
}
.response-body {
padding: 16px;
border-bottom: 1px solid var(--vp-c-divider);
}
.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);
}
.field-descriptions {
padding: 16px;
}
.field-descriptions h4 {
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-1);
margin: 0 0 12px 0;
}
.field-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.field-item {
padding: 12px;
background: var(--vp-c-bg-soft);
border-radius: 6px;
border-left: 3px solid var(--vp-c-brand);
}
.field-name {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.field-name code {
font-family: monospace;
font-size: 13px;
color: var(--vp-c-brand);
font-weight: 600;
}
.field-type {
font-size: 11px;
padding: 2px 6px;
background: rgba(var(--vp-c-brand-rgb), 0.1);
color: var(--vp-c-brand);
border-radius: 4px;
font-family: monospace;
}
.field-desc {
font-size: 13px;
color: var(--vp-c-text-2);
line-height: 1.6;
}
@media (max-width: 640px) {
.response-tabs {
flex-direction: column;
}
.status-line {
flex-wrap: wrap;
}
.header-item {
flex-direction: column;
align-items: flex-start;
}
}
</style>