Vue笔记(六)

一、路由设计配置--一级路由配置

在路由文件(一般是 router/index.js )里定义路由对象数组,每个对象包含 path (路由路径,如 '/' 代表首页)、 name (路由名称,方便代码引用)、 component (对应路由的组件 )。配置时要保证路径唯一,组件引入路径正确,否则会导致路由错误 

二、登录页静态布局&图形验证码功能

(一)登录页静态布局

通过HTML构建页面结构,定义登录表单、输入框、按钮等元素。运用CSS进行样式设计,调整元素的大小、颜色、位置和间距,让页面布局合理且美观,提升用户视觉体验,确保各元素在不同屏幕尺寸下显示正常。

(二)图形验证码功能

在Vue项目中,借助相关插件或自定义代码生成图形验证码。用户输入验证码后,前端进行初步格式校验,然后与后端生成的验证码比对,验证用户身份,防止恶意登录。还会涉及验证码刷新功能,若用户看不清,可点击刷新获取新的验证码。 

三、api接口模块

(一)封装图片验证码接口

1.接口封装目的:在Vue项目开发中,将图片验证码接口进行封装,能够让代码结构更清晰,增强代码的复用性,便于后续的维护和管理。同时,这也有助于提高前端与后端交互的效率,保障数据传输的准确性。
 
2.实现方式:通常会使用Axios库来实现接口封装。首先要安装Axios,然后在项目中引入。接着,根据后端提供的接口文档,配置请求的URL、请求方法(一般为GET请求来获取图片验证码)等参数。例如,创建一个专门的API文件,在其中定义获取图片验证码的函数,函数内部使用Axios发送请求,并对响应结果进行处理 。
 
3.注意事项:封装过程中要确保接口请求的安全性,对可能出现的错误(如网络异常、接口响应错误等)进行合理处理。同时,要注意接口的版本兼容性,若后端接口发生变化,及时调整前端的封装代码,以保证功能正常运

(二)实现登录

1.接口封装要点:使用Axios等工具构建登录接口。在项目中创建专门的API文件,定义登录函数。配置请求URL、方法(一般是POST),将用户输入的账号、密码作为参数传递。比如 axios.post('/login', { username, password }) ,提升代码复用性与维护性。
 
2.登录功能实现流程:用户在登录页输入信息,触发登录事件调用封装接口。前端收集数据并发送请求,后端验证账号密码。若正确,返回包含用户信息或token的响应;前端据此存储信息,进行页面跳转(如跳转到首页);若错误,前端接收错误提示,展示给用户。
 
3.安全与优化措施:对密码进行加密处理,防止传输中泄露。在前端对用户输入进行格式校验,减少无效请求。

<template><div><input v-model="username" placeholder="用户名"><input v-model="password" type="password" placeholder="密码"><button @click="login">登录</button></div>
</template><script>
import axios from 'axios';export default {data() {return {username: '',password: ''};},methods: {async login() {try {const response = await axios.post('/login', {username: this.username,password: this.password});if (response.data.success) {// 登录成功,可进行页面跳转、存储用户信息等操作console.log('登录成功');} else {console.log('登录失败', response.data.message);}} catch (error) {console.error('登录请求出错', error);}}}
};
</script>

四、toast

1.作用:toast轻提示用于在不中断用户操作的情况下,短暂展示提示信息,如操作结果反馈、系统通知等,能有效提升用户体验。
2.在登录、注册、表单提交等功能模块中广泛应用。登录成功时,显示“登录成功”的toast提示;表单提交失败时,提示“提交失败

五、短信验证功能

1.获取手机号:在前端页面利用表单组件收集用户输入的手机号,通过Vue的双向数据绑定实时获取和更新数据。
 
2. 发送请求获取验证码:使用Axios等工具将手机号发送到后端。后端接收到请求后,生成验证码并借助短信平台发送给用户手机。
 
3.倒计时与校验:前端设置倒计时,防止用户频繁获取验证码。用户收到验证码后输入,前端对其进行格式校验,再发送到后端与存储

六、响应拦截式--统一处理错误

1.概念:响应拦截器是在前端请求获取到后端响应后,在数据到达具体业务逻辑代码前,对响应数据进行统一处理的机制。在Vue项目中,通常借助Axios库实现,能有效提升代码的健壮性和用户体验。
 
2.优势:集中管理错误处理逻辑,避免在各个请求处理处重复编写错误处理代码,使代码更简洁、易维护。
 
3.在项目中配置Axios的响应拦截器,通过判断响应状态码或响应数据中的错误标识,执行不同的错误处理逻辑。例如,当状态码为404时,弹出“页面未找到”的提示;若为500,提示“服务器内部错误” 。还可以针对特定业务错误,根据响应数据中的自定义错误信息,给出相应的提示内容。

七、将token权证信息存入vuex

1.token作为用户身份验证和授权的关键凭证,在前后端交互中用于确认用户身份。通过验证token,后端能判断用户是否有权限访问特定资源,保障系统安全。
 
2.Vuex是Vue的状态管理工具,将token存入Vuex,能在整个Vue应用内集中管理和共享token信息。各组件可方便获取token,避免重复传递数据,提高数据管理效率,优化组件间通信。
 
3.登录成功后,从后端响应获取token;在Vuex的 store.js 中,定义 state 存储token,如 state: { token: null }  ;利用 mutations 方法修改 state 中的token值,如 SET_TOKEN(state, token) { state.token = token } ;在登录组件内调用 mutations 方法,将token存入Vuex,实现数据全局管理

八、storage存储模块--vuex持久化处理

1.Vuex持久化的必要性:Vuex存储在内存中,页面刷新数据丢失。在实际应用里,如用户登录状态(token等信息)需要保持,所以要进行持久化处理,提升用户体验。
 
2.storage存储模块应用:借助浏览器的 localStorage 或 sessionStorage 。 localStorage 存储的数据长期有效, sessionStorage 在会话结束(关闭页面)时清除。将Vuex中的数据,如登录信息、用户设置等,转换为字符串存入storage,在页面加载时再读取并还原到Vuex 。
 
3.安装 vuex-persist 插件简化操作。配置插件时,指定要持久化的状态模块、存储方式(如 localStorage )。在 store.js 中引入并使用插件,完成后刷新页面,Vuex数据会从storage重新加载,实现数据持久化 。

九、添加请求loading效果

1.当用户发起网络请求时,由于网络延迟或服务器响应时间的不确定性,页面可能会出现短暂的无响应状态。添加loading效果能让用户直观感知到系统正在处理请求,提升用户体验,避免用户因等待而重复操作。
 
2.借助拦截器(如Axios的请求和响应拦截器)来控制loading效果的显示与隐藏。在请求发送前,设置loading状态为true,触发显示loading动画;请求完成后,无论成功或失败,将loading状态设为false,隐藏loading动画。

安装Axios:如果项目尚未安装Axios,通过npm或yarn进行安装。
bash
  
npm install axios
# 或
yarn add axios
 

 

javascriptimport axios from 'axios';
import Vue from 'vue';// 创建Axios实例
const service = axios.create({baseURL: process.env.VUE_APP_BASE_API, // 根据实际情况配置基础URLtimeout: 5000 // 请求超时时间
});// 请求拦截器
service.interceptors.request.use(config => {// 在请求发送前,设置loading状态为true,可在Vuex或组件data中定义loading状态Vue.$store.commit('SET_LOADING', true); // 假设在Vuex中管理loading状态,需先配置好Vuexreturn config;},error => {return Promise.reject(error);}
);// 响应拦截器
service.interceptors.response.use(response => {// 请求成功后,设置loading状态为falseVue.$store.commit('SET_LOADING', false);return response;},error => {// 请求失败时,同样设置loading状态为falseVue.$store.commit('SET_LOADING', false);return Promise.reject(error);}
);export default service;

在组件中使用并显示loading效果:在需要发起请求的组件中,假设使用Vuex管理loading状态。

html<template><div><button @click="fetchData">获取数据</button><!-- 根据loading状态显示loading效果,这里以简单的加载提示为例 --><div v-if="loading" class="loading">Loading...</div> </div>
</template><script>
import api from '@/api'; // 引入配置好的Axios实例
import { mapState } from 'vuex';export default {computed: {...mapState(['loading']) // 从Vuex中获取loading状态},methods: {async fetchData() {try {const response = await api.get('/your-api-url');console.log(response.data);} catch (error) {console.error(error);}}}
};
</script><style scoped>
.loading {margin-top: 10px;color: gray;
}
</style>

 

十、全局前置导航守卫

1.在Vue Router中,全局前置导航守卫是一种在路由切换前触发的函数,能对每次路由跳转进行全局控制,决定是否允许跳转、重定向到其他页面等。它在路由配置和应用逻辑间起到关键作用,增强了应用的安全性和交互性。
 
2.当用户尝试进行路由切换时,全局前置导航守卫函数会首先被调用。函数接收 to (即将进入的目标路由对象)、 from (当前离开的路由对象)和 next (用于控制路由跳转的函数)作为参数。根据业务逻辑判断是否调用 next() 允许跳转,或调用 next('/login') 等进行重定向。若不调用 next ,路由切换会被阻塞。

javascriptimport Vue from 'vue';
import Router from 'vue-router';
import Home from '@/views/Home.vue';
import Login from '@/views/Login.vue';Vue.use(Router);const router = new Router({routes: [{path: '/',name: 'Home',component: Home,meta: { requiresAuth: true } // 标记该路由需要登录权限},{path: '/login',name: 'Login',component: Login}]
});// 全局前置导航守卫
router.beforeEach((to, from, next) => {// 假设从Vuex中获取用户登录状态,需提前配置好Vuexconst isLoggedIn = Vue.prototype.$store.getters.isLoggedIn; if (to.matched.some(record => record.meta.requiresAuth)) {// 如果目标路由需要登录权限if (isLoggedIn) {// 用户已登录,允许跳转next(); } else {// 用户未登录,重定向到登录页next('/login'); }} else {// 目标路由不需要登录权限,直接允许跳转next(); }
});export default router;

十一、首页--静态结构准备&动态渲染

1.使用HTML构建首页的基本框架,包括页面布局的划分,如头部、主体内容区、侧边栏、底部等部分。运用CSS对各个部分进行样式设计,设置元素的大小、颜色、字体、边距、对齐方式等样式属性,让页面具备初步的视觉效果,符合设计稿要求。
 
2.动态渲染:在Vue环境下,利用Vue的响应式原理和数据绑定机制实现动态渲染。通过 v - for 指令遍历数据数组,为列表类数据生成对应的DOM元素;使用 v - if 或 v - show 指令根据数据状态控制元素的显示与隐藏;将数据绑定到HTML元素的属性或文本内容上,实现数据驱动视图更新。通常会在组件的 created 或 mounted 生命周期钩子函数中发起数据请求,获取后端数据后进行动态渲染。

html<template><div class="home"><!-- 头部 --><header class="header"><h1>网站首页</h1></header><!-- 主体内容区 --><main class="main"><!-- 动态渲染列表 --><ul class="article - list"><li v - for="article in articles" :key="article.id" class="article - item"><h2>{{ article.title }}</h2><p>{{ article.content }}</p></li></ul></main><!-- 底部 --><footer class="footer"><p>版权所有 © 2024</p></footer></div>
</template><style scoped>
.header {background - color: #333;color: white;text - align: center;padding: 20px;
}.main {padding: 20px;
}.article - list {list - style: none;padding: 0;
}.article - item {border: 1px solid #ccc;padding: 10px;margin - bottom: 10px;
}.footer {background - color: #333;color: white;text - align: center;padding: 10px;margin - top: 20px;
}
</style>Vue逻辑代码(在 Home.vue 的 <script> 标签内)javascriptexport default {data() {return {articles: []};},created() {// 模拟从后端获取数据,实际开发中使用Axios等库发送请求this.fetchArticles();},methods: {async fetchArticles() {// 假设后端返回的数据结构如下const responseData = [{ id: 1, title: '文章1标题', content: '文章1内容' },{ id: 2, title: '文章2标题', content: '文章2内容' }];this.articles = responseData;}}
};

十二、搜索历史管理

1.搜索历史管理在应用中起着提升用户体验的关键作用。它能帮助用户快速找回之前的搜索内容,减少重复输入,提高搜索效率。对于开发人员而言,需要考虑数据存储、展示和更新等多方面的问题。
 
2.数据存储:通常利用浏览器的 localStorage 或 sessionStorage 来存储搜索历史。 localStorage 存储的数据长期有效,关闭浏览器后数据依然存在; sessionStorage 则在会话期间有效,关闭页面后数据消失。将搜索历史以数组形式存储,每个搜索关键词作为数组的一个元素。
 
3.在页面上展示搜索历史列表,用户可以点击历史记录进行快速搜索。提供删除功能,用户能手动删除单个或全部搜索历史,保证搜索历史的时效性和相关性。
 
4.更新逻辑:每次用户进行新的搜索时,检查搜索关键词是否已存在于历史记录中。若存在,将其移至数组头部,保持最近搜索的记录在最前面;若不存在,将新关键词添加到数组头部,并判断数组长度,超过一定数量(如10条)时,删除最后一个元素,以控制历史记录数量。

5. 

html<template><div><input v-model="searchQuery" @input="handleSearch" placeholder="搜索"><ul><li v-for="(history, index) in searchHistory" :key="index" @click="searchByHistory(history)">{{ history }}<button @click.stop="deleteHistory(index)">删除</button></li></ul><button @click="clearAllHistory">清空历史</button></div>
</template><script>
export default {data() {return {searchQuery: '',searchHistory: []};},created() {const storedHistory = localStorage.getItem('searchHistory');if (storedHistory) {this.searchHistory = JSON.parse(storedHistory);}},methods: {handleSearch() {if (this.searchQuery.trim() === '') return;if (this.searchHistory.includes(this.searchQuery)) {this.searchHistory.splice(this.searchHistory.indexOf(this.searchQuery), 1);}this.searchHistory.unshift(this.searchQuery);if (this.searchHistory.length > 10) {this.searchHistory.pop();}localStorage.setItem('searchHistory', JSON.stringify(this.searchHistory));// 这里可添加实际搜索逻辑,如调用搜索接口},searchByHistory(history) {this.searchQuery = history;// 这里可添加实际搜索逻辑,如调用搜索接口},deleteHistory(index) {this.searchHistory.splice(index, 1);localStorage.setItem('searchHistory', JSON.stringify(this.searchHistory));},clearAllHistory() {this.searchHistory = [];localStorage.removeItem('searchHistory');}}
};
</script>

十三、搜索列表页--静态布局与渲染

(一)静态布局

搜索列表页的静态布局需构建清晰的页面结构。顶部通常设置搜索框,方便用户进行新的搜索操作。中间主体部分用于展示搜索结果列表,每个结果项应包含关键信息,如标题、简介、图片(若有)等。底部可能添加分页导航,以便用户浏览多页搜索结果。利用HTML和CSS实现布局搭建,通过CSS设置元素样式,包括字体、颜色、间距、背景等,确保页面美观且符合交互逻辑。

(二)动态渲染

在Vue环境下,通过数据绑定和指令实现动态渲染。使用 v - for 指令遍历搜索结果数组,为每个结果生成对应的DOM元素。将数据绑定到HTML元素的属性或文本内容上,如 { { item.title }} 显示标题。通常在组件的生命周期钩子函数(如 created 或 mounted )中发起请求获取搜索结果数据,然后更新页面显示。

html<template><div class="search - list - page"><!-- 搜索框 --><input v - model="searchQuery" placeholder="输入关键词搜索" @input="search"><!-- 搜索结果列表 --><ul class="result - list"><li v - for="(item, index) in searchResults" :key="index" class="result - item"><img v - if="item.imageUrl" :src="item.imageUrl" alt="结果图片" class="result - image"><div class="result - content"><h3 class="result - title">{{ item.title }}</h3><p class="result - desc">{{ item.description }}</p></div></li></ul><!-- 分页导航 --><div class="pagination"><button @click="prevPage" :disabled="currentPage === 1">上一页</button><span>{{ currentPage }}/{{ totalPages }}</span><button @click="nextPage" :disabled="currentPage === totalPages">下一页</button></div></div>
</template><script>
export default {data() {return {searchQuery: '',searchResults: [],currentPage: 1,totalPages: 1,perPage: 10};},methods: {async search() {// 模拟请求搜索结果,实际需替换为真实API请求const response = await fetch(`/api/search?query=${this.searchQuery}&page=${this.currentPage}&limit=${this.perPage}`);const data = await response.json();this.searchResults = data.results;this.totalPages = data.totalPages;},prevPage() {if (this.currentPage > 1) {this.currentPage--;this.search();}},nextPage() {if (this.currentPage < this.totalPages) {this.currentPage++;this.search();}}},created() {this.search();}
};
</script><style scoped>
.search - list - page {padding: 20px;
}input {width: 300px;padding: 10px;margin - bottom: 20px;
}.result - list {list - style: none;padding: 0;
}.result - item {display: flex;align - items: start;margin - bottom: 20px;border: 1px solid #ccc;padding: 10px;
}.result - image {width: 80px;height: 80px;object - fit: cover;margin - right: 10px;
}.result - content {flex: 1;
}.result - title {margin - top: 0;margin - bottom: 5px;
}.result - desc {margin: 0;color: #666;
}.pagination {margin - top: 20px;
}button {padding: 8px 16px;margin: 0 5px;
}
</style>

十四、商品详情页--静态结构和动态渲染

(一)静态结构搭建

商品详情页的静态结构要全面展示商品信息。顶部通常是商品图片展示区域,可使用轮播图形式展示多图;接着是商品基本信息,如名称、价格、销量等;中间部分为商品详情描述,可能包含文字介绍、产品参数表格;底部设置购买按钮、评论区入口等交互元素。利用HTML构建页面框架,通过CSS进行样式设计,调整各元素的布局、字体、颜色和间距,确保页面布局合理、美观且符合用户浏览习惯。

(二)动态渲染实现

基于Vue框架,利用数据绑定和生命周期函数实现动态渲染。在组件的 created 或 mounted 钩子函数中,通过Axios等工具向后端发送请求获取商品数据。使用 v - for 指令遍历数组类型的数据(如多图数组)进行图片轮播展示;通过插值表达式(如 { { product.name }} )将商品数据绑定到对应的HTML元素,实现数据实时更新页面。若涉及复杂数据结构,如对象嵌套,要正确获取和绑定数据,保证页面展示准确。

<template><div class="product - detail - page"><!-- 商品图片区域 --><div class="product - images"><div v - for="(image, index) in product.images" :key="index" class="image - item"><img :src="image" alt="商品图片"></div></div><!-- 商品基本信息区域 --><div class="product - info"><h1>{{ product.name }}</h1><p>价格: {{ product.price }}元</p><p>销量: {{ product.sales }}</p></div><!-- 商品详情描述区域 --><div class="product - description"><h2>商品详情</h2><p v - html="product.description"></p></div><!-- 购买按钮和其他交互区域 --><div class="action - area"><button @click="addToCart">加入购物车</button><button @click="goToComments">查看评论</button></div></div>
</template><script>
import axios from 'axios';export default {data() {return {product: {}};},created() {this.fetchProductDetails();},methods: {async fetchProductDetails() {try {const response = await axios.get('/api/products/1');// 假设商品ID为1,实际根据路由参数获取this.product = response.data;} catch (error) {console.error('获取商品详情失败', error);}},addToCart() {// 加入购物车逻辑,如调用后端接口添加商品到购物车console.log('已加入购物车');},goToComments() {// 跳转到评论页逻辑,如使用Vue Router进行路由跳转console.log('跳转到评论页');}}
};
</script><style scoped>
.product - detail - page {padding: 20px;
}.product - images {display: flex;overflow - x: scroll;
}.image - item {margin - right: 10px;
}.image - item img {width: 200px;height: 200px;object - fit: cover;
}.product - info {margin - top: 20px;
}.product - description {margin - top: 20px;
}.action - area {margin - top: 20px;
}button {padding: 10px 20px;margin - right: 10px;
}
</style>

十五、加入购物车

(一)弹层显示

<template><div><!-- 商品详情页,简化示例 --><h1>商品名称: {{ product.name }}</h1><button @click="addToCart">加入购物车</button><!-- 加入购物车弹层 --><div v - if="isCartPopupVisible" class="cart - popup"><div class="cart - popup - content"><p>商品已加入购物车</p><p>商品名称: {{ product.name }}</p><p>数量: 1</p><button @click="goToCart">查看购物车</button><button @click="continueShopping">继续购物</button></div></div></div>
</template><script>
import axios from 'axios';export default {data() {return {product: { name: '示例商品' },// 实际从商品详情页获取isCartPopupVisible: false};},methods: {async addToCart() {try {// 模拟加入购物车的API请求await axios.post('/api/cart/add', { productId: 1 });this.isCartPopupVisible = true;// 自动关闭弹层,3秒后执行setTimeout(() => {this.isCartPopupVisible = false;}, 3000);} catch (error) {console.error('加入购物车失败', error);}},goToCart() {this.isCartPopupVisible = false;// 实际使用Vue Router进行路由跳转至购物车页面console.log('跳转到购物车页面');},continueShopping() {this.isCartPopupVisible = false;console.log('继续购物');}}
};
</script><style scoped>
.cart - popup {position: fixed;top: 20px;right: 20px;background - color: rgba(0, 0, 0, 0.8);color: white;padding: 20px;border - radius: 5px;box - shadow: 0 0 5px rgba(0, 0, 0, 0.3);z - index: 1000;animation: fadeIn 0.3s ease - in - out;
}@keyframes fadeIn {from {opacity: 0;transform: translateY(-20px);}to {opacity: 1;transform: translateY(0);}
}.cart - popup - content {text - align: center;
}button {margin: 10px;padding: 10px 20px;background - color: #007BFF;color: white;border: none;border - radius: 5px;cursor: pointer;
}
</style>

(二)数字框基本封装

<template><div class="quantity - box"><button @click="decreaseQuantity">-</button><inputv - model.number="quantity"type="number":min="minQuantity":max="maxQuantity"@input="handleInput"/><button @click="increaseQuantity">+</button></div>
</template><script>
export default {props: {// 初始数量initialQuantity: {type: Number,default: 1},// 最小数量minQuantity: {type: Number,default: 1},// 最大数量maxQuantity: {type: Number,default: Infinity}},data() {return {quantity: this.initialQuantity};},methods: {decreaseQuantity() {if (this.quantity > this.minQuantity) {this.quantity--;this.$emit('quantity - change', this.quantity);}},increaseQuantity() {if (this.quantity < this.maxQuantity) {this.quantity++;this.$emit('quantity - change', this.quantity);}},handleInput(event) {const inputValue = parseInt(event.target.value, 10);if (isNaN(inputValue) || inputValue < this.minQuantity) {this.quantity = this.minQuantity;} else if (inputValue > this.maxQuantity) {this.quantity = this.maxQuantity;} else {this.quantity = inputValue;}this.$emit('quantity - change', this.quantity);}}
};
</script><style scoped>
.quantity - box {display: flex;align - items: center;
}.quantity - box button {padding: 5px 10px;border: 1px solid #ccc;background - color: #f9f9f9;cursor: pointer;
}.quantity - box input {width: 50px;padding: 5px;text - align: center;border: 1px solid #ccc;margin: 0 5px;
}
</style>

(三)判断token登录提示

<template><div><button @click="addToCart">加入购物车</button></div>
</template><script>
import axios from 'axios';
export default {methods: {addToCart() {const token = localStorage.getItem('token');if (!token) {// 使用window.alert简单提示,实际可使用更美观的自定义弹窗组件const confirmLogin = window.confirm('您还未登录,请先登录');if (confirmLogin) {// 假设使用Vue Router,跳转到登录页面this.$router.push('/login');}return;}// 模拟商品数据,实际从当前商品详情获取const product = { id: 1, name: '示例商品' };this.addProductToCart(product);},async addProductToCart(product) {try {const response = await axios.post('/api/cart/add', {productId: product.id});if (response.data.success) {// 使用window.alert简单提示,实际可优化提示方式window.alert('加入购物车成功');} else {window.alert('加入购物车失败');}} catch (error) {console.error('加入购物车请求出错', error);window.alert('加入购物车失败,请稍后重试');}}}
};
</script>

十六、购物车

(一)基本静态布局

<template><div class="shopping - cart - page"><!-- 标题栏 --><header class="cart - header"><h1>购物车</h1></header><!-- 商品列表 --><div class="cart - items"><!-- 模拟商品项,实际数据从后端获取并动态渲染 --><div v - for="(item, index) in cartItems" :key="index" class="cart - item"><img :src="item.imageUrl" alt="商品图片" class="cart - item - image"><div class="cart - item - info"><p class="cart - item - name">{{ item.name }}</p><p class="cart - item - price">价格: {{ item.price }}元</p><div class="quantity - control"><span>数量: {{ item.quantity }}</span><!-- 预留数量调整按钮,后续添加交互逻辑 --><button>-</button><button>+</button></div></div><button class="delete - button" @click="deleteItem(index)">删除</button></div></div><!-- 底部信息 --><footer class="cart - footer"><div class="total - price"><p>总价: {{ totalPrice }}元</p></div><button class="checkout - button">结算</button></footer></div>
</template><script>
export default {data() {return {cartItems: [{imageUrl: 'example1.jpg',name: '商品1',price: 100,quantity: 1},{imageUrl: 'example2.jpg',name: '商品2',price: 200,quantity: 2}]};},computed: {totalPrice() {return this.cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);}},methods: {deleteItem(index) {this.cartItems.splice(index, 1);}}
};
</script><style scoped>
.shopping - cart - page {padding: 20px;
}.cart - header {text - align: center;margin - bottom: 20px;
}.cart - items {list - style: none;padding: 0;
}.cart - item {display: flex;align - items: center;border: 1px solid #ccc;padding: 10px;margin - bottom: 10px;
}.cart - item - image {width: 80px;height: 80px;object - fit: cover;margin - right: 10px;
}.cart - item - info {flex: 1;
}.cart - item - name {margin - top: 0;margin - bottom: 5px;
}.cart - item - price {margin: 0;color: #666;
}.quantity - control {margin - top: 5px;
}.delete - button {background - color: #ff0000;color: white;border: none;padding: 5px 10px;border - radius: 3px;cursor: pointer;
}.cart - footer {margin - top: 20px;display: flex;justify - content: space - between;align - items: center;
}.total - price {font - weight: bold;
}.checkout - button {background - color: #007BFF;color: white;border: none;padding: 10px 20px;border - radius: 3px;cursor: pointer;
}
</style>

(二)构建vue模块,获取数据存储

import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';Vue.use(Vuex);const cartModule = {namespaced: true,state: {cartItems: [],totalPrice: 0},mutations: {SET_CART_ITEMS(state, items) {state.cartItems = items;},UPDATE_TOTAL_PRICE(state) {state.totalPrice = state.cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);}},actions: {async fetchCartItems({ commit }) {try {const response = await axios.get('/api/cart');commit('SET_CART_ITEMS', response.data);commit('UPDATE_TOTAL_PRICE');} catch (error) {console.error('获取购物车数据失败', error);}}}
};export default new Vuex.Store({modules: {cart: cartModule}
});

(三)mapState动态计算展示

1. mapState 是Vuex提供的辅助函数,用于简化从Vuex的 state 中获取数据并映射到组件计算属性的过程。在购物车功能中,借助 mapState ,能便捷地将购物车相关数据(如购物车商品列表、总价等)引入到组件中,实现数据驱动视图的更新。
 
2. mapState 将Vuex中购物车商品列表数据映射为组件的计算属性。利用Vue的 v - for 指令遍历该计算属性,为每个商品项生成对应的DOM元素。将商品的各项信息(如名称、价格、数量)绑定到相应的HTML元素上,实现购物车列表的动态渲染。
 3.

<template><div class="shopping - cart - page"><!-- 标题栏 --><header class="cart - header"><h1>购物车</h1></header><!-- 商品列表 --><div class="cart - items"><div v - for="(item, index) in cartItems" :key="index" class="cart - item"><img :src="item.imageUrl" alt="商品图片" class="cart - item - image"><div class="cart - item - info"><p class="cart - item - name">{{ item.name }}</p><p class="cart - item - price">价格: {{ item.price }}元</p><div class="quantity - control"><span>数量: {{ item.quantity }}</span></div></div><button class="delete - button" @click="deleteItem(index)">删除</button></div></div><!-- 底部信息 --><footer class="cart - footer"><div class="total - price"><p>总价: {{ totalPrice }}元</p></div><button class="checkout - button">结算</button></footer></div>
</template><script>
import { mapState } from 'vuex';export default {computed: {...mapState('cart', {cartItems: state => state.cartItems,totalPrice: state => state.totalPrice})},methods: {deleteItem(index) {// 这里暂未实现删除功能的具体逻辑,实际需调用Vuex的action修改购物车数据console.log(`删除第 ${index + 1} 个商品`);}}
};
</script><style scoped>
.shopping - cart - page {padding: 20px;
}.cart - header {text - align: center;margin - bottom: 20px;
}.cart - items {list - style: none;padding: 0;
}.cart - item {display: flex;align - items: center;border: 1px solid #ccc;padding: 10px;margin - bottom: 10px;
}.cart - item - image {width: 80px;height: 80px;object - fit: cover;margin - right: 10px;
}.cart - item - info {flex: 1;
}.cart - item - name {margin - top: 0;margin - bottom: 5px;
}.cart - item - price {margin: 0;color: #666;
}.quantity - control {margin - top: 5px;
}.delete - button {background - color: #ff0000;color: white;border: none;padding: 5px 10px;border - radius: 3px;cursor: pointer;
}.cart - footer {margin - top: 20px;display: flex;justify - content: space - between;align - items: center;
}.total - price {font - weight: bold;
}.checkout - button {background - color: #007BFF;color: white;border: none;padding: 10px 20px;border - radius: 3px;cursor: pointer;
}
</style>

(四)封装getters动态计算展示

1.etters用于对store中的state进行加工处理,相当于store的计算属性。在购物车功能里,利用getters可以对购物车商品数据进行动态计算,如计算选中商品的总价、商品数量等,方便在组件中复用这些计算结果。
 
2.封装getters,将复杂的计算逻辑集中管理,避免在多个组件中重复编写相同的计算代码,提高代码的可维护性和复用性。当购物车数据结构或计算规则发生变化时,只需在getters中修改,所有依赖该计算结果的组件会自动更新。
 
3.state中的购物车商品列表数据进行计算。在组件中,通过 mapGetters 辅助函数将getters映射到组件的计算属性,然后在模板中使用这些计算属性进行数据展示。

javascriptconst cartModule = {namespaced: true,state: {cartItems: [{ id: 1, name: '商品1', price: 100, quantity: 1, isChecked: false },{ id: 2, name: '商品2', price: 200, quantity: 2, isChecked: true }]},getters: {// 计算选中商品的总价selectedTotalPrice(state) {return state.cartItems.reduce((total, item) => {if (item.isChecked) {return total + item.price * item.quantity;}return total;}, 0);},// 计算选中商品的数量selectedItemCount(state) {return state.cartItems.reduce((count, item) => {if (item.isChecked) {return count + item.quantity;}return count;}, 0);}}
};export default cartModule;

(五)全选反选

javascriptconst cartModule = {namespaced: true,state: {cartItems: [{ id: 1, name: '商品1', price: 100, quantity: 1, isChecked: false },{ id: 2, name: '商品2', price: 200, quantity: 2, isChecked: false }]},mutations: {// 全选购物车商品SELECT_ALL_ITEMS(state) {state.cartItems.forEach(item => item.isChecked = true);},// 反选购物车商品INVERT_SELECTION(state) {state.cartItems.forEach(item => item.isChecked =!item.isChecked);}},actions: {selectAllItems({ commit }) {commit('SELECT_ALL_ITEMS');},invertSelection({ commit }) {commit('INVERT_SELECTION');}}
};export default cartModule;

(六)数字框修改数量

javascriptconst cartModule = {namespaced: true,state: {cartItems: [{ id: 1, name: '商品1', price: 100, quantity: 1, isChecked: false },{ id: 2, name: '商品2', price: 200, quantity: 2, isChecked: false }]},mutations: {// 修改购物车中商品的数量UPDATE_CART_ITEM_QUANTITY(state, { itemId, newQuantity }) {const item = state.cartItems.find(i => i.id === itemId);if (item) {item.quantity = newQuantity;}}},actions: {updateCartItemQuantity({ commit }, payload) {commit('UPDATE_CART_ITEM_QUANTITY', payload);},// 重新计算总价(可在数量变化等操作后调用)recalculateTotalPrice({ state }) {// 假设总价计算逻辑const total = state.cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);// 可在这里更新总价到state中的总价变量}},getters: {// 计算购物车总价cartTotalPrice(state) {return state.cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0);}}
};export default cartModule;购物车组件(假设在 components/Cart.vue )html<template><div><h1>购物车</h1><ul><li v-for="item in cartItems" :key="item.id">{{ item.name }} - {{ item.price }}元<!-- 数字框及增减按钮 --><div><button @click="decreaseQuantity(item.id)">-</button><input v-model.number="item.quantity" type="number" min="1"><button @click="increaseQuantity(item.id)">+</button></div></li></ul><p>购物车总价: {{ cartTotalPrice }}元</p></div>
</template><script>
import { mapState, mapActions, mapGetters } from 'vuex';export default {computed: {...mapState('cart', ['cartItems']),...mapGetters('cart', ['cartTotalPrice'])},methods: {...mapActions('cart', ['updateCartItemQuantity','recalculateTotalPrice']),increaseQuantity(itemId) {const item = this.cartItems.find(i => i.id === itemId);if (item) {this.updateCartItemQuantity({ itemId, newQuantity: item.quantity + 1 });this.recalculateTotalPrice();}},decreaseQuantity(itemId) {const item = this.cartItems.find(i => i.id === itemId);if (item && item.quantity > 1) {this.updateCartItemQuantity({ itemId, newQuantity: item.quantity - 1 });this.recalculateTotalPrice();}}}
};
</script>

(七)编辑、删除、空购物车处理

<template><div><h1>购物车</h1><ul><li v - for="(item, index) in cartItems" :key="index"><!-- 非编辑状态展示 --><div v - if="!item.isEditing"><span>{{ item.name }}</span><span> - 数量: {{ item.quantity }}</span><button @click="editItem(index)">编辑</button><button @click="deleteItem(index)">删除</button></div><!-- 编辑状态展示 --><div v - if="item.isEditing"><input v - model="item.name" type="text"><input v - model.number="item.quantity" type="number" min="1"><button @click="saveItem(index)">保存</button></div></li></ul><button @click="emptyCart">清空购物车</button><!-- 空购物车提示 --><div v - if="cartItems.length === 0" class="empty - cart - msg">您的购物车目前为空,<router - link to="/">去逛逛</router - link></div></div>
</template><script>
import { mapState, mapActions } from 'vuex';export default {computed: {...mapState('cart', ['cartItems'])},methods: {...mapActions('cart', ['deleteCartItem', 'emptyCart']),editItem(index) {this.cartItems[index].isEditing = true;},saveItem(index) {this.cartItems[index].isEditing = false;// 可在此处添加向服务器更新数据的逻辑},async deleteItem(index) {const confirmDelete = window.confirm('确定要删除该商品吗?');if (confirmDelete) {const itemId = this.cartItems[index].id;await this.deleteCartItem(itemId);}},async emptyCart() {const confirmEmpty = window.confirm('确定要清空购物车吗?');if (confirmEmpty) {await this.emptyCart();}}}
};
</script>// store/modules/cart.js
const cartModule = {namespaced: true,state: {cartItems: [{ id: 1, name: '商品1', quantity: 1, isEditing: false },{ id: 2, name: '商品2', quantity: 2, isEditing: false }]},mutations: {// 修改购物车商品列表SET_CART_ITEMS(state, items) {state.cartItems = items;},// 删除单个商品DELETE_CART_ITEM(state, itemId) {state.cartItems = state.cartItems.filter(item => item.id!== itemId);},// 清空购物车EMPTY_CART(state) {state.cartItems = [];}},actions: {async deleteCartItem({ commit }, itemId) {// 可在此处添加向服务器发送删除请求的逻辑commit('DELETE_CART_ITEM', itemId);},async emptyCart({ commit }) {// 可在此处添加向服务器发送清空购物车请求的逻辑commit('EMPTY_CART');}}
};export default cartModule;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/69425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

webpack【初体验】使用 webpack 打包一个程序

打包前 共 3 个文件 dist\index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Webpack 示例&…

Spring Boot接入Deep Seek的API

1&#xff0c;首先进入deepseek的官网&#xff1a;DeepSeek | 深度求索&#xff0c;单击右上角的API开放平台。 2&#xff0c;单击API keys&#xff0c;创建一个API&#xff0c;创建完成务必复制&#xff01;&#xff01;不然关掉之后会看不看api key&#xff01;&#xff01;&…

02.07 TCP服务器与客户端的搭建

一.思维导图 二.使用动态协议包实现服务器与客户端 1. 协议包的结构定义 首先&#xff0c;是协议包的结构定义。在两段代码中&#xff0c;pack_t结构体都被用来表示协议包&#xff1a; typedef struct Pack {int size; // 记录整个协议包的实际大小enum Type type; …

笔灵ai写作技术浅析(六):智能改写与续写

笔灵AI写作中的智能改写和续写技术是其核心功能之一,旨在帮助用户生成高质量、多样化的文本内容。 一、智能改写技术 1. 基本原理 智能改写的目标是在保持原文语义不变的前提下,对文本进行重新表述,生成语法正确、语义连贯且风格多样的新文本。其核心思想是通过语义理解和…

3.如何标注数据集

软件安装&#xff1a; Labelme&#xff0c;打开链接之后跳转如下&#xff1a; 这里往下翻&#xff0c;如下&#xff1a; 选择上图圈起来的exe进行下载就可以了&#xff0c;下载完成之后就可以双击直接打开了。如果通过github下载很慢的话可以直接选择通过网盘分享的文件&…

【分布式理论7】分布式调用之:服务间的(RPC)远程调用

文章目录 一、RPC 调用过程二、RPC 动态代理&#xff1a;屏蔽远程通讯细节1. 动态代理示例2. 如何将动态代理应用于 RPC 三、RPC序列化与协议编码1. RPC 序列化2. RPC 协议编码2.1. 协议编码的作用2.2. RPC 协议消息组成 四、RPC 网络传输1. 网络传输流程2. 关键优化点 一、RPC…

SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来Matlab实现

SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来Matlab实现 目录 SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来Matlab实现预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SSA-TCN麻雀算法优化时间卷积神经网络时间序列预测未来&#xff08;优…

智慧停车场解决方案(文末联系,领取整套资料,可做论文)

一、方案概述 本智慧停车场解决方案旨在通过硬件设备与软件系统的深度整合&#xff0c;实现停车场的智能化管理与服务&#xff0c;提升车主的停车体验&#xff0c;优化停车场运营效率。 二、硬件架构 硬件设备说明&#xff1a; 车牌识别摄像机&#xff1a;安装在停车场入口和…

DeepSeek开源多模态大模型Janus-Pro部署

DeepSeek多模态大模型部署 请自行根据电脑配置选择合适环境配置安装conda以及gitJanus 项目以及依赖安装运行cpu运行gpu运行 进入ui界面 请自行根据电脑配置选择合适 本人家用电脑为1060&#xff0c;因此部署的7B模型。配置高的可以考虑更大参数的模型。 环境配置 安装conda…

C#常用集合优缺点对比

先上结论&#xff1a; 在C#中&#xff0c;链表、一维数组、字典、List<T>和ArrayList是常见的数据集合类型&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的比较&#xff1a; 1. 一维数组 (T[]) 优点&#xff1a; 性能高&#xff1a;数组在内存中…

python-leetcode-删除有序数组中的重复项 II

80. 删除有序数组中的重复项 II - 力扣&#xff08;LeetCode&#xff09; class Solution:def removeDuplicates(self, nums: List[int]) -> int:if len(nums) < 2:return len(nums)j 2 # 允许最多两个相同的元素for i in range(2, len(nums)):if nums[i] ! nums[j - 2…

如何启用 Apache Rewrite 重写模块 ?

Apache 的 mod_rewrite 是最强大的 URL 操作模块之一。使用 mod_rewrite&#xff0c;您可以重定向和重写 url&#xff0c;这对于在您的网站上实现 seo 友好的 URL 结构特别有用。在本文中&#xff0c;我们将引导您了解如何在基于 Debian 和基于 RHEL 的系统上在 Apache 中启用 …

动手学图神经网络(9):利用图神经网络进行节点分类 WeightsBiases

利用图神经网络进行节点分类Weights&Biases 引言 在本篇博客中,将深入探讨如何使用图神经网络(GNNs)来完成节点分类任务。以 Cora 数据集为例,该数据集是一个引用网络,节点代表文档,推断每个文档的类别。同时,使用 Weights & Biases(W&B)来跟踪实验过程和…

STM32单片机学习记录(2.9)

一、STM32 15.1 - FLASH闪存 1. FLASH简介 &#xff08;1&#xff09;STM32系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程&#xff1b; &#xff08;2&#x…

<论文>DeepSeek-R1:通过强化学习激励大语言模型的推理能力(深度思考)

一、摘要 本文跟大家来一起阅读DeepSeek团队发表于2025年1月的一篇论文《DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning | Papers With Code》&#xff0c;新鲜的DeepSeek-R1推理模型&#xff0c;作者规模属实庞大。如果你正在使用Deep…

Spring Boot 3.4 中 MockMvcTester 的新特性解析

引言 在 Spring Boot 3.4 版本中&#xff0c;引入了一个全新的 MockMvcTester 类&#xff0c;使 MockMvc 测试可以直接支持 AssertJ 断言。本文将深入探讨这一新特性&#xff0c;分析它如何优化 MockMvc 测试并提升测试的可读性。 Spring MVC 示例 为了演示 MockMvcTester 的…

来自国外的实用软件 ,已接触所有限制!

今天我给大家带来了一款超棒的全自动抠图软件&#xff0c;真的是一个来自国外的宝藏工具&#xff01;而且好消息是&#xff0c;它现在完全解除了限制&#xff0c;可以无限畅快地使用了。 Teorex PhotoScissors 抠图软件 这款软件特别贴心&#xff0c;根本不需要安装&#xff0…

Jetbrains IDE http客户端使用教程

简介 JetBrains IDE&#xff08;如IntelliJ IDEA&#xff0c; WebStorm&#xff0c; PhpStorm和PyCharm&#xff09;自带一个内置的HTTP客户端&#xff0c;允许直接从IDE发送HTTP请求&#xff0c;而无需使用第三方工具&#xff0c;如Postman或cURL。 JetBrains IDE 中的 HTTP…

活动预告 |【Part1】Microsoft Azure 在线技术公开课:AI 基础知识

课程介绍 参加“Azure 在线技术公开课&#xff1a;AI 基础知识”活动&#xff0c;了解 AI 核心概念。参加我们举办的本次免费培训活动&#xff0c;了解组织如何使用 AI 技术克服实际挑战&#xff0c;以及如何借助 Azure AI 服务构建智能应用程序。本次培训适用于任何对 AI 解决…

小红书提出新面部视频交换方法DynamicFace,可生成高质量且一致的视频面部图像。

DynamicFace是一种新颖的面部视频交换方法&#xff0c;旨在生成高质量且一致的视频面部图像。该方法结合了扩散模型的强大能力和可插拔的时间层&#xff0c;以解决传统面部交换技术面临的两个主要挑战&#xff1a;在保持源面部身份的同时&#xff0c;准确传递目标面部的运动信息…