This commit is contained in:
dichgrem
2025-10-31 10:15:06 +08:00
parent f0ed76db7c
commit 17ba3fd25e

586
vue7.html Normal file
View File

@@ -0,0 +1,586 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>4-4 购物车 · 添加图书</title>
<script src="https://unpkg.com/vue@3.3.4/dist/vue.global.js"></script>
<style>
:root {
--bg: #f6f8fa;
--card: #ffffff;
--muted: #6b7280;
--text: #111827;
--primary: #2563eb;
--primary-600: #1e40af;
--border: #e5e7eb;
--chip: #eef2ff;
--chip-text: #3730a3;
--danger: #dc2626;
--success: #059669;
--shadow: 0 10px 24px rgba(0, 0, 0, .06);
--radius: 14px;
}
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "PingFang SC", "Hiragino Sans GB";
color: var(--text);
background: radial-gradient(1200px 600px at 10% -10%, #eff6ff 0%, transparent 60%),
radial-gradient(1000px 500px at 110% 10%, #fef3c7 0%, transparent 60%),
var(--bg);
}
.container {
max-width: 1100px;
margin: 32px auto 48px;
padding: 0 16px;
}
.titlebar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
h1 {
margin: 0;
font-size: 28px;
letter-spacing: .4px;
display: flex;
align-items: center;
gap: 10px;
}
.badge {
font-size: 12px;
color: #fff;
background: var(--primary);
padding: 2px 8px;
border-radius: 999px;
box-shadow: var(--shadow);
}
/* One-column vertical stack */
.stack {
display: flex;
flex-direction: column;
gap: 16px;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.card h3 {
margin: 0 0 12px;
font-size: 18px;
letter-spacing: .3px;
}
.card-body {
padding: 18px;
}
.subtle {
font-size: 12px;
color: #4b5563;
margin-top: -4px;
}
/* Form */
.form-grid {
display: grid;
gap: 12px;
grid-template-columns: 120px 1fr;
align-items: center;
}
@media (max-width: 560px) {
.form-grid {
grid-template-columns: 1fr;
}
.form-grid label {
font-size: 13px;
}
}
.form-grid label {
color: #374151;
}
.form-grid input[type="text"],
.form-grid input[type="number"],
.form-grid input[type="month"],
.form-grid select {
width: 100%;
appearance: none;
border: 1px solid var(--border);
padding: 10px 12px;
border-radius: 10px;
outline: none;
background: #fff;
transition: box-shadow .2s, border-color .2s;
}
.form-grid input:focus,
.form-grid select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(37, 99, 235, .12);
}
.inline {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.radio,
.checkbox {
display: inline-flex;
align-items: center;
gap: 6px;
}
.actions {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
}
.btn {
appearance: none;
border: 1px solid transparent;
cursor: pointer;
padding: 10px 14px;
border-radius: 10px;
font-weight: 600;
transition: transform .06s ease, box-shadow .2s ease, background .2s ease;
}
.btn:active {
transform: translateY(1px);
}
.btn-primary {
background: var(--primary);
color: #fff;
box-shadow: 0 6px 18px rgba(37, 99, 235, .25);
}
.btn-primary:hover {
background: var(--primary-600);
}
.btn-ghost {
background: #fff;
border-color: var(--border);
}
.error {
color: var(--danger);
font-size: 13px;
}
/* List (vertical cards) */
.list {
display: flex;
flex-direction: column;
gap: 12px;
}
.row {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: center;
border: 1px solid var(--border);
border-radius: 12px;
background: #fff;
padding: 14px;
box-shadow: var(--shadow);
}
.row:hover {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, .10), var(--shadow);
}
.meta {
display: grid;
gap: 6px;
}
.title {
font-weight: 800;
line-height: 1.3;
}
.muted {
color: var(--muted);
font-size: 13px;
}
.chips {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.chip {
background: var(--chip);
color: var(--chip-text);
border: 1px solid #e0e7ff;
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
}
.right {
display: flex;
align-items: center;
gap: 12px;
}
.qty {
display: flex;
align-items: center;
gap: 8px;
}
.iconbtn {
width: 28px;
height: 28px;
border-radius: 8px;
line-height: 26px;
text-align: center;
border: 1px solid var(--border);
background: #fff;
cursor: pointer;
}
.iconbtn:hover {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, .12);
}
.price {
font-weight: 800;
}
.tag-on {
color: var(--success);
font-weight: 700;
}
.danger {
color: #fff;
background: var(--danger);
border-color: transparent;
}
.danger:hover {
filter: brightness(.95);
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-top: 14px;
padding: 12px 16px;
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.total {
font-size: 18px;
font-weight: 800;
}
.empty {
text-align: center;
color: var(--muted);
padding: 48px 16px;
}
</style>
</head>
<body>
<div id="app" class="container">
<div class="titlebar">
<h1>图书购物车 </h1>
</div>
<div class="stack">
<!-- 顶部:添加图书 -->
<div class="card">
<div class="card-body">
<h3>添加图书</h3>
<div class="form-grid" style="margin-top:12px;">
<label for="name">书名*</label>
<input id="name" v-model="form.name" placeholder="例如:《现代操作系统》" type="text" />
<label for="author">作者*</label>
<input id="author" v-model="form.author" placeholder="例如Andrew S. Tanenbaum" type="text" />
<label for="date">出版日期*</label>
<input id="date" v-model="form.date" type="month" />
<label for="price">价格*</label>
<input id="price" v-model.number="form.price" type="number" step="0.01" min="0" placeholder="例如88.00" />
<label for="num">数量*</label>
<input id="num" v-model.number="form.num" type="number" min="1" />
<label for="category">类别*</label>
<select id="category" v-model="form.category">
<option value="">请选择</option>
<option value="计算机基础">计算机基础</option>
<option value="编程语言">编程语言</option>
<option value="数据库">数据库</option>
<option value="计算机网络">计算机网络</option>
<option value="操作系统">操作系统</option>
<option value="软件工程">软件工程</option>
</select>
<label>格式*</label>
<div class="inline">
<label class="radio"><input type="radio" name="format" v-model="form.format" value="纸质书" /> 纸质书</label>
<label class="radio"><input type="radio" name="format" v-model="form.format" value="电子书" /> 电子书</label>
<label class="radio"><input type="radio" name="format" v-model="form.format" value="套装" /> 套装</label>
</div>
<label>标签</label>
<div class="inline">
<label class="checkbox"><input type="checkbox" v-model="form.tags" value="教材" /> 教材</label>
<label class="checkbox"><input type="checkbox" v-model="form.tags" value="经典" /> 经典</label>
<label class="checkbox"><input type="checkbox" v-model="form.tags" value="实例丰富" /> 实例丰富</label>
<label class="checkbox"><input type="checkbox" v-model="form.tags" value="考试推荐" /> 考试推荐</label>
</div>
<label>是否现货</label>
<div class="inline">
<label class="checkbox">
<input type="checkbox" v-model="form.inStock" :true-value="true" :false-value="false" />
有现货
</label>
</div>
<label for="language">语言</label>
<select id="language" v-model="form.language">
<option value="中文">中文</option>
<option value="英文">英文</option>
<option value="其他">其他</option>
</select>
<label for="isbn">ISBN</label>
<input id="isbn" v-model="form.isbn" placeholder="例如978-7-xxxx-xxxxx-x" type="text" />
</div>
<div class="actions">
<button class="btn btn-primary" @click="addBook">添加到列表</button>
<button class="btn btn-ghost" @click="resetForm">重置</button>
<span class="error" v-if="errorMsg">{{ errorMsg }}</span>
</div>
</div>
</div>
<!-- 底部:垂直卡片列表 -->
<div class="card" v-if="books.length">
<div class="card-body">
<h3>图书列表</h3>
<div class="list" style="margin-top:10px;">
<div class="row" v-for="item in books" :key="item.id">
<div class="meta">
<div class="title">{{ item.name }}</div>
<div class="muted">作者:{{ item.author }} | 类别:{{ item.category }} | 格式:{{ item.format }}</div>
<div class="muted">出版:{{ item.date }} | ISBN{{ item.isbn }} | 价格:¥{{ item.price.toFixed(2)
}} | 现货:<span :class="{'tag-on': item.inStock}">{{ item.inStock ? '是' : '否' }}</span></div>
<div class="chips">
<span class="chip" v-for="t in item.tags" :key="t">{{ t }}</span>
</div>
</div>
<div class="right">
<div class="qty">
<button class="iconbtn" title="增加" @click="inc(item)">+</button>
<strong>{{ item.num }}</strong>
<button class="iconbtn" title="减少" @click="dec(item)">-</button>
</div>
<button class="btn danger" @click="remove(item.id)">移除</button>
</div>
</div>
</div>
<div class="footer">
<div class="muted">共 {{ books.length }} 本书</div>
<div class="total">总价格:¥{{ sum.toFixed(2) }}</div>
</div>
</div>
</div>
<div class="empty card" v-else>
<div class="card-body">
<h3 style="margin:0 0 4px;">列表为空</h3>
<div class="muted">请先在上方表单添加图书</div>
</div>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
books: [
{ id: 1, name: "《JavaScript程序设计》", author: "张三", category: "编程语言", format: "纸质书", tags: ["教材", "经典"], inStock: true, language: "中文", isbn: "978-7-00000-000-1", date: "2022-09", price: 85.00, num: 1 },
{ id: 2, name: "《C语言基础》", author: "李四", category: "编程语言", format: "纸质书", tags: ["教材"], inStock: true, language: "中文", isbn: "978-7-00000-000-2", date: "2021-02", price: 59.00, num: 1 },
{ id: 3, name: "《Java高级语言编程》", author: "王五", category: "编程语言", format: "电子书", tags: ["经典", "实例丰富"], inStock: true, language: "中文", isbn: "978-7-00000-000-3", date: "2022-10", price: 39.00, num: 1 },
{ id: 4, name: "《数据库原理》", author: "赵六", category: "数据库", format: "纸质书", tags: ["教材", "考试推荐"], inStock: false, language: "中文", isbn: "978-7-00000-000-4", date: "2023-03", price: 128.00, num: 1 },
{ id: 5, name: "《计算机网络》", author: "周七", category: "计算机网络", format: "纸质书", tags: ["经典"], inStock: true, language: "中文", isbn: "978-7-00000-000-5", date: "2022-08", price: 88.00, num: 1 },
],
form: {
name: "",
author: "",
date: "",
price: null,
num: 1,
category: "",
format: "",
tags: [],
inStock: false,
language: "中文",
isbn: ""
},
errorMsg: ""
};
},
computed: {
// 计算总价
sum() {
return this.books.reduce((total, book) => {
return total + book.price * book.num;
}, 0);
},
},
methods: {
inc(item) {
item.num++;
},
dec(item) {
if (item.num > 1)
item.num--;
},
remove(id) {
this.books = this.books.filter(b => b.id !== id);
},
// 表单验证
validateForm() {
if (!this.form.name.trim()) {
this.errorMsg = "书名不能为空";
return false;
}
if (!this.form.author.trim()) {
this.errorMsg = "作者不能为空";
return false;
}
if (!this.form.date) {
this.errorMsg = "出版日期不能为空";
return false;
}
if (!this.form.price || this.form.price <= 0) {
this.errorMsg = "价格必须大于0";
return false;
}
if (!this.form.num || this.form.num < 1) {
this.errorMsg = "数量必须至少为1";
return false;
}
if (!this.form.category) {
this.errorMsg = "请选择类别";
return false;
}
if (!this.form.format) {
this.errorMsg = "请选择格式";
return false;
}
this.errorMsg = "";
return true;
},
// 获取下一个ID
nextId() {
if (this.books.length === 0) return 1;
return Math.max(...this.books.map(b => b.id)) + 1;
},
// 添加图书到列表
addBook() {
if (!this.validateForm()) {
return;
}
const newBook = {
id: this.nextId(),
name: this.form.name,
author: this.form.author,
date: this.form.date,
price: this.form.price,
num: this.form.num,
category: this.form.category,
format: this.form.format,
tags: [...this.form.tags],
inStock: this.form.inStock,
language: this.form.language,
isbn: this.form.isbn
};
this.books.push(newBook);
this.resetForm();
},
// 重置表单
resetForm() {
this.form = {
name: "",
author: "",
date: "",
price: null,
num: 1,
category: "",
format: "",
tags: [],
inStock: false,
language: "中文",
isbn: ""
};
this.errorMsg = "";
}
}
}).mount("#app");
</script>
</body>
</html>