Files
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

434 lines
8.7 KiB
Vue
Raw Permalink 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.
<!--
SessionCookieDemo.vue
Session + Cookie手动推进更贴近真实 Web 登录态
-->
<template>
<div class="session-demo">
<div class="header">
<div class="title">
🍪 Session + Cookie有状态登录
</div>
<div class="subtitle">
默认手动推进先看清楚状态再进入下一步避免自动下一步误解
</div>
</div>
<div class="controls">
<button
class="btn primary"
:disabled="step !== 0"
@click="start"
>
开始
</button>
<button
class="btn"
:disabled="step <= 1"
@click="prev"
>
上一步
</button>
<button
class="btn primary"
:disabled="step === 0 || step >= maxStep"
@click="next"
>
下一步
</button>
<button
class="btn"
@click="reset"
>
重置
</button>
</div>
<div
v-if="step > 0"
class="progress"
>
Step {{ step }} / {{ maxStep }} · {{ steps[step - 1]?.title }}
</div>
<div class="grid">
<div class="card">
<div class="card-title">
浏览器客户端
</div>
<div class="box">
<div class="box-title">
Cookie Jar
</div>
<div
v-if="cookie"
class="kv"
>
<div class="k">
session_id
</div>
<div class="v mono">
{{ cookie }}
</div>
</div>
<div
v-else
class="empty"
>
暂无 Cookie
</div>
</div>
<div class="box">
<div class="box-title">
本步请求
</div>
<pre class="code"><code>{{ clientRequest }}</code></pre>
</div>
</div>
<div class="card">
<div class="card-title">
服务器
</div>
<div class="box">
<div class="box-title">
Session StoreRedis/Memory
</div>
<div
v-if="session"
class="kv"
>
<div class="k mono">
{{ cookie }}
</div>
<div class="v">
<div class="row">
<span class="muted">user_id</span> 123
</div>
<div class="row">
<span class="muted">username</span> alice
</div>
<div class="row">
<span class="muted">role</span> admin
</div>
</div>
</div>
<div
v-else
class="empty"
>
暂无 Session
</div>
</div>
<div class="box">
<div class="box-title">
本步响应
</div>
<pre class="code"><code>{{ serverResponse }}</code></pre>
</div>
</div>
</div>
<div class="card">
<div class="card-title">
{{ steps[step - 1]?.title || '流程说明' }}
</div>
<div class="desc">
{{ steps[step - 1]?.desc }}
</div>
<div
v-if="steps[step - 1]?.warn"
class="warn"
>
<div class="warn-title">
注意
</div>
<div class="warn-text">
{{ steps[step - 1]?.warn }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const maxStep = 5
const step = ref(0)
const cookie = ref('')
const session = ref(false)
const steps = [
{
title: '1) 登录请求(POST /login',
desc: '用户提交用户名/密码,服务器验证成功后创建 Session。'
},
{
title: '2) 服务器 Set-Cookie',
desc: '服务器返回 Set-Cookie: session_id=...;浏览器保存 Cookie。',
warn: 'Cookie 建议加 HttpOnly + Secure + SameSite;同时要考虑 CSRF 防护。'
},
{
title: '3) 后续请求自动带 Cookie',
desc: '浏览器对同域请求会自动带上 Cookie,服务器用 session_id 查 Session。'
},
{
title: '4) 授权判断(role/权限)',
desc: '认证(你是谁)之后,仍需要授权(你能做什么)。比如 admin 才能访问管理接口。'
},
{
title: '5) 注销',
desc: '服务器删除 Session(或让其过期),并让浏览器清理 Cookie。'
}
]
const start = () => {
step.value = 1
cookie.value = ''
session.value = false
}
const next = () => {
step.value = Math.min(maxStep, step.value + 1)
applyState()
}
const prev = () => {
step.value = Math.max(1, step.value - 1)
applyState()
}
const reset = () => {
step.value = 0
cookie.value = ''
session.value = false
}
const applyState = () => {
if (step.value <= 1) {
cookie.value = ''
session.value = false
return
}
if (step.value >= 2) {
if (!cookie.value)
cookie.value = 'sess_' + Math.random().toString(36).slice(2, 10)
session.value = true
}
if (step.value >= 5) {
// logout (show as empty state by step title/response)
// We don't auto-clear state; keep it visible until reset to avoid “auto” confusion.
}
}
const clientRequest = computed(() => {
if (step.value === 0) return '(点击开始)'
if (step.value === 1) {
return `POST /login
Content-Type: application/json
{"username":"alice","password":"******"}`
}
if (step.value === 2) return '(等待服务器响应并写入 Cookie'
if (step.value === 3) {
return `GET /api/user/profile
Cookie: session_id=${cookie.value}`
}
if (step.value === 4) {
return `GET /api/admin/users
Cookie: session_id=${cookie.value}`
}
return `POST /logout
Cookie: session_id=${cookie.value}`
})
const serverResponse = computed(() => {
if (step.value === 0) return ''
if (step.value === 1) return '200 OK (credentials valid)'
if (step.value === 2) {
return `200 OK
Set-Cookie: session_id=${cookie.value}; HttpOnly; Secure; SameSite=Lax`
}
if (step.value === 3) return '200 OK (profile payload...)'
if (step.value === 4)
return '200 OK (admin data...) / 403 Forbidden (if not admin)'
return `200 OK
Set-Cookie: session_id=; Max-Age=0`
})
</script>
<style scoped>
.session-demo {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 6px;
padding: 1.5rem;
margin: 0.5rem 0;
}
.header {
margin-bottom: 1rem;
}
.title {
font-weight: 800;
color: var(--vp-c-text-1);
}
.subtitle {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.controls {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.75rem;
}
.btn {
padding: 0.5rem 0.75rem;
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: 700;
font-size: 0.875rem;
}
.btn.primary {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: var(--vp-c-bg);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.progress {
color: var(--vp-c-text-2);
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.card {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.75rem;
}
.card-title {
font-weight: 800;
margin-bottom: 0.75rem;
color: var(--vp-c-text-1);
}
.box {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 0.75rem;
}
.box-title {
font-weight: 800;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.empty {
color: var(--vp-c-text-3);
font-style: italic;
}
.kv {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 0.75rem;
align-items: start;
}
.k {
font-weight: 800;
color: var(--vp-c-text-1);
}
.v {
color: var(--vp-c-text-2);
line-height: 1.7;
}
.row {
display: flex;
gap: 0.5rem;
}
.muted {
color: var(--vp-c-text-3);
min-width: 72px;
}
.mono {
font-family: var(--vp-font-family-mono);
word-break: break-all;
}
.code {
margin: 0;
padding: 0.75rem;
border-radius: 6px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
overflow-x: auto;
color: var(--vp-c-text-1);
}
.desc {
color: var(--vp-c-text-2);
line-height: 1.75;
}
.warn {
margin-top: 0.75rem;
border: 1px solid rgba(var(--vp-c-brand-rgb), 0.18);
background: rgba(var(--vp-c-brand-rgb), 0.06);
border-radius: 6px;
padding: 0.75rem;
}
.warn-title {
font-weight: 800;
color: var(--vp-c-text-1);
margin-bottom: 0.25rem;
}
.warn-text {
color: var(--vp-c-text-2);
line-height: 1.7;
}
@media (max-width: 720px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>