feat: add interactive demos for AI history, Auth design, and Git intro
This commit is contained in:
@@ -0,0 +1,862 @@
|
||||
<!--
|
||||
AuthInteractiveLoginDemo.vue
|
||||
交互式登录流程演示
|
||||
|
||||
用途:
|
||||
通过模拟真实的登录流程,让用户直观理解认证和授权的概念。
|
||||
|
||||
互动功能:
|
||||
- 模拟登录:输入用户名密码,看到完整的认证流程
|
||||
- 可视化数据流:HTTP 请求和响应的实时展示
|
||||
- 两种模式对比:Session vs JWT
|
||||
-->
|
||||
<template>
|
||||
<div class="auth-interactive-login">
|
||||
<div class="header">
|
||||
<div class="title">🔐 认证流程演示</div>
|
||||
<div class="subtitle">模拟登录过程,理解认证与授权的区别</div>
|
||||
</div>
|
||||
|
||||
<!-- 模式切换 -->
|
||||
<div class="mode-selector">
|
||||
<div class="mode-label">选择鉴权方式:</div>
|
||||
<div class="mode-buttons">
|
||||
<button
|
||||
class="mode-btn"
|
||||
:class="{ active: mode === 'session' }"
|
||||
@click="switchMode('session')"
|
||||
>
|
||||
🍪 Session + Cookie
|
||||
</button>
|
||||
<button
|
||||
class="mode-btn"
|
||||
:class="{ active: mode === 'jwt' }"
|
||||
@click="switchMode('jwt')"
|
||||
>
|
||||
🎫 JWT Token
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 登录表单 -->
|
||||
<div class="login-section">
|
||||
<div class="form-container">
|
||||
<div class="form-title">登录表单</div>
|
||||
<div class="form-fields">
|
||||
<div class="field-group">
|
||||
<label>用户名</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="username"
|
||||
placeholder="输入用户名"
|
||||
:disabled="locked"
|
||||
/>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label>密码</label>
|
||||
<input
|
||||
type="password"
|
||||
v-model="password"
|
||||
placeholder="输入密码"
|
||||
:disabled="locked"
|
||||
@keyup.enter="startDemo"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="login-btn"
|
||||
@click="startDemo"
|
||||
:disabled="!username || !password || locked"
|
||||
>
|
||||
开始演示
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="hints">
|
||||
<div class="hint-title">💡 提示</div>
|
||||
<div class="hint-text">
|
||||
试试用户名 <code>admin</code>,密码 <code>123456</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stepper" v-if="flowStep > 0">
|
||||
<div class="stepper-title">
|
||||
当前步骤:{{ flowStep }} / {{ maxStep }}
|
||||
<span class="stepper-hint"
|
||||
>(手动推进,避免“自动下一步”误解)</span
|
||||
>
|
||||
</div>
|
||||
<div class="stepper-actions">
|
||||
<button
|
||||
class="step-btn"
|
||||
@click="prevStep"
|
||||
:disabled="flowStep <= 1"
|
||||
>
|
||||
上一步
|
||||
</button>
|
||||
<button
|
||||
class="step-btn primary"
|
||||
@click="nextStep"
|
||||
:disabled="flowStep >= maxStep"
|
||||
>
|
||||
下一步
|
||||
</button>
|
||||
<button class="step-btn" @click="resetDemo">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实时数据流 -->
|
||||
<div class="data-flow">
|
||||
<div class="flow-title">📊 数据流可视化</div>
|
||||
|
||||
<!-- 请求阶段 -->
|
||||
<div class="flow-stage request-stage" v-if="currentStage >= 1">
|
||||
<div class="stage-header">
|
||||
<span class="stage-badge">{{
|
||||
currentStage === 1 ? '📤' : '✅'
|
||||
}}</span>
|
||||
<span class="stage-name">1. 客户端发送登录请求</span>
|
||||
</div>
|
||||
<div class="request-content" v-if="currentStage >= 1">
|
||||
<div class="request-line">
|
||||
<span class="method">POST</span>
|
||||
<span class="path">/api/login</span>
|
||||
</div>
|
||||
<div class="request-body">
|
||||
<div class="body-title">Body:</div>
|
||||
<pre>
|
||||
{
|
||||
"username": "{{ username }}",
|
||||
"password": "******"
|
||||
}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-arrow" v-if="currentStage >= 1">⬇️</div>
|
||||
|
||||
<!-- 服务器验证阶段 -->
|
||||
<div class="flow-stage server-stage" v-if="currentStage >= 2">
|
||||
<div class="stage-header">
|
||||
<span class="stage-badge">{{
|
||||
currentStage === 2 ? '⚙️' : '✅'
|
||||
}}</span>
|
||||
<span class="stage-name">2. 服务器验证身份</span>
|
||||
</div>
|
||||
<div class="server-content" v-if="currentStage >= 2">
|
||||
<div class="verification-steps">
|
||||
<div
|
||||
class="verify-step"
|
||||
:class="{ success: verificationStep >= 1 }"
|
||||
>
|
||||
<span class="step-icon">{{
|
||||
verificationStep >= 1 ? '✅' : '⏳'
|
||||
}}</span>
|
||||
<span class="step-text">查询用户数据库</span>
|
||||
</div>
|
||||
<div
|
||||
class="verify-step"
|
||||
:class="{ success: verificationStep >= 2 }"
|
||||
>
|
||||
<span class="step-icon">{{
|
||||
verificationStep >= 2 ? '✅' : '⏳'
|
||||
}}</span>
|
||||
<span class="step-text">验证密码哈希</span>
|
||||
</div>
|
||||
<div
|
||||
class="verify-step"
|
||||
:class="{ success: verificationStep >= 3 }"
|
||||
>
|
||||
<span class="step-icon">{{
|
||||
verificationStep >= 3 ? '✅' : '⏳'
|
||||
}}</span>
|
||||
<span class="step-text"
|
||||
>生成{{
|
||||
mode === 'session' ? 'Session' : 'JWT Token'
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-arrow" v-if="currentStage >= 2">⬇️</div>
|
||||
|
||||
<!-- 响应阶段 -->
|
||||
<div class="flow-stage response-stage" v-if="currentStage >= 3">
|
||||
<div class="stage-header">
|
||||
<span class="stage-badge">{{
|
||||
currentStage === 3 ? '📥' : '✅'
|
||||
}}</span>
|
||||
<span class="stage-name">3. 服务器返回认证结果</span>
|
||||
</div>
|
||||
<div class="response-content" v-if="authResult">
|
||||
<div class="response-status success">✅ 登录成功</div>
|
||||
<div class="response-body">
|
||||
<div class="body-title">Response:</div>
|
||||
<pre v-if="mode === 'session'">
|
||||
{
|
||||
"status": "success",
|
||||
"user": {
|
||||
"id": 123,
|
||||
"username": "{{ username }}"
|
||||
}
|
||||
}</pre
|
||||
>
|
||||
<pre v-else>
|
||||
{
|
||||
"status": "success",
|
||||
"token": "{{ authResult?.token }}",
|
||||
"user": {
|
||||
"id": 123,
|
||||
"username": "{{ username }}"
|
||||
}
|
||||
}</pre
|
||||
>
|
||||
</div>
|
||||
<div class="auth-mechanism" v-if="currentStage >= 4">
|
||||
<div class="mechanism-title">
|
||||
{{ mode === 'session' ? '🍪 Cookie 设置' : '🎫 Token 存储' }}
|
||||
</div>
|
||||
<div class="mechanism-content">
|
||||
<div v-if="mode === 'session'">
|
||||
<code
|
||||
>Set-Cookie: session_id={{ authResult?.sessionId }};
|
||||
HttpOnly; Secure</code
|
||||
>
|
||||
</div>
|
||||
<div v-else>
|
||||
<code
|
||||
>localStorage.setItem("token", "{{
|
||||
authResult?.token
|
||||
}}")</code
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 后续请求 -->
|
||||
<div class="flow-stage request-stage" v-if="currentStage >= 5">
|
||||
<div class="stage-header">
|
||||
<span class="stage-badge">🔄</span>
|
||||
<span class="stage-name">4. 后续请求自动携带认证信息</span>
|
||||
</div>
|
||||
<div class="subsequent-request">
|
||||
<div class="request-line">
|
||||
<span class="method">GET</span>
|
||||
<span class="path">/api/user/profile</span>
|
||||
</div>
|
||||
<div class="auth-header">
|
||||
<div class="header-title">Header:</div>
|
||||
<div v-if="mode === 'session'">
|
||||
<code>Cookie: session_id={{ authResult?.sessionId }}</code>
|
||||
</div>
|
||||
<div v-else>
|
||||
<code>Authorization: Bearer {{ authResult?.token }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态说明 -->
|
||||
<div class="state-description" v-if="currentStage >= 4">
|
||||
<div class="description-title">
|
||||
📖 {{ mode === 'session' ? 'Session' : 'JWT' }} 工作原理
|
||||
</div>
|
||||
<div class="description-content">
|
||||
<p v-if="mode === 'session'">
|
||||
<strong>Session 模式:</strong>服务器在内存或 Redis 中创建一个
|
||||
Session,存储用户信息。 服务器返回一个
|
||||
<code>session_id</code> 给客户端,客户端后续请求会自动在 Cookie
|
||||
中携带这个 ID。 服务器根据 ID 查找对应的 Session,从而识别用户身份。
|
||||
</p>
|
||||
<p v-else>
|
||||
<strong>JWT 模式:</strong>服务器将用户信息编码成 JWT
|
||||
Token,直接返回给客户端。 客户端将 Token 存储在
|
||||
localStorage,后续请求在 <code>Authorization</code> Header 中携带。
|
||||
服务器验证 Token 的签名即可识别用户,无需存储状态。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 重置按钮 -->
|
||||
<div class="reset-section" v-if="currentStage >= 5">
|
||||
<button class="reset-btn" @click="resetDemo">🔄 重新演示</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const mode = ref('session') // 'session' or 'jwt'
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const flowStep = ref(0) // 0 = not started, 1..maxStep = manual steps
|
||||
const authResult = ref(null)
|
||||
const locked = ref(false)
|
||||
|
||||
const maxStep = 7
|
||||
|
||||
const currentStage = computed(() => {
|
||||
if (flowStep.value === 0) return 0
|
||||
if (flowStep.value === 1) return 1
|
||||
if (flowStep.value >= 2 && flowStep.value <= 4) return 2
|
||||
if (flowStep.value === 5) return 3
|
||||
if (flowStep.value === 6) return 4
|
||||
return 5
|
||||
})
|
||||
|
||||
const verificationStep = computed(() => {
|
||||
if (flowStep.value < 2) return 0
|
||||
if (flowStep.value === 2) return 1
|
||||
if (flowStep.value === 3) return 2
|
||||
return 3
|
||||
})
|
||||
|
||||
const switchMode = (newMode) => {
|
||||
mode.value = newMode
|
||||
resetDemo()
|
||||
}
|
||||
|
||||
const buildAuthResult = () => {
|
||||
if (mode.value === 'session') {
|
||||
return {
|
||||
sessionId: 'sess_' + Math.random().toString(36).substring(2, 15)
|
||||
}
|
||||
}
|
||||
|
||||
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
|
||||
const payload = btoa(
|
||||
JSON.stringify({
|
||||
user_id: 123,
|
||||
username: username.value,
|
||||
exp: Math.floor(Date.now() / 1000) + 3600
|
||||
})
|
||||
)
|
||||
const signature = btoa(`${header}.${payload}.secret`)
|
||||
return {
|
||||
token: `${header}.${payload}.${signature}`.substring(0, 50) + '...'
|
||||
}
|
||||
}
|
||||
|
||||
const startDemo = () => {
|
||||
if (!username.value || !password.value) return
|
||||
|
||||
// Start at step 1 (request). Other steps are manual via Next.
|
||||
authResult.value = buildAuthResult()
|
||||
flowStep.value = 1
|
||||
locked.value = true
|
||||
}
|
||||
|
||||
const nextStep = () => {
|
||||
flowStep.value = Math.min(maxStep, flowStep.value + 1)
|
||||
}
|
||||
|
||||
const prevStep = () => {
|
||||
flowStep.value = Math.max(1, flowStep.value - 1)
|
||||
}
|
||||
|
||||
const resetDemo = () => {
|
||||
username.value = ''
|
||||
password.value = ''
|
||||
flowStep.value = 0
|
||||
authResult.value = null
|
||||
locked.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.auth-interactive-login {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 700;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.3rem;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 模式切换 */
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.mode-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.mode-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: 2px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.mode-btn:hover {
|
||||
border-color: var(--vp-c-brand);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: var(--vp-c-brand);
|
||||
color: var(--vp-c-bg);
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
/* 主内容 */
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* 登录表单 */
|
||||
.login-section {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.stepper {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.stepper-title {
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stepper-hint {
|
||||
margin-left: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.stepper-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.step-btn {
|
||||
padding: 0.5rem 0.8rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.step-btn.primary {
|
||||
background: var(--vp-c-brand);
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.step-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.field-group label {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.field-group input {
|
||||
padding: 0.6rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.field-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.field-group input:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: var(--vp-c-brand);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.login-btn:hover:not(:disabled) {
|
||||
background: #2563eb;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.login-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.hints {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-left: 3px solid var(--vp-c-brand);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.hint-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.3rem;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
font-size: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hint-text code {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* 数据流 */
|
||||
.data-flow {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.flow-title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.flow-stage {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
animation: slideIn 0.4s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.stage-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.stage-badge {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.stage-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.request-line {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.method {
|
||||
font-weight: 700;
|
||||
color: #16a34a;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.path {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.request-body,
|
||||
.response-body {
|
||||
background: #1e293b;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.body-title,
|
||||
.header-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.request-body pre,
|
||||
.response-body pre {
|
||||
margin: 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.75rem;
|
||||
color: #e2e8f0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 验证步骤 */
|
||||
.verification-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.verify-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.verify-step.success {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 响应 */
|
||||
.response-status {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.response-status.success {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
/* 认证机制 */
|
||||
.auth-mechanism {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border-left: 3px solid var(--vp-c-brand);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.mechanism-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.mechanism-content code {
|
||||
display: block;
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.75rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 后续请求 */
|
||||
.subsequent-request {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-header code {
|
||||
display: block;
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.75rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
color: var(--vp-c-text-2);
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* 状态说明 */
|
||||
.state-description {
|
||||
background: var(--vp-c-bg);
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.description-title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.description-content {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.description-content code {
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
/* 重置 */
|
||||
.reset-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
padding: 0.75rem 2rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #64748b;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
background: #475569;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mode-selector {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.mode-buttons {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user