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

439 lines
9.4 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.
<!--
JQueryVsStateDemo.vue
用可视化方式解释jQuery = 手动改 DOM框架 = State 自动同步
-->
<template>
<div class="jq-demo">
<div class="header">
<div class="title">
什么是 jQuery购物车数量秒懂
</div>
<div class="subtitle">
左边 jQuery 一样手动改页面容易漏右边 Vue/React
一样只改状态
</div>
</div>
<div class="panes">
<!-- jQuery-like -->
<div class="pane">
<div class="pane-title">
jQuery 思路到处改 DOM
</div>
<div class="mock-app">
<div class="topbar">
<span>🛒 角标</span>
<span
class="badge"
:class="{ wrong: jqBadgeWrong }"
>{{
jqBadge
}}</span>
</div>
<div class="content">
<div class="row">
购物车页数量
<span
class="num"
:class="{ wrong: jqPageWrong }"
>{{
jqPage
}}</span>
</div>
<div class="row">
结算按钮
<button class="checkout">
去结算 ({{ jqButtonLabel }})
</button>
</div>
</div>
</div>
<div class="controls">
<div class="control-title">
模拟你写的命令
</div>
<div class="btns">
<button @click="jqIncreaseData">
数据 +1但还没改页面
</button>
<button @click="jqUpdateBadge">
改角标
</button>
<button @click="jqUpdateCartPage">
改购物车页
</button>
<button @click="jqUpdateCheckoutButton">
改结算按钮
</button>
</div>
<div
class="hint"
:class="{ danger: jqInconsistent }"
>
{{ jqHint }}
</div>
<div class="log">
<div class="log-title">
命令日志
</div>
<div
v-if="jqLogs.length === 0"
class="log-empty"
>
还没有操作
</div>
<div
v-else
class="log-list"
>
<div
v-for="(l, idx) in jqLogs"
:key="idx"
class="log-item"
>
{{ l }}
</div>
</div>
</div>
</div>
</div>
<!-- State-driven -->
<div class="pane">
<div class="pane-title">
Vue/React 思路只改 State
</div>
<div class="mock-app">
<div class="topbar">
<span>🛒 角标</span>
<span class="badge">{{ state }}</span>
</div>
<div class="content">
<div class="row">
购物车页数量 <span class="num">{{ state }}</span>
</div>
<div class="row">
结算按钮
<button class="checkout">
去结算 ({{ state }} )
</button>
</div>
</div>
</div>
<div class="controls">
<div class="control-title">
你只需要做一件事
</div>
<div class="btns">
<button
class="primary"
@click="state = state + 1"
>
state +1
</button>
<button
class="secondary"
@click="resetAll"
>
重置
</button>
</div>
<div class="hint ok">
State 变了界面三处会自动同步不需要你手动找 DOM 去改
</div>
<div class="mini">
<div class="mini-title">
这里的两个新词
</div>
<div class="mini-item">
<strong>DOM</strong>浏览器里的页面结构按钮/文字/图片都在里面
</div>
<div class="mini-item">
<strong>State</strong>页面的数据比如购物车数量
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const state = ref(1)
// jQuery side: "real data" + "DOM" values displayed at multiple places
const jqData = ref(1)
const jqBadge = ref(1)
const jqPage = ref(1)
const jqButtonLabel = ref('1 件')
const jqLogs = ref([])
const log = (txt) => {
jqLogs.value.unshift(
`${new Date().toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})} - ${txt}`
)
jqLogs.value = jqLogs.value.slice(0, 8)
}
const jqIncreaseData = () => {
jqData.value += 1
log(`数据 +1(现在真实数据 = ${jqData.value}`)
}
const jqUpdateBadge = () => {
jqBadge.value = jqData.value
log(`更新角标 DOM = ${jqBadge.value}`)
}
const jqUpdateCartPage = () => {
jqPage.value = jqData.value
log(`更新购物车页 DOM = ${jqPage.value}`)
}
const jqUpdateCheckoutButton = () => {
jqButtonLabel.value = `${jqData.value}`
log(`更新结算按钮 DOM = ${jqButtonLabel.value}`)
}
const jqInconsistent = computed(() => {
return (
jqBadge.value !== jqData.value ||
jqPage.value !== jqData.value ||
jqButtonLabel.value !== `${jqData.value}`
)
})
const jqBadgeWrong = computed(() => jqBadge.value !== jqData.value)
const jqPageWrong = computed(() => jqPage.value !== jqData.value)
const jqHint = computed(() => {
if (!jqInconsistent.value) return '✅ 三处显示一致(恭喜你都改对了)'
return '⚠️ 数据和页面不一致:你可能漏更新了某一处 DOM(真实项目里这就是 bug)'
})
const resetAll = () => {
state.value = 1
jqData.value = 1
jqBadge.value = 1
jqPage.value = 1
jqButtonLabel.value = '1 件'
jqLogs.value = []
}
</script>
<style scoped>
.jq-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;
}
.panes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1rem;
}
.pane {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 12px;
padding: 0.75rem;
}
.pane-title {
font-weight: 700;
font-size: 0.95rem;
margin-bottom: 0.75rem;
}
.mock-app {
border: 1px dashed var(--vp-c-divider);
border-radius: 12px;
overflow: hidden;
}
.topbar {
padding: 0.6rem 0.75rem;
background: var(--vp-c-bg-soft);
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2ch;
padding: 0.1rem 0.45rem;
border-radius: 999px;
background: rgba(59, 130, 246, 0.15);
color: #1d4ed8;
font-weight: 700;
}
.content {
padding: 0.75rem;
}
.row {
margin-bottom: 0.6rem;
font-size: 0.92rem;
}
.num {
font-weight: 800;
padding: 0.05rem 0.25rem;
border-radius: 6px;
background: rgba(34, 197, 94, 0.12);
color: #15803d;
}
.checkout {
border: none;
background: var(--vp-c-brand);
color: #fff;
padding: 0.4rem 0.8rem;
border-radius: 10px;
font-size: 0.85rem;
}
.controls {
margin-top: 0.9rem;
}
.control-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
}
.btns {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.btns button {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.35rem 0.65rem;
border-radius: 10px;
cursor: pointer;
font-size: 0.85rem;
}
.btns button.primary {
border: none;
background: #22c55e;
color: #fff;
}
.btns button.secondary {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
}
.hint {
margin-top: 0.65rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
border: 1px dashed var(--vp-c-divider);
background: var(--vp-c-bg-soft);
padding: 0.6rem 0.7rem;
border-radius: 10px;
}
.hint.danger {
color: #b91c1c;
border-color: rgba(239, 68, 68, 0.4);
background: rgba(239, 68, 68, 0.08);
}
.hint.ok {
color: #166534;
border-color: rgba(34, 197, 94, 0.35);
background: rgba(34, 197, 94, 0.08);
}
.wrong {
background: rgba(239, 68, 68, 0.12) !important;
color: #b91c1c !important;
}
.log {
margin-top: 0.75rem;
}
.log-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.35rem;
}
.log-empty {
color: var(--vp-c-text-3);
font-size: 0.85rem;
}
.log-list {
display: grid;
gap: 0.25rem;
}
.log-item {
font-family: var(--vp-font-family-mono);
font-size: 0.78rem;
color: var(--vp-c-text-2);
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 6px;
padding: 0.35rem 0.5rem;
}
.mini {
margin-top: 0.75rem;
border-top: 1px dashed var(--vp-c-divider);
padding-top: 0.75rem;
}
.mini-title {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.4rem;
}
.mini-item {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.25rem;
}
</style>