Files
test-repo/docs/.vitepress/theme/components/appendix/web-basics/SpaStatePreservationDemo.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

345 lines
6.7 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.
<!--
SpaStatePreservationDemo.vue
SPA vs MPA页面切换时状态是否保留的演示
-->
<template>
<div class="spa-state-demo">
<div class="header">
<div class="title">
页面切换时输入会不会丢
</div>
<div class="subtitle">
同样点击切换页面MPA 会像刷新一样清空SPA 会保留状态
</div>
</div>
<div class="mode-switch">
<button
class="mode"
:class="{ active: mode === 'mpa' }"
@click="switchMode('mpa')"
>
MPA整页刷新
</button>
<button
class="mode"
:class="{ active: mode === 'spa' }"
@click="switchMode('spa')"
>
SPA局部切换
</button>
<button
class="reset"
@click="resetAll"
>
重置
</button>
</div>
<div class="app">
<div class="nav">
<button
v-for="p in pages"
:key="p"
class="nav-btn"
:class="{ active: page === p }"
@click="go(p)"
>
{{ p }}
</button>
</div>
<div class="screen">
<div
v-if="loading"
class="loading"
>
加载中...
</div>
<div
v-else
class="content"
>
<div class="row">
当前页面<strong>{{ page }}</strong>
</div>
<div class="form">
<label>
备注模拟表单输入
<input
v-model="note"
type="text"
placeholder="输入点东西试试"
>
</label>
<div class="help">
提示切到别的页面再回来看看这段文字还在不在
</div>
</div>
<div class="row">
购物车数量模拟状态
<button
class="small"
@click="cart = Math.max(0, cart - 1)"
>
-
</button>
<strong class="num">{{ cart }}</strong>
<button
class="small"
@click="cart = cart + 1"
>
+
</button>
</div>
</div>
</div>
<div class="explain">
<div class="card">
<div class="label">
你现在看到的现象
</div>
<div class="value">
{{ explainText }}
</div>
</div>
<div class="card">
<div class="label">
背后的原因一句话
</div>
<div class="value">
{{ reasonText }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const pages = ['首页', '商品', '购物车']
const mode = ref('mpa')
const page = ref('首页')
const loading = ref(false)
// 模拟用户输入/页面状态
const note = ref('我想买两杯奶茶')
const cart = ref(1)
const switchMode = (next) => {
mode.value = next
// 切模式时也模拟一次“回到首页”
go('首页')
}
const resetAll = () => {
mode.value = 'mpa'
page.value = '首页'
note.value = '我想买两杯奶茶'
cart.value = 1
loading.value = false
}
const go = (nextPage) => {
loading.value = true
// MPA:切换 = 类似刷新,状态丢失
if (mode.value === 'mpa') {
note.value = ''
cart.value = 0
}
const delay = mode.value === 'mpa' ? 650 : 150
setTimeout(() => {
page.value = nextPage
loading.value = false
}, delay)
}
const explainText = computed(() =>
mode.value === 'mpa'
? 'MPA:切换页面时像刷新,输入和状态经常会丢'
: 'SPA:切换页面只换内容区域,输入和状态更容易保留'
)
const reasonText = computed(() =>
mode.value === 'mpa'
? '因为浏览器加载了“新的页面”,旧页面的内存状态会被清掉'
: '因为还是“同一个页面”,只是 JavaScript 把内容换了一下'
)
</script>
<style scoped>
.spa-state-demo {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.header {
margin-bottom: 1rem;
}
.title {
font-weight: 700;
font-size: 1.05rem;
}
.subtitle {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.mode-switch {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.mode {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
padding: 0.4rem 0.8rem;
border-radius: 999px;
font-size: 0.85rem;
cursor: pointer;
}
.mode.active {
border-color: #3b82f6;
color: #1d4ed8;
background: rgba(59, 130, 246, 0.12);
}
.reset {
border: none;
background: var(--vp-c-brand);
color: #fff;
padding: 0.4rem 0.8rem;
border-radius: 999px;
font-size: 0.85rem;
cursor: pointer;
}
.app {
border: 1px dashed var(--vp-c-divider);
border-radius: 12px;
background: var(--vp-c-bg);
padding: 0.75rem;
}
.nav {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.75rem;
}
.nav-btn {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.35rem 0.7rem;
border-radius: 6px;
font-size: 0.85rem;
cursor: pointer;
}
.nav-btn.active {
border-color: #22c55e;
background: rgba(34, 197, 94, 0.12);
color: #15803d;
}
.screen {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 0.9rem;
min-height: 120px;
}
.loading {
color: var(--vp-c-text-2);
display: flex;
align-items: center;
justify-content: center;
min-height: 100px;
}
.content .row {
margin-bottom: 0.75rem;
font-size: 0.95rem;
}
.form label {
display: flex;
flex-direction: column;
gap: 0.35rem;
font-size: 0.9rem;
}
.form input {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 6px;
padding: 0.45rem 0.6rem;
font-size: 0.9rem;
}
.help {
margin-top: 0.35rem;
color: var(--vp-c-text-2);
font-size: 0.85rem;
}
.small {
border: none;
background: rgba(99, 102, 241, 0.15);
color: #4338ca;
padding: 0.2rem 0.55rem;
border-radius: 6px;
cursor: pointer;
margin: 0 0.35rem;
}
.num {
display: inline-block;
min-width: 2ch;
text-align: center;
}
.explain {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.75rem;
margin-top: 0.9rem;
}
.card {
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
padding: 0.75rem;
background: var(--vp-c-bg-soft);
}
.label {
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.value {
margin-top: 0.25rem;
font-size: 0.9rem;
font-weight: 600;
line-height: 1.35;
}
</style>