Files
Vue/test/vue7.html
2025-12-01 10:11:46 +08:00

586 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<!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>