vue3微信公众号商城项目实战系列(8)商品展示页面
本篇实现在首页展示商品功能,表结构如下:
表名 |
字段 |
功能 |
goods |
goods_id (int) 商品编号 goods_name (varchar) 商品名称 photo (varchar) 商品图片 price (decimal) 价格 |
商品表 |
页面呈现效果如下:
第1步:在 api.js 中加入获取首页商品信息和加购物车的接口方法,如下:
1 export function goodsRecommend(data) { 2 return request({ 3 url: '/goods/recommend', 4 method: 'post', 5 data: data 6 }) 7 } 8 9 10 export function cartAdd(data) { 11 return request({ 12 url: '/cart/add', 13 method: 'post', 14 data: data 15 }) 16 }
第2步:在 Home.vue 中修改代码如下:
布局块代码
1 <template> 2 <div class="home"> 3 4 <div class="goods-list"> 5 <div class="goods-item" v-for="(item,index) in recommendtionList"> 6 <div><img class="goods-img" :src="item.photo" /></div> 7 <div class="goods-name"><span>{{ item.goodsName }}</span></div> 8 <div class="goods-add"> 9 <span class="goods-price">¥{{ item.price }} 元</span> 10 <template v-if="item.qty==0"> 11 <img src="/public/cart.png" @click="onAddCart(item.goodsId,index)" /> 12 </template> 13 <template v-else> 14 <img src="/public/cartadd.png" /> 15 </template> 16 </div> 17 </div> 18 </div> 19 20 <TabBar name="home" /> 21 </div> 22 </template>
第5行中 recommendtionList 是从后端接口获取的商品信息数组,json 格式 ,使用 v-for 遍历输出。
第10行中的 item.qty 是该商品已经加入购物车的数量,如果==0 就显示购物车图标,否则显示已加入的图标,如下:
脚本块代码
1 <script setup> 2 import { onMounted, reactive, toRefs } from 'vue'; 3 import {goodsRecommend, cartAdd} from '@/http/api'; 4 import TabBar from '@/components/TabBar.vue'; 5 6 const data = reactive({recommendtionList:[] }) 7 let {recommendtionList } = toRefs(data); 8 9 onMounted(() => { 10 let formData={}; 11 goodsRecommend(formData).then(res=>{ 12 data.recommendtionList=res; 13 }); 14 }) 15 16 function onAddCart(goodsId, index){ 17 let formData = {goodsId:goodsId,qty:1}; 18 cartAdd(formData).then(res=>{ 19 data.recommendtionList[index].qty=1; 20 }) 21 } 22 </script>
第3行导入 api.js 中的接口方法,分别用于显示商品、加购物车。
第6~7行初始化一个商品数组对象用于接收 goodsRecommend() 接口的返回值
,toRefs( ) 函数能将 响应式对象(这里是data)的每个属性还原为单独的响应式对象,也可以不这样处理,那布局代码块中的第5行要这样写:
<div class="goods-item" v-for="(item,index) in data.recommendtionList">
即由 "recommendtionList" 改成 "data.recommendtionList" 就可以了。
第9~14行是在钩子函数 onMounted()中获取商品信息,钩子函数在页面加载完后执行,这样就能第一时间显示商品信息。
第16~21行实现加购物车的功能,data.recommendtionList[index].qty=1; 的作用是当用户加完购物车后
,图标能马上更换(见下图) ,因为 recommendtionList 是响应式对象,任何时候其值发生改变都会同步影响所有引用到的地方。
样式代码块
1 <style> 2 .home{ 3 padding-bottom: 60px; 4 } 5 .goods-list{ 6 display: flex; 7 flex-direction: row; 8 flex-wrap: wrap; 9 justify-content: space-evenly; 10 background-color: #f0f0f0; 11 } 12 .goods-item{ 13 width: 176px; 14 margin-bottom: 7px; 15 padding:4px 10px; 16 background-color: #FFF; 17 text-align: center; 18 } 19 .goods-img{ 20 width:150px; 21 height: 150px; 22 vertical-align: middle; 23 border:solid 1px #E0E0E0; 24 border-radius: 6px; 25 } 26 .goods-name{ 27 margin-top: 4px; 28 text-align: left; 29 } 30 .goods-add{ 31 display: flex; 32 flex-direction: row; 33 justify-content: space-between; 34 align-items: center; 35 } 36 .goods-price{ 37 font-size: 16px; 38 font-weight: bold; 39 color:orangered; 40 } 41 42 </style>
这里没特别的地方,根据布局需要去定义就可以了。
最后,我们结合上一篇中的需求来解决加购物车后显示角标的问题。
这里需要用到 vue3 的另一个重要包 pinia , 它是官方推荐用来管理状态的(vue2中的状态管理包是vuex)。
官方文档地址:https://pinia.vuejs.org/zh/
1. 在本例子中,状态管理(本质是管理数据,用状态管理这个名称含有一种动态的意味)用来管理购物车中商品的数量
,当用户在任何页面修改了购物车商品数量的时候,状态管理都需要记录下来,这样不管用户怎么切换页面,购物车的角标都能正确显示。
2. 状态管理定义的数据所有页面都能引用,相当于一个全局的数据处理器。
3.更改后的数量需要持久化,确保用户刷新的时候不会归 0。
使用步骤如下:
第1步:安装 pinia,命令: npm install pinia
第2步:在 src/stores/目录下新增 cart.js 文件,stores 目录集中存放所有状态管理的相关文件,如下图。
第3步:修改 main.js, 将 pinia 的实例挂载到 app实例上(见红色字体),如下:
1 import { createApp } from 'vue' 2 import App from './App.vue' 3 4 import { createPinia } from 'pinia' 5 const pinia = createPinia(); 6 7 import './assets/main.css' 8 9 import rtx from './router/routerx.js' 10 11 12 createApp(App).use(pinia).use(rtx).mount('#app')
说明:4~5行创建一个pinia实例对象,第12行用use()方法挂载到app实例对象上。
第4步:定义store,cart.js中代码如下:
1 export const useCartStore = defineStore('cart', () => { 2 const count = ref(0); 3 function increment() { 4 count.value++; 5 } 6 function reduction(){ 7 if(count.value>1){ 8 count.value--; 9 } 10 } 11 12 return { count, increment, reduction } 13 })
说明:
第1行:定义store 用 defineStore()函数(这里是组合式API的写法,和选项式API区别见如下官方文档截图),对象名称 useCartStore 可以是任意的
,但建议使用 useXxxStore 这样的命名方式,Xxx 作为 defineStore()函数的第一个参数(必须唯一)。
第2行:count 是保存购物车中商品数量的变量,是值类型,所以变成响应式数据需要用 ref() 函数处理(引用类型用 reactive()函数)
,使用的时候不能直接用,必须是 xxx.value 的形式 这是值类型和引用类型的差别。
第3~10行:定义2个方法处理 count , 建议用方法去操作 count ,不要直接赋值。
第12行:用 return 语句把这3个对象(1个变量2个方法)返回,外部文件引用 cart.js 后就可以使用这3个对象了。
第5步,使用store ,修改 Home.vue 中 <script> 脚本代码块的内容如下(见红色字体部分):
1 <script setup> 2 import { onMounted, reactive, toRefs } from 'vue'; 3 import {goodsRecommend, cartAdd} from '@/http/api'; 4 import TabBar from '@/components/TabBar.vue'; 5 import { useCartStore } from '@/stores/cart.js' 6 const cartStore = useCartStore(); 7 8 const data = reactive({recommendtionList:[] }) 9 let {recommendtionList } = toRefs(data); 10 11 onMounted(() => { 12 let formData={}; 13 goodsRecommend(formData).then(res=>{ 14 data.recommendtionList=res; 15 }); 16 }) 17 18 function onAddCart(goodsId, index){ 19 let formData = {goodsId:goodsId,qty:1}; 20 cartAdd(formData).then(res=>{ 21 data.recommendtionList[index].qty=1; 22 cartStore.increment(); 23 }) 24 } 25 </script>
第5行引用store对象,第6行实例化,第22行调用 increment() 函数给将store对象的count属性加1。
最后我们到 TabBar.vue 组将中使用 store对象的count属性就可以了,TabBar.vue 代码修改如下(见红色字体部分):
1 <template> 2 <div class="tab-bar"> 3 <template v-for="item in arrTab"> 4 <div :class="item.tabClass" @click="onClick(item.tabName)"> 5 <img class="tab-bar-item-img" :src="item.tabIcon" /> 6 <template v-if="item.tabName=='cart' && cartStore.count"> 7 <span>{{ item.tabText }} {{ cartStore.count }}</span> 8 </template> 9 <template v-else> 10 <span>{{ item.tabText }}</span> 11 </template> 12 </div> 13 </template> 14 </div> 15 </template>
第6~11行是修改的部分, 用v-if 校验如果tab项=购物车而且商品数量>0时就显示角标。
<script>脚本代码块如下:
1 <script setup> 2 import { reactive, onMounted } from 'vue'; 3 import {useRouter} from 'vue-router'; 4 import { useCartStore } from '@/stores/cart.js' 5 const cartStore = useCartStore(); 6 7 //一下相同部分省略 8 9 </script>
刷新页面后点加购物车,效果如下:
角标效果(应该显示在图标右上角)用 css 的 relative 定位实现,这里就不展开了。
最后,用 pinia 的 pinia-plugin-persistedstate 插件持久化 store 解决 刷新页面后 count 消失的问题:
第1步: 安装插件,命令:npm install pinia-plugin-persistedstate
第2步:将插件挂载到 pinia 实例上,修改 main.js 如下(见红色字体):
1 import { createApp } from 'vue' 2 import App from './App.vue' 3 4 import { createPinia } from 'pinia'; 5 const pinia = createPinia(); 6 7 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; 8 pinia.use(piniaPluginPersistedstate) 9 10 import './assets/main.css' 11 12 import rtx from './router/routerx.js' 13 14 15 createApp(App).use(pinia).use(rtx).mount('#app')
第3步:在 cart.js 中 做持久化配置,代码如下(见红色代码):
1 import { ref } from 'vue'; 2 import {defineStore} from 'pinia'; 3 4 export const useCartStore = defineStore('cart', () => { 5 const count = ref(0); 6 function increment() { 7 count.value++; 8 } 9 function reduction(){ 10 if(count.value>1){ 11 count.value--; 12 } 13 } 14 15 return { count, increment, reduction } 16 }, 17 { persist:true } 18 )
刷新页面,购物车的角标值已经不会清 0 了,如下图: