397 lines
8.3 KiB
Vue
397 lines
8.3 KiB
Vue
|
|
<!--
|
|||
|
|
ApiDocumentDemo.vue
|
|||
|
|
参考 ide-intro 的“虚拟界面 + 点击探索”风格。
|
|||
|
|
目标:让新手学会看 API 文档的 3 个重点:入口在哪 / 要填什么 / 会得到什么。
|
|||
|
|
-->
|
|||
|
|
<template>
|
|||
|
|
<div class="wrap">
|
|||
|
|
<div class="head">
|
|||
|
|
<div class="title">怎么读 API 文档?(像找按钮一样找)</div>
|
|||
|
|
<div class="sub">
|
|||
|
|
任务:请在下面的“假文档”里,依次点出:<b>入口</b>、<b>要填什么</b>、<b>会得到什么</b>。
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="game">
|
|||
|
|
<div class="doc">
|
|||
|
|
<div class="docBar">
|
|||
|
|
<span class="dot red" />
|
|||
|
|
<span class="dot yellow" />
|
|||
|
|
<span class="dot green" />
|
|||
|
|
<span class="docTitle">API 文档(示例)</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="docBody">
|
|||
|
|
<button
|
|||
|
|
class="block"
|
|||
|
|
:class="{ hit: hits.entry }"
|
|||
|
|
@click="hit('entry')"
|
|||
|
|
>
|
|||
|
|
<div class="blockK">入口</div>
|
|||
|
|
<div class="blockV">GET /v1/users/{id}</div>
|
|||
|
|
<div class="blockHint">(你要按哪个按钮)</div>
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
class="block"
|
|||
|
|
:class="{ hit: hits.input }"
|
|||
|
|
@click="hit('input')"
|
|||
|
|
>
|
|||
|
|
<div class="blockK">要填什么</div>
|
|||
|
|
<div class="blockV">id(用户编号)</div>
|
|||
|
|
<div class="blockHint">(你要告诉它什么)</div>
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
class="block"
|
|||
|
|
:class="{ hit: hits.output }"
|
|||
|
|
@click="hit('output')"
|
|||
|
|
>
|
|||
|
|
<div class="blockK">会得到什么</div>
|
|||
|
|
<div class="blockV">{ id, name }</div>
|
|||
|
|
<div class="blockHint">(成功时给你的结果)</div>
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
class="block gray"
|
|||
|
|
:class="{ hit: hits.fail }"
|
|||
|
|
@click="hit('fail')"
|
|||
|
|
>
|
|||
|
|
<div class="blockK">失败会怎样(常见)</div>
|
|||
|
|
<div class="blockV">没钥匙 / 找不到 / 太频繁</div>
|
|||
|
|
<div class="blockHint">(你要能看懂失败原因)</div>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="side">
|
|||
|
|
<div class="task">
|
|||
|
|
<div class="taskTitle">你要找的 3 个重点</div>
|
|||
|
|
<div class="taskList">
|
|||
|
|
<div :class="['taskItem', hits.entry && 'done']">
|
|||
|
|
① 入口在哪(点“入口”)
|
|||
|
|
</div>
|
|||
|
|
<div :class="['taskItem', hits.input && 'done']">
|
|||
|
|
② 要填什么(点“要填什么”)
|
|||
|
|
</div>
|
|||
|
|
<div :class="['taskItem', hits.output && 'done']">
|
|||
|
|
③ 会得到什么(点“会得到什么”)
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="muted">你只要先会这三件事,就能开始用 API 了。</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="explain" v-if="last">
|
|||
|
|
<div class="explainTitle">你刚刚点的是:{{ labelOf(last) }}</div>
|
|||
|
|
<div class="explainText">{{ explainOf(last) }}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="actions">
|
|||
|
|
<button class="btn" @click="autoWin">一键帮我找对</button>
|
|||
|
|
<button class="ghost" @click="reset">重置</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="win" v-if="won">
|
|||
|
|
<div class="winTitle">完成!</div>
|
|||
|
|
<div class="winText">
|
|||
|
|
你已经会读 80% 的 API 文档了:入口 / 要填 / 会得到。
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { computed, reactive, ref } from 'vue'
|
|||
|
|
|
|||
|
|
const hits = reactive({
|
|||
|
|
entry: false,
|
|||
|
|
input: false,
|
|||
|
|
output: false,
|
|||
|
|
fail: false
|
|||
|
|
})
|
|||
|
|
const last = ref('')
|
|||
|
|
|
|||
|
|
const won = computed(() => hits.entry && hits.input && hits.output)
|
|||
|
|
|
|||
|
|
function hit(key) {
|
|||
|
|
hits[key] = true
|
|||
|
|
last.value = key
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function reset() {
|
|||
|
|
hits.entry = false
|
|||
|
|
hits.input = false
|
|||
|
|
hits.output = false
|
|||
|
|
hits.fail = false
|
|||
|
|
last.value = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function autoWin() {
|
|||
|
|
hits.entry = true
|
|||
|
|
hits.input = true
|
|||
|
|
hits.output = true
|
|||
|
|
last.value = 'output'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function labelOf(key) {
|
|||
|
|
if (key === 'entry') return '入口'
|
|||
|
|
if (key === 'input') return '要填什么'
|
|||
|
|
if (key === 'output') return '会得到什么'
|
|||
|
|
if (key === 'fail') return '失败会怎样'
|
|||
|
|
return key
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function explainOf(key) {
|
|||
|
|
if (key === 'entry') {
|
|||
|
|
return '入口就是“按钮名字”。你要按哪个按钮,先找到它。'
|
|||
|
|
}
|
|||
|
|
if (key === 'input') {
|
|||
|
|
return '要填什么 = 你需要提供的信息。比如 id、页码、搜索词。'
|
|||
|
|
}
|
|||
|
|
if (key === 'output') {
|
|||
|
|
return '会得到什么 = 成功时返回的数据。你要关心字段有什么、有没有可能为空。'
|
|||
|
|
}
|
|||
|
|
if (key === 'fail') {
|
|||
|
|
return '失败会怎样 = 你要能看懂失败原因,好给用户提示/重试。'
|
|||
|
|
}
|
|||
|
|
return ''
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.wrap {
|
|||
|
|
border: 1px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 14px;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.head {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 6px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-weight: 900;
|
|||
|
|
font-size: 16px;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sub {
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.game {
|
|||
|
|
margin-top: 12px;
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: 1.2fr 0.8fr;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.doc {
|
|||
|
|
border: 1px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 14px;
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.docBar {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 6px;
|
|||
|
|
align-items: center;
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
border-bottom: 1px solid var(--vp-c-divider);
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dot {
|
|||
|
|
width: 10px;
|
|||
|
|
height: 10px;
|
|||
|
|
border-radius: 999px;
|
|||
|
|
}
|
|||
|
|
.dot.red {
|
|||
|
|
background: #ef4444;
|
|||
|
|
}
|
|||
|
|
.dot.yellow {
|
|||
|
|
background: #f59e0b;
|
|||
|
|
}
|
|||
|
|
.dot.green {
|
|||
|
|
background: #22c55e;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.docTitle {
|
|||
|
|
margin-left: 6px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: 900;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.docBody {
|
|||
|
|
padding: 12px;
|
|||
|
|
display: grid;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.block {
|
|||
|
|
border: 1px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
text-align: left;
|
|||
|
|
cursor: pointer;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.block:hover {
|
|||
|
|
border-color: var(--vp-c-brand-1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.block.gray {
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.block.hit {
|
|||
|
|
border-color: #22c55e;
|
|||
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, #22c55e 18%, transparent);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.blockK {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
font-weight: 900;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.blockV {
|
|||
|
|
margin-top: 6px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 900;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.blockHint {
|
|||
|
|
margin-top: 6px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.side {
|
|||
|
|
border: 1px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
background: var(--vp-c-bg);
|
|||
|
|
padding: 12px;
|
|||
|
|
display: grid;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.taskTitle {
|
|||
|
|
font-weight: 900;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.taskList {
|
|||
|
|
margin-top: 10px;
|
|||
|
|
display: grid;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.taskItem {
|
|||
|
|
border: 1px solid var(--vp-c-divider);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 8px 10px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
font-weight: 800;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.taskItem.done {
|
|||
|
|
border-color: #22c55e;
|
|||
|
|
background: color-mix(in srgb, #22c55e 12%, var(--vp-c-bg));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.muted {
|
|||
|
|
margin-top: 10px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.explain {
|
|||
|
|
border: 1px dashed var(--vp-c-divider);
|
|||
|
|
border-radius: 12px;
|
|||
|
|
background: var(--vp-c-bg-soft);
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.explainTitle {
|
|||
|
|
font-weight: 900;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: var(--vp-c-text-1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.explainText {
|
|||
|
|
margin-top: 8px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: var(--vp-c-text-2);
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.actions {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 10px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn {
|
|||
|
|
border: 1px solid var(--vp-c-brand-1);
|
|||
|
|
background: var(--vp-c-brand-1);
|
|||
|
|
color: #fff;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
padding: 8px 12px;
|
|||
|
|
font-weight: 900;
|
|||
|
|
cursor: pointer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.win {
|
|||
|
|
border: 1px solid #22c55e;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
background: color-mix(in srgb, #22c55e 12%, var(--vp-c-bg));
|
|||
|
|
padding: 10px 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.winTitle {
|
|||
|
|
font-weight: 900;
|
|||
|
|
color: #166534;
|
|||
|
|
font-size: 13px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.winText {
|
|||
|
|
margin-top: 8px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #166534;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 720px) {
|
|||
|
|
.game {
|
|||
|
|
grid-template-columns: 1fr;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|