docs(api-intro): rewrite API introduction with interactive examples and clearer explanations

- Restructure content with more engaging metaphors and practical examples
- Add simplified interactive components to demonstrate key concepts
- Improve readability with better organization and visual aids
- Update terminology to be more beginner-friendly
- Include real-world API usage scenarios
This commit is contained in:
sanbuphy
2026-01-20 08:51:04 +08:00
parent 6806f05deb
commit 389c9126a1
9 changed files with 2008 additions and 2820 deletions
@@ -1,438 +1,337 @@
<!--
ApiMethodDemo.vue
参考 ide-intro 虚拟环境演示风格
目标 GET/POST/DELETE 讲成//三个按钮并用可视化列表展示效果
注意这是选读内容但组件本身要足够好玩足够直观
目标清晰展示各种 HTTP 方法的含义和使用场景
-->
<template>
<div class="wrap">
<div class="head">
<div class="title">三个按钮GET/ POST/ DELETE</div>
<div class="sub">你不用记英文先玩点按钮看看列表怎么变</div>
</div>
<div class="demo">
<div class="title">🔍 HTTP 方法GETPOSTPUTDELETE 是什么</div>
<p class="subtitle">把它们想象成对数据的"操作方式"</p>
<div class="board">
<div class="left">
<div class="panelTitle">小列表服务器里有的东西</div>
<div class="list">
<div v-if="items.length === 0" class="empty">空的</div>
<div v-for="it in items" :key="it.id" class="row">
<div class="pillId">#{{ it.id }}</div>
<div class="name">{{ it.name }}</div>
<div class="methods-grid">
<div class="method-card get">
<div class="method-badge">GET</div>
<div class="method-title">📖 获取查询</div>
<div class="method-desc">
<p><strong>只看不改</strong> - 从服务器获取数据</p>
<div class="method-examples">
<div class="example-item"> 查询用户信息</div>
<div class="example-item"> 搜索商品</div>
<div class="example-item"> 获取文章列表</div>
</div>
</div>
<div class="mini">
<span class="miniK">提示</span>
<span class="miniV">你可以一直点GET列表不会变</span>
<div class="method-tip">
💡 可以安全重试不会改变服务器数据
</div>
</div>
<div class="right">
<div class="panelTitle">按按钮模拟 API</div>
<div class="btnRow">
<button class="btn get" :disabled="busy" @click="getList">
GET
</button>
<button class="btn post" :disabled="busy" @click="addOne">
POST
</button>
<button class="btn del" :disabled="busy" @click="removeOne">
DELETE
</button>
</div>
<div class="inputs">
<label class="field">
<span class="k">要加什么</span>
<input v-model="newName" class="input" placeholder="随便写个名字" />
</label>
</div>
<div class="result">
<div class="resultTitle">返回结果</div>
<div v-if="!last" class="muted">点一个按钮试试</div>
<div v-else class="resBox" :class="{ ok: last.ok, bad: !last.ok }">
<div class="badge">{{ last.ok ? '成功' : '失败' }}</div>
<div class="text">{{ last.text }}</div>
<div class="method-card post">
<div class="method-badge">POST</div>
<div class="method-title"> 创建新增</div>
<div class="method-desc">
<p><strong>新建数据</strong> - 在服务器创建新资源</p>
<div class="method-examples">
<div class="example-item"> 创建新用户</div>
<div class="example-item"> 提交订单</div>
<div class="example-item"> 发表评论</div>
</div>
</div>
<div class="method-tip">
不能随意重试可能会重复创建
</div>
</div>
<div class="foot">
<div class="stat">
<span class="statK">你点了</span>
<span class="statV">{{ clicks }}</span>
<span class="statK"></span>
<div class="method-card put">
<div class="method-badge">PUT</div>
<div class="method-title"> 更新替换</div>
<div class="method-desc">
<p><strong>整体替换</strong> - 用新数据完全替换旧数据</p>
<div class="method-examples">
<div class="example-item"> 修改用户全部信息</div>
<div class="example-item"> 更换文章全部内容</div>
</div>
<button class="ghost" :disabled="busy" @click="reset">重置</button>
</div>
<div class="method-tip">
会覆盖整个资源需要提供完整数据
</div>
</div>
<div class="method-card patch">
<div class="method-badge">PATCH</div>
<div class="method-title">🔧 修改部分</div>
<div class="method-desc">
<p><strong>局部更新</strong> - 只修改资源的部分字段</p>
<div class="method-examples">
<div class="example-item"> 只修改用户昵称</div>
<div class="example-item"> 更新文章标题</div>
</div>
</div>
<div class="method-tip">
💡 只传需要修改的字段更灵活
</div>
</div>
<div class="method-card delete">
<div class="method-badge">DELETE</div>
<div class="method-title">🗑 删除</div>
<div class="method-desc">
<p><strong>移除数据</strong> - 从服务器删除资源</p>
<div class="method-examples">
<div class="example-item"> 删除用户</div>
<div class="example-item"> 取消订单</div>
<div class="example-item"> 删除评论</div>
</div>
</div>
<div class="method-tip">
操作不可逆删除后无法恢复
</div>
</div>
</div>
<div class="one">
<div class="oneTitle">一句话总结</div>
<div class="oneText">
GET 通常只是拿数据POST/DELETE 改数据所以网络抖动时重试 POST
要更小心
</div>
<div class="comparison-table">
<div class="table-title">📊 快速对比</div>
<table>
<thead>
<tr>
<th>方法</th>
<th>操作</th>
<th>是否会改数据</th>
<th>能否重试</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="badge get">GET</span></td>
<td>查询</td>
<td> </td>
<td> 可以</td>
</tr>
<tr>
<td><span class="badge post">POST</span></td>
<td>创建</td>
<td> </td>
<td> 不建议</td>
</tr>
<tr>
<td><span class="badge put">PUT</span></td>
<td>替换</td>
<td> </td>
<td> 不建议</td>
</tr>
<tr>
<td><span class="badge patch">PATCH</span></td>
<td>修改</td>
<td> </td>
<td> 不建议</td>
</tr>
<tr>
<td><span class="badge delete">DELETE</span></td>
<td>删除</td>
<td> </td>
<td> 不建议</td>
</tr>
</tbody>
</table>
</div>
<div class="tips">
<p><strong>💡 新手建议</strong></p>
<ul>
<li>先学会 <strong>GET</strong>查询 <strong>POST</strong>创建就够用了</li>
<li>PUT/PATCH/DELETE 可以慢慢学理解原理更重要</li>
<li>实际开发中GET POST 占了 80% 的使用场景</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const busy = ref(false)
const clicks = ref(0)
const seq = ref(3)
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cookie' }
])
const newName = ref('Donut')
const last = ref(null)
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms))
}
async function getList() {
clicks.value += 1
busy.value = true
await sleep(220)
last.value = {
ok: true,
text: `拿到了 ${items.value.length} 条数据(列表不变)`
}
busy.value = false
}
async function addOne() {
clicks.value += 1
busy.value = true
await sleep(260)
const name = String(newName.value || '').trim()
if (!name) {
last.value = { ok: false, text: '你还没写“要加什么”' }
busy.value = false
return
}
seq.value += 1
items.value = [...items.value, { id: seq.value, name }]
last.value = { ok: true, text: `已添加:#${seq.value} ${name}` }
busy.value = false
}
async function removeOne() {
clicks.value += 1
busy.value = true
await sleep(240)
if (items.value.length === 0) {
last.value = { ok: false, text: '已经空了,删不了' }
busy.value = false
return
}
const removed = items.value[items.value.length - 1]
items.value = items.value.slice(0, -1)
last.value = { ok: true, text: `已删除:#${removed.id} ${removed.name}` }
busy.value = false
}
function reset() {
seq.value = 3
items.value = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cookie' }
]
newName.value = 'Donut'
last.value = null
clicks.value = 0
}
// 无需脚本逻辑
</script>
<style scoped>
.wrap {
.demo {
border: 1px solid var(--vp-c-divider);
border-radius: 14px;
border-radius: 12px;
padding: 20px;
background: var(--vp-c-bg-soft);
padding: 16px;
}
.head {
display: flex;
flex-direction: column;
gap: 6px;
margin: 16px 0;
}
.title {
font-weight: 900;
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
color: var(--vp-c-text-1);
}
.subtitle {
color: var(--vp-c-text-2);
margin-bottom: 24px;
}
.methods-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.method-card {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 12px;
padding: 16px;
position: relative;
}
.method-card.get { border-color: #3b82f6; }
.method-card.post { border-color: #22c55e; }
.method-card.put { border-color: #f59e0b; }
.method-card.patch { border-color: #8b5cf6; }
.method-card.delete { border-color: #ef4444; }
.method-badge {
position: absolute;
top: 12px;
right: 12px;
padding: 4px 12px;
border-radius: 6px;
font-weight: bold;
font-size: 12px;
color: white;
}
.method-card.get .method-badge { background: #3b82f6; }
.method-card.post .method-badge { background: #22c55e; }
.method-card.put .method-badge { background: #f59e0b; }
.method-card.patch .method-badge { background: #8b5cf6; }
.method-card.delete .method-badge { background: #ef4444; }
.method-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 12px;
padding-right: 60px;
color: var(--vp-c-text-1);
}
.sub {
font-size: 13px;
color: var(--vp-c-text-2);
.method-desc p {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
}
.board {
margin-top: 12px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.left,
.right {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg);
.method-examples {
background: var(--vp-c-bg-soft);
padding: 12px;
border-radius: 8px;
margin-bottom: 12px;
}
.panelTitle {
font-weight: 900;
.example-item {
font-size: 13px;
padding: 4px 0;
color: var(--vp-c-text-1);
}
.list {
margin-top: 10px;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
padding: 10px;
display: grid;
gap: 8px;
min-height: 160px;
}
.empty {
font-size: 12px;
color: var(--vp-c-text-3);
}
.row {
display: flex;
gap: 10px;
align-items: center;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 12px;
padding: 8px 10px;
}
.pillId {
font-size: 12px;
font-weight: 900;
color: var(--vp-c-text-2);
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 999px;
padding: 2px 8px;
}
.name {
font-size: 13px;
font-weight: 900;
color: var(--vp-c-text-1);
}
.mini {
margin-top: 10px;
font-size: 12px;
color: var(--vp-c-text-2);
}
.miniK {
font-weight: 900;
}
.btnRow {
margin-top: 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.btn {
border-radius: 12px;
.method-tip {
padding: 10px 12px;
font-weight: 900;
cursor: pointer;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
font-size: 12px;
line-height: 1.5;
}
.method-card.get .method-tip {
background: #eff6ff;
color: #1e40af;
}
.method-card.post .method-tip,
.method-card.put .method-tip,
.method-card.patch .method-tip,
.method-card.delete .method-tip {
background: #fef2f2;
color: #991b1b;
}
.comparison-table {
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.table-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: var(--vp-c-text-1);
}
.btn.get {
border-color: color-mix(in srgb, #60a5fa 45%, var(--vp-c-divider));
}
.btn.post {
border-color: color-mix(in srgb, #22c55e 45%, var(--vp-c-divider));
}
.btn.del {
border-color: color-mix(in srgb, #ef4444 45%, var(--vp-c-divider));
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.inputs {
margin-top: 12px;
}
.field {
display: grid;
grid-template-columns: 72px 1fr;
gap: 10px;
align-items: center;
}
.k {
font-size: 12px;
color: var(--vp-c-text-2);
font-weight: 900;
}
.input {
table {
width: 100%;
border: 1px solid var(--vp-c-divider);
border-collapse: collapse;
}
thead {
background: var(--vp-c-bg-soft);
border-radius: 10px;
padding: 8px 10px;
}
th {
padding: 12px;
text-align: left;
font-weight: bold;
font-size: 13px;
color: var(--vp-c-text-1);
font-size: 13px;
border-bottom: 2px solid var(--vp-c-divider);
}
.result {
margin-top: 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg-soft);
padding: 10px 12px;
}
.resultTitle {
font-weight: 900;
td {
padding: 12px;
font-size: 13px;
border-bottom: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
}
.muted {
margin-top: 8px;
font-size: 12px;
color: var(--vp-c-text-2);
}
.resBox {
margin-top: 8px;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg);
padding: 10px 12px;
}
.resBox.ok {
border-color: color-mix(in srgb, #22c55e 45%, var(--vp-c-divider));
}
.resBox.bad {
border-color: color-mix(in srgb, #ef4444 45%, var(--vp-c-divider));
tr:last-child td {
border-bottom: none;
}
.badge {
display: inline-block;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 999px;
padding: 2px 10px;
padding: 4px 10px;
border-radius: 6px;
font-weight: bold;
font-size: 12px;
font-weight: 900;
color: var(--vp-c-text-1);
color: white;
}
.text {
margin-top: 8px;
font-size: 13px;
font-weight: 900;
color: var(--vp-c-text-1);
}
.badge.get { background: #3b82f6; }
.badge.post { background: #22c55e; }
.badge.put { background: #f59e0b; }
.badge.patch { background: #8b5cf6; }
.badge.delete { background: #ef4444; }
.foot {
margin-top: 12px;
display: flex;
justify-content: space-between;
gap: 10px;
align-items: center;
}
.stat {
font-size: 12px;
.tips {
background: var(--vp-c-bg);
padding: 16px;
border-radius: 8px;
font-size: 14px;
line-height: 1.8;
color: var(--vp-c-text-2);
}
.statV {
font-weight: 900;
color: var(--vp-c-text-1);
margin: 0 6px;
.tips p {
margin-bottom: 8px;
}
.ghost {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
border-radius: 10px;
padding: 8px 12px;
font-weight: 900;
cursor: pointer;
.tips ul {
margin: 0;
padding-left: 20px;
}
.ghost:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.one {
margin-top: 12px;
border: 1px dashed var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg);
padding: 10px 12px;
}
.oneTitle {
font-weight: 900;
font-size: 13px;
color: var(--vp-c-text-1);
}
.oneText {
margin-top: 8px;
font-size: 12px;
color: var(--vp-c-text-2);
line-height: 1.6;
}
@media (max-width: 720px) {
.board {
grid-template-columns: 1fr;
}
.btnRow {
grid-template-columns: 1fr;
}
.tips li {
margin: 4px 0;
}
</style>