智慧商城(continue)

文章目录

    • 1.静态页面结构准备和动态渲染
    • 2.搜索 - 历史记录管理
        • 1. 写好基础静态页面,可以先往里面加一点假数据
        • 2. 上面基本的渲染直接利用history渲染就可以了
        • 3. 搜索历史基本渲染结束了,开始点击搜索添加历史
        • 4. vant内用v-model=" ",可以快速拿到搜索框的值
        • 5. 往历史记录里面追加,追加到最前面,要用到`onshift`
        • 6. 清空数组,就是把它变成一个空数组,只需要在垃圾桶图标的地方注册一个点击事件,然后在methods中写方法
        • 7. 完成搜索历史的持久化,往storage模块里封装方法就可以了
    • 3.搜索列表 - 静态布局 & 渲染
        • 1.现在这个手机是写死的,不管搜什么都是手机(基于搜索关键字渲染)
        • 2.基于分类页进行渲染
    • 4.商品详情 - 静态布局 & 渲染
        • 1.图片部分
        • 2.商品评价部分(获取接口)
    • 5.加入购物车 - 唤起弹层
    • 6.加入购物车 - 封装数字组件
    • 7.加入购物车 - 判断token登录提示
        • 1.封装接口 api/cart.js
        • 2.页面中调用请求
        • 3.请求拦截器中,统一携带 token
    • 8.构建 vuex cart模块,获取数据存储
    • 10. 购物车 - 封装 getters - 动态计算展示
    • 11. 购物车 - 全选反选功能
    • 12. 购物车 - 数字框修改数量
    • 13. 购物车 - 编辑、删除、空购物车处理
    • 14. 订单结算台
    • 15. 个人中心 - 基本渲染
    • 16. 个人中心 - 退出功能
    • 17. 项目打包优化
      • (1) 打包命令
      • (2) 配置publicPath
      • (3) 路由懒加载

1.静态页面结构准备和动态渲染

van-search是搜索框

van-swipe & van-swipe-item是轮播图

van-grid & van-grid-item是grid布局

2.搜索 - 历史记录管理

目标:构建搜索页面的静态布局,完成历史记录的管理

历史管理的需求:
1.搜索历史基本渲染(展示之前搜索过的标签

2.点击搜索(添加历史)

点击 搜索按钮 或 底下历史记录, 都能进行搜索
①若之前 没有 相同搜索关键字,则直接追加到最前面
②若之前 已有 相同搜索关键字, 将该原有关键字移除,再追加

3.清空历史:添加清空图标, 可以清空历史记录

4.持久化:搜索历史需要持久化,刷新历史不丢失

搜索部分在views/search/index.vue里面写

1. 写好基础静态页面,可以先往里面加一点假数据
<script>
export default {name: 'SearchIndex',data () {return {history: ['手机', '白酒', '电视']}}
}
</script>
2. 上面基本的渲染直接利用history渲染就可以了

在这里插入图片描述

<!-- 搜索历史 --><div class="search-history" v-if="history.length > 0"><!-- 上面的 v-if 是,历史的长度大于0,有历史,才去渲染下面的东西 --><div class="title"><span>最近搜索</span><van-icon name="delete-o" size="16" /></div><div class="list"><!-- 内容用v-for循环 --><div v-for ="item in history" :key="item" class="list-item" @click="$router.push('/searchlist')">{{ item }}</div></div></div>
3. 搜索历史基本渲染结束了,开始点击搜索添加历史

给搜索和最近搜索标签添加点击事件,goSearch,在下面添加方法
在这里插入图片描述

<div @click="goSearch">搜索</div>
<div v-for ="item in history" :key="item" class="list-item" @click="goSearch">{{ item }}</div>methods: {goSearch () {console.log('进行了搜索')}}
4. vant内用v-model=" ",可以快速拿到搜索框的值
<van-search v-model="search" show-action placeholder="请输入搜索关键词" clearable>
<div @click="goSearch(search)">搜索</div> <!--把search传过去-->
<div v-for ="item in history" :key="item" class="list-item" @click="goSearch(item)">{{ item }}</div>data () {return {search: '',}},methods: {goSearch (key) {console.log('进行了搜索')}}
5. 往历史记录里面追加,追加到最前面,要用到onshift
  methods: {goSearch (key) {// console.log('进行了搜索')const index = this.history.indexOf(key) // indexOf的作用是用来查找当前这个key在history里的下标,如果将来真的找到了,便于删除if (index !== -1) {// 存在相同的项,将原有的关键字移除// splice (从哪开始,删除几个,项1,项2)this.history.splice(index, 1)}this.history.unshift(key)}}
6. 清空数组,就是把它变成一个空数组,只需要在垃圾桶图标的地方注册一个点击事件,然后在methods中写方法
<van-icon @click="clear" name="delete-o" size="16" />clear () {this.history = []}
7. 完成搜索历史的持久化,往storage模块里封装方法就可以了

先在storage里面写

const HISTORY_KEY = 'zxy_history_list'// 获取搜索历史
export const getHiatoryList = () => {const result = localStorage.getItem(HISTORY_KEY)return result ? JSON.parse(result) : []
}// 设置搜索历史
export const setHiatoryList = (arr) => {localStorage.setItem(HISTORY_KEY, JSON.stringify(arr))
}

然后历史记录应该优先从本地去读,直接调用(看有"👈"的行)

import { getHistoryList, setHistoryList } from '@/utils/storage'👈
export default {name: 'SearchIndex',data () {return {search: '',history: getHistoryList()// 从本地读取👈}},methods: {goSearch (key) {// console.log('进行了搜索')const index = this.history.indexOf(key) // indexOf的作用是用来查找当前这个key在history里的下标,如果将来真的找到了,便于删除if (index !== -1) {// 存在相同的项,将原有的关键字移除// splice (从哪开始,删除几个,项1,项2)this.history.splice(index, 1)}this.history.unshift(key)setHistoryList(this.history)// 本地存👈// 跳转到搜索列表页this.$router.push(`/searchlist?search=${key}`)👈},clear () {this.history = []setHistoryList([])👈}}
}

3.搜索列表 - 静态布局 & 渲染

在这里插入图片描述

1.现在这个手机是写死的,不管搜什么都是手机(基于搜索关键字渲染)

在这里插入图片描述

要去找接口文档

在这里插入图片描述

api/product.jsimport requset from '@/utils/request'// 获取搜索商品列表的数据
export const getProList = (obj) => {const { categoryId, goodsName, page } = objreturn requset.get('/goods/list', {params: {categoryId,goodsName,page}})
}

计算属性,query拿地址栏参数

export default {name: 'SearchIndex',components: {GoodsItem},computed: {// 获取地址栏的搜索关键字querySearch () {return this.$route.query.search}}
}

在created里面发请求,拿数据然后渲染

data () {return {page: 1,proList: []}},async created () {const { data: { list } } = await getProList({goodsName: this.querySearch,page: this.page})this.proList = list.data}

list.vue把item传进去,用Goodsitem.vue解析

list.vue<div class="goods-list"><GoodsItem v-for="item in proList" :key="item.goods_id" :item="item"></GoodsItem>
</div>

在这里插入图片描述

2.基于分类页进行渲染

新建api/category.js

import request from '@/utils/request'// 获取分类数据
export const getCategoryData = () => {return request.get('/category/list')
}

list.vue

async created () {const { data: { list } } = await getProList({categoryId: this.$route.query.categoryId,👈goodsName: this.querySearch,page: this.page})this.proList = list.data}

4.商品详情 - 静态布局 & 渲染

在这里插入图片描述

1.图片部分
product.js// 获取商品详情数据
export const getProDetail = (goodsId) => {return requset.get('/goods/detail', {params: {goodsId}})
}
prodetail/index.vue<van-swipe :autoplay="3000" @change="onChange"><van-swipe-item v-for="(image, index) in images" :key="index"><img :src="image.external_url" />👈</van-swipe-item><template #indicator><div class="custom-indicator">{{ current + 1 }} / {{ images.length }}</div></template></van-swipe><!-- 商品说明 --><div class="info"><div class="title"><div class="price"><span class="now">¥{{ detail.goods_price_min }}</span>👈<span class="oldprice">¥{{ detail.goods_price_max }}</span>👈</div><div class="sellcount">已售 {{ detail.goods_sales }} 件</div>👈</div><div class="msg text-ellipsis-2">{{ detail.goods_name }}👈</div>data () {return {images: [],current: 0,detail: {}}},computed: {goodsId () {return this.$route.params.id}},created () {this.getDetail()},methods: {onChange (index) {this.current = index},async getDetail () {const { data: { detail } } = await getProDetail(this.goodsId)this.detail = detailthis.images = detail.goods_imagesconsole.log(this.images)}}

在这里插入图片描述

商品描述部分不能用{{ }},因为里面包含p标签

<!-- 商品描述 --><div class="desc" v-html="detail.content"></div>
2.商品评价部分(获取接口)
product.js// 获取商品评价
export const getProComments = (goodsId, limit) => {return request.get('/comment/listRows', {params: {goodsId,limit}})
}
index.vue<!-- 商品评价 --><div class="comment"><div class="comment-title"><div class="left">商品评价 ({{ total }})</div><div class="right">查看更多 <van-icon name="arrow" /> </div></div><div class="comment-list"><div class="comment-item" v-for="item in commentList" :key="item.comment_id"><div class="top"><img :src="item.user.avatar_url || defaultImg" alt=""><div class="name">{{ item.user.nick_name }}</div><van-rate :size="16" :value="item.score / 2" color="#ffd21e" void-icon="star" void-color="#eee"/></div><div class="content">{{ item.content }}</div><div class="time">{{ item.create_time }}</div></div></div></div>import defaultImg from '@/assets/default-avatar.png'export default {name: 'ProDetail',data () {return {images: [],current: 0,detail: {},total: 0, // 评价总数👈commentList: [], // 评价列表👈defaultImg👈}},computed: {goodsId () {return this.$route.params.id}},created () {this.getDetail()this.getComments()👈},methods: {onChange (index) {this.current = index},async getDetail () {const { data: { detail } } = await getProDetail(this.goodsId)this.detail = detailthis.images = detail.goods_imagesconsole.log(this.images)},async getComments () {👈const { data: { list, total } } = await getProComments(this.goodsId, 3)👈this.commentList = list👈this.total = total👈}👈}
}
</script>

在这里插入图片描述

5.加入购物车 - 唤起弹层

在这里插入图片描述
弹层用的是vant中的反馈组件

import { ActionSheet } from 'vant';Vue.use(ActionSheet);

自定义面板

通过插槽可以自定义面板的展示内容,同时可以使用title属性展示标题栏

<van-action-sheet v-model="show" title="标题"><div class="content">内容</div>
</van-action-sheet><style>.content {padding: 16px 16px 160px;}
</style>
index.vue<!-- 加入购物车的弹层 --><van-action-sheet v-model="showPannel" :title="mode === 'cart' ? '加入购物车' : '立刻购买'"><div class="product"><div class="product-title"><div class="left"><img :src="detail.goods_image" alt=""></div><div class="right"><div class="price"><span>¥</span><span class="nowprice">{{ detail.goods_price_min }}</span></div><div class="count"><span>库存</span><span>{{ detail.stock_total }}</span></div></div></div><div class="num-box"><span>数量</span>数字框占位</div><!-- 有库存才显示提交按钮 --><div class="showbtn" v-if="detail.stock_total > 0"><div class="btn" v-if="mode === 'cart'">加入购物车</div><div class="btn now" v-else>立刻购买</div></div><div class="btn-none" v-else>该商品已抢完</div></div></van-action-sheet>

6.加入购物车 - 封装数字组件

在这里插入图片描述

components/CountBox.vue
<template><div class="count-box"><button @click="handleSub" class="minus">-</button><input :value="value" @change="handleChange" class="inp" type="text"><button @click="handleAdd" class="add">+</button></div></template><script>
export default {props: {value: {type: Number,default: 1}},methods: {handleSub () {if (this.value <= 1) {return}this.$emit('input', this.value - 1)},handleAdd () {this.$emit('input', this.value + 1)},handleChange (e) {// console.log(e.target.value)const num = +e.target.value // 转数字处理 (1) 数字 (2) NaN// 输入了不合法的文本 或 输入了负值,回退成原来的 value 值if (isNaN(num) || num < 1) {e.target.value = this.valuereturn}this.$emit('input', num)}}
}
</script><style lang="less" scoped>
.count-box {width: 110px;display: flex;.add, .minus {width: 30px;height: 30px;outline: none;border: none;background-color: #efefef;}.inp {width: 40px;height: 30px;outline: none;border: none;margin: 0 5px;background-color: #efefef;text-align: center;}
}
</style>
prodeatil/index.jsimport CountBox from '@/components/CountBox.vue'👈export default {name: 'ProDetail',components: {CountBox},data () {return {images: [],current: 0,detail: {},total: 0, // 评价总数commentList: [], // 评价列表defaultImg,showPannel: false, // 控制弹层的显示隐藏mode: 'cart', // 标记弹层状态addCount: 1 // 数字框绑定的数据👈}},

7.加入购物车 - 判断token登录提示

在这里插入图片描述

1.封装接口 api/cart.js

// 加入购物车

export const addCart = (goodsId, goodsNum, goodsSkuId) => {return request.post('/cart/add', {goodsId,goodsNum,goodsSkuId})
}
2.页面中调用请求
data () {return {cartTotal: 0}  
},async addCart () {...const { data } = await addCart(this.goodsId, this.addCount, this.detail.skuList[0].goods_sku_id)this.cartTotal = data.cartTotalthis.$toast('加入购物车成功')this.showPannel = false
},
3.请求拦截器中,统一携带 token
// 自定义配置 - 请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {...const token = store.getters.tokenif (token) {config.headers['Access-Token'] = tokenconfig.headers.platform = 'H5'}return config
}, function (error) {// 对请求错误做些什么return Promise.reject(error)
})

8.构建 vuex cart模块,获取数据存储

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

新建 modules/cart.js 模块

export default {namespaced: true,state () {return {cartList: []}},mutations: {},actions: {},getters: {}
}

挂载到 store 上面

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart'Vue.use(Vuex)export default new Vuex.Store({getters: {token: state => state.user.userInfo.token},modules: {user,cart}
})

封装 API 接口 api/cart.js

// 获取购物车列表数据
export const getCartList = () => {return request.get('/cart/list')
}
封装 action 和 mutation
mutations: {setCartList (state, newList) {state.cartList = newList},
},
actions: {async getCartAction (context) {const { data } = await getCartList()data.list.forEach(item => {item.isChecked = true})context.commit('setCartList', data.list)}
},

页面中 dispatch 调用

computed: {isLogin () {return this.$store.getters.token}
},
created () {if (this.isLogin) {this.$store.dispatch('cart/getCartAction')}
},
  1. 购物车 - mapState - 渲染购物车列表
    将数据映射到页面
import { mapState } from 'vuex'computed: {...mapState('cart', ['cartList'])
}

动态渲染

<!-- 购物车列表 -->
<div class="cart-list"><div class="cart-item" v-for="item in cartList" :key="item.goods_id"><van-checkbox icon-size="18" :value="item.isChecked"></van-checkbox><div class="show" @click="$router.push(`/prodetail/${item.goods_id}`)"><img :src="item.goods.goods_image" alt=""></div><div class="info"><span class="tit text-ellipsis-2">{{ item.goods.goods_name }}</span><span class="bottom"><div class="price">¥ <span>{{ item.goods.goods_price_min }}</span></div><CountBox :value="item.goods_num"></CountBox></span></div></div>
</div>

10. 购物车 - 封装 getters - 动态计算展示

封装 getters:商品总数 / 选中的商品列表 / 选中的商品总数 / 选中的商品总价

getters: {cartTotal (state) {return state.cartList.reduce((sum, item, index) => sum + item.goods_num, 0)},selCartList (state) {return state.cartList.filter(item => item.isChecked)},selCount (state, getters) {return getters.selCartList.reduce((sum, item, index) => sum + item.goods_num, 0)},selPrice (state, getters) {return getters.selCartList.reduce((sum, item, index) => {return sum + item.goods_num * item.goods.goods_price_min}, 0).toFixed(2)}
}

页面中 mapGetters 映射使用

computed: {...mapGetters('cart', ['cartTotal', 'selCount', 'selPrice']),
},<!-- 购物车开头 -->
<div class="cart-title"><span class="all">共<i>{{ cartTotal || 0 }}</i>件商品</span><span class="edit"><van-icon name="edit"  />编辑</span>
</div><div class="footer-fixed"><div  class="all-check"><van-checkbox  icon-size="18"></van-checkbox>全选</div><div class="all-total"><div class="price"><span>合计:</span><span>¥ <i class="totalPrice">{{ selPrice }}</i></span></div><div v-if="true" :class="{ disabled: selCount === 0 }" class="goPay">结算({{ selCount }})</div><div v-else  :class="{ disabled: selCount === 0 }" class="delete">删除({{ selCount }})</div></div>
</div>

11. 购物车 - 全选反选功能

全选 getters
getters: {isAllChecked (state) {return state.cartList.every(item => item.isChecked)}
}...mapGetters('cart', ['isAllChecked']),<div class="all-check"><van-checkbox :value="isAllChecked" icon-size="18"></van-checkbox>全选
</div>

点击小选,修改状态

<van-checkbox @click="toggleCheck(item.goods_id)" ...></van-checkbox>toggleCheck (goodsId) {this.$store.commit('cart/toggleCheck', goodsId)
},mutations: {toggleCheck (state, goodsId) {const goods = state.cartList.find(item => item.goods_id === goodsId)goods.isChecked = !goods.isChecked},
}

点击全选,重置状态

<div @click="toggleAllCheck" class="all-check"><van-checkbox :value="isAllChecked" icon-size="18"></van-checkbox>全选
</div>toggleAllCheck () {this.$store.commit('cart/toggleAllCheck', !this.isAllChecked)
},mutations: {toggleAllCheck (state, flag) {state.cartList.forEach(item => {item.isChecked = flag})},
}

12. 购物车 - 数字框修改数量

封装 api 接口

// 更新购物车商品数量
export const changeCount = (goodsId, goodsNum, goodsSkuId) => {return request.post('/cart/update', {goodsId,goodsNum,goodsSkuId})
}

页面中注册点击事件,传递数据

<CountBox :value="item.goods_num" @input="value => changeCount(value, item.goods_id, item.goods_sku_id)"></CountBox>changeCount (value, goodsId, skuId) {this.$store.dispatch('cart/changeCountAction', {value,goodsId,skuId})
},

提供 action 发送请求, commit mutation

mutations: {changeCount (state, { goodsId, value }) {const obj = state.cartList.find(item => item.goods_id === goodsId)obj.goods_num = value}
},
actions: {async changeCountAction (context, obj) {const { goodsId, value, skuId } = objcontext.commit('changeCount', {goodsId,value})await changeCount(goodsId, value, skuId)},
}

13. 购物车 - 编辑、删除、空购物车处理

data 提供数据, 定义是否在编辑删除的状态

data () {return {isEdit: false}
},

注册点击事件,修改状态

<span class="edit" @click="isEdit = !isEdit"><van-icon name="edit"  />编辑
</span>

底下按钮根据状态变化

<div v-if="!isEdit" :class="{ disabled: selCount === 0 }" class="goPay">去结算({{ selCount }})
</div>
<div v-else :class="{ disabled: selCount === 0 }" class="delete">删除</div>

监视编辑状态,动态控制复选框状态

watch: {isEdit (value) {if (value) {this.$store.commit('cart/toggleAllCheck', false)} else {this.$store.commit('cart/toggleAllCheck', true)}}
}

购物车 - 删除功能完成
查看接口,封装 API ( 注意:此处 id 为获取回来的购物车数据的 id )

// 删除购物车
export const delSelect = (cartIds) => {return request.post('/cart/clear', {cartIds})
}

注册删除点击事件

<div v-else :class="{ disabled: selCount === 0 }" @click="handleDel" class="delete">删除({{ selCount }})
</div>async handleDel () {if (this.selCount === 0) returnawait this.$store.dispatch('cart/delSelect')this.isEdit = false
},

提供 actions

actions: {// 删除购物车数据async delSelect (context) {const selCartList = context.getters.selCartListconst cartIds = selCartList.map(item => item.id)await delSelect(cartIds)Toast('删除成功')// 重新拉取最新的购物车数据 (重新渲染)context.dispatch('getCartAction')}
},

购物车 - 空购物车处理
外面包个大盒子,添加 v-if 判断

<div class="cart-box" v-if="isLogin && cartList.length > 0"><!-- 购物车开头 --><div class="cart-title">...</div><!-- 购物车列表 --><div class="cart-list">...</div><div class="footer-fixed">...</div>
</div><div class="empty-cart" v-else><img src="@/assets/empty.png" alt=""><div class="tips">您的购物车是空的, 快去逛逛吧</div><div class="btn" @click="$router.push('/')">去逛逛</div>
</div>

14. 订单结算台

所谓的 “立即结算”,本质就是跳转到订单结算台,并且跳转的同时,需要携带上对应的订单参数。

而具体需要哪些参数,就需要基于 【订单结算台】 的需求来定。

(1) 静态布局

(2) 获取收货地址列表

  1. 封装获取地址的接口
import request from '@/utils/request'
// 获取地址列表
export const getAddressList = () => {return request.get('/address/list')
}
  1. 页面中 - 调用获取地址
data () {return {addressList: []}
},
computed: {selectAddress () {// 这里地址管理不是主线业务,直接获取默认第一条地址return this.addressList[0] }
},
async created () {this.getAddressList()
},
methods: {async getAddressList () {const { data: { list } } = await getAddressList()this.addressList = list}
}
  1. 页面中 - 进行渲染
computed: {longAddress () {const region = this.selectAddress.regionreturn region.province + region.city + region.region + this.selectAddress.detail}
},<div class="info" v-if="selectAddress?.address_id"><div class="info-content"><span class="name">{{ selectAddress.name }}</span><span class="mobile">{{ selectAddress.phone }}</span></div><div class="info-address">{{ longAddress }}</div>
</div>

(3) 订单结算 - 封装通用接口

思路分析 : 这里的订单结算,有两种情况:

购物车结算,需要两个参数

① mode=“cart”

② cartIds=“cartId, cartId”

立即购买结算,需要三个参数

① mode=“buyNow”

② goodsId=“商品id”

③ goodsSkuId=“商品skuId”

都需要跳转时将参数传递过来

封装通用 API 接口 api/order

import request from '@/utils/request'export const checkOrder = (mode, obj) => {return request.get('/checkout/order', {params: {mode,delivery: 0,couponId: 0,isUsePoints: 0,...obj}})
}

15. 个人中心 - 基本渲染

1 封装获取个人信息 - API接口

import request from '@/utils/request'// 获取个人信息
export const getUserInfoDetail = () => {return request.get('/user/info')
}

2 调用接口,获取数据进行渲染

<template><div class="user"><div class="head-page" v-if="isLogin"><div class="head-img"><img src="@/assets/default-avatar.png" alt="" /></div><div class="info"><div class="mobile">{{ detail.mobile }}</div><div class="vip"><van-icon name="diamond-o" />普通会员</div></div></div><div v-else class="head-page" @click="$router.push('/login')"><div class="head-img"><img src="@/assets/default-avatar.png" alt="" /></div><div class="info"><div class="mobile">未登录</div><div class="words">点击登录账号</div></div></div><div class="my-asset"><div class="asset-left"><div class="asset-left-item"><span>{{ detail.pay_money || 0 }}</span><span>账户余额</span></div><div class="asset-left-item"><span>0</span><span>积分</span></div><div class="asset-left-item"><span>0</span><span>优惠券</span></div></div><div class="asset-right"><div class="asset-right-item"><van-icon name="balance-pay" /><span>我的钱包</span></div></div></div><div class="order-navbar"><div class="order-navbar-item" @click="$router.push('/myorder?dataType=all')"><van-icon name="balance-list-o" /><span>全部订单</span></div><div class="order-navbar-item" @click="$router.push('/myorder?dataType=payment')"><van-icon name="clock-o" /><span>待支付</span></div><div class="order-navbar-item" @click="$router.push('/myorder?dataType=delivery')"><van-icon name="logistics" /><span>待发货</span></div><div class="order-navbar-item" @click="$router.push('/myorder?dataType=received')"><van-icon name="send-gift-o" /><span>待收货</span></div></div><div class="service"><div class="title">我的服务</div><div class="content"><div class="content-item"><van-icon name="records" /><span>收货地址</span></div><div class="content-item"><van-icon name="gift-o" /><span>领券中心</span></div><div class="content-item"><van-icon name="gift-card-o" /><span>优惠券</span></div><div class="content-item"><van-icon name="question-o" /><span>我的帮助</span></div><div class="content-item"><van-icon name="balance-o" /><span>我的积分</span></div><div class="content-item"><van-icon name="refund-o" /><span>退换/售后</span></div></div></div><div class="logout-btn"><button>退出登录</button></div></div>
</template><script>
import { getUserInfoDetail } from '@/api/user.js'
export default {name: 'UserPage',data () {return {detail: {}}},created () {if (this.isLogin) {this.getUserInfoDetail()}},computed: {isLogin () {return this.$store.getters.token}},methods: {async getUserInfoDetail () {const { data: { userInfo } } = await getUserInfoDetail()this.detail = userInfoconsole.log(this.detail)}}
}
</script><style lang="less" scoped>
.user {min-height: 100vh;background-color: #f7f7f7;padding-bottom: 50px;
}.head-page {height: 130px;background: url("http://cba.itlike.com/public/mweb/static/background/user-header2.png");background-size: cover;display: flex;align-items: center;.head-img {width: 50px;height: 50px;border-radius: 50%;overflow: hidden;margin: 0 10px;img {width: 100%;height: 100%;object-fit: cover;}}
}
.info {.mobile {margin-bottom: 5px;color: #c59a46;font-size: 18px;font-weight: bold;}.vip {display: inline-block;background-color: #3c3c3c;padding: 3px 5px;border-radius: 5px;color: #e0d3b6;font-size: 14px;.van-icon {font-weight: bold;color: #ffb632;}}
}.my-asset {display: flex;padding: 20px 0;font-size: 14px;background-color: #fff;.asset-left {display: flex;justify-content: space-evenly;flex: 3;.asset-left-item {display: flex;flex-direction: column;justify-content: center;align-items: center;span:first-child {margin-bottom: 5px;color: #ff0000;font-size: 16px;}}}.asset-right {flex: 1;.asset-right-item {display: flex;flex-direction: column;justify-content: center;align-items: center;.van-icon {font-size: 24px;margin-bottom: 5px;}}}
}.order-navbar {display: flex;padding: 15px 0;margin: 10px;font-size: 14px;background-color: #fff;border-radius: 5px;.order-navbar-item {display: flex;flex-direction: column;justify-content: center;align-items: center;width: 25%;.van-icon {font-size: 24px;margin-bottom: 5px;}}
}.service {font-size: 14px;background-color: #fff;border-radius: 5px;margin: 10px;.title {height: 50px;line-height: 50px;padding: 0 15px;font-size: 16px;}.content {display: flex;justify-content: flex-start;flex-wrap: wrap;font-size: 14px;background-color: #fff;border-radius: 5px;.content-item {display: flex;flex-direction: column;justify-content: center;align-items: center;width: 25%;margin-bottom: 20px;.van-icon {font-size: 24px;margin-bottom: 5px;color: #ff3800;}}}
}.logout-btn {button {width: 60%;margin: 10px auto;display: block;font-size: 13px;color: #616161;border-radius: 9px;border: 1px solid #dcdcdc;padding: 7px 0;text-align: center;background-color: #fafafa;}
}
</style>

16. 个人中心 - 退出功能

1 注册点击事件

<button @click="logout">退出登录</button>

2 提供方法

methods: {logout () {this.$dialog.confirm({title: '温馨提示',message: '你确认要退出么?'}).then(() => {this.$store.dispatch('user/logout')}).catch(() => {})}
}actions: {logout (context) {context.commit('setUserInfo', {})context.commit('cart/setCartList', [], { root: true })}
},

17. 项目打包优化

vue脚手架只是开发过程中,协助开发的工具,当真正开发完了 => 脚手架不参与上线

参与上线的是 => 打包后的源代码

打包:

  • 将多个文件压缩合并成一个文件
  • 语法降级
  • less sass ts 语法解析, 解析成css

打包后,可以生成,浏览器能够直接运行的网页 => 就是需要上线的源码!

(1) 打包命令

vue脚手架工具已经提供了打包命令,直接使用即可。

yarn build

在项目的根目录会自动创建一个文件夹dist,dist中的文件就是打包后的文件,只需要放到服务器中即可。

(2) 配置publicPath

module.exports = {// 设置获取.js,.css文件时,是以相对地址为基准的。// https://cli.vuejs.org/zh/config/#publicpathpublicPath: './'
}

(3) 路由懒加载

路由懒加载 & 异步组件, 不会一上来就将所有的组件都加载,而是访问到对应的路由了,才加载解析这个路由对应的所有组件

官网链接:https://router.vuejs.org/zh/guide/advanced/lazy-loading.html#%E4%BD%BF%E7%94%A8-webpack

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

const ProDetail = () => import('@/views/prodetail')
const Pay = () => import('@/views/pay')
const MyOrder = () => import('@/views/myorder')

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

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

相关文章

2024年美国大学生数学建模比赛MCM问题B:搜索潜水器-思路解析与代码解答

2024 MCM Problem B Searching for Submersibles 一、题目翻译 背景&#xff1a; 总部位于希腊的小型海上巡航潜艇&#xff08;MCMS&#xff09;公司&#xff0c;制造能够将人类运送到海洋最深处的潜水器。潜水器被移动到该位置&#xff0c;并不受主船的束缚。MCMS现在希望用…

IP地址查询网络威胁:解析威胁、防范攻击

随着互联网的不断普及和发展&#xff0c;网络威胁也愈发严峻。对IP地址进行查询以解析网络威胁&#xff0c;成为网络安全领域一项重要的工作。本文将深入探讨IP地址查询网络威胁的原理、应用场景、防范策略以及未来的发展方向。 IP地址查询网络威胁原理 IP地址查询IP数据云 -…

【Java程序设计】【C00243】基于Springboot的社区医院管理系统(有论文)

基于Springboot的社区医院管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的社区医院管理服务系统 本系统分为系统功能模块、管理员功能模块、用户功能模块以及医生功能模块。 系统功能模块&#xff1a;社…

泰克示波器(TBS2000系列)触发功能使用讲解——边沿触发

# Trigger区域 触发区域用于对触发功能进行配置。示波器的触发功能用于采集&#xff08;Acquire&#xff09;那些在瞬间出现的信号&#xff0c;便于我们分析观察&#xff0c;此时可以当做逻辑分析仪使用。触发区域按钮包括&#xff1a;menu、Level\Force Trig三个。 目录 1.1 …

java设计模式:策略模式

在平常的开发工作中&#xff0c;经常会用到不同的设计模式&#xff0c;合理的使用设计模式&#xff0c;可以提高开发效率&#xff0c;提高代码质量&#xff0c;提高代码的可拓展性和维护性。今天来聊聊策略模式。 策略模式是一种行为型设计模式&#xff0c;运行时可以根据需求动…

STM32 UART/USART与RTOS的多任务通信和同步机制设计

在STM32微控制器中&#xff0c;UART/USART与RTOS的多任务通信和同步机制设计可以通过操作系统提供的任务调度机制和各种同步原语&#xff08;例如信号量、邮箱、消息队列等&#xff09;来实现。在下面的解释中&#xff0c;我将介绍如何设计基于FreeRTOS的STM32多任务通信和同步…

ZigBee学习——在官方例程基础实现点灯

IAR版本 :10.10.1 Z-stack版本 :3.0.2 文章目录 一、买的板子原理图二、实现过程2.1 重定义LED的物理映射(HAL层)2.2 创建LED事件(应用层)2.2.1 定义用户事件2.2.2 修改zclGenericApp_event_loop() 2.3 触发事件 一、买的板子原理图 二、实现过程 2.1 重定义LED的物理映射(HAL…

分库分表 21 条法则,hold 住!

大家好&#xff5e;今天给大家分享分库分表的 21 条法则 我们结合具体业务场景&#xff0c;以t_order表为例进行架构优化。由于数据量已经达到亿级别&#xff0c;查询性能严重下降&#xff0c;因此我们采用了分库分表技术来处理这个问题。具体而言&#xff0c;我们将原本的单库…

数据结构中的时间复杂度和空间复杂度基础

目录 数据结构 数据结构中的基本名词 数据 数据对象 数据元素 数据项 数据类型 数据对象、数据元素和数据项之间的关系 数据结构及分类 逻辑结构 物理结构 算法 算法的特点 算法设计上的要求 算法效率的衡量 时间复杂度 大O渐进表示法 最坏情况和平均情况 常…

探究Steam爆款游戏”幻兽帕鲁“:玩家评价揭秘

探究Steam爆款游戏”幻兽帕鲁“&#xff1a;玩家评价揭秘 文章目录 探究Steam爆款游戏”幻兽帕鲁“&#xff1a;玩家评价揭秘1 背景描述2 数据说明3 数据来源4 问题描述5 数据探索与预处理5.1 数据加载5.2 数据清洗 6 数据分析6.1 评论分布分析6.2 评论内容情感分析6.3 地理分布…

2024年2月4日 十二生肖 今日运势

小运播报&#xff1a;2024年2月4日&#xff0c;星期日&#xff0c;农历腊月廿五 &#xff08;癸卯年乙丑月戊戌日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;兔、马、虎 需要注意&#xff1a;牛、鸡、龙 喜神方位&#xff1a;东南方 财神方位&#xff1a;正…

WebGL+Three.js入门与实战——绘制水平移动的点、通过鼠标控制绘制(点击绘制、移动绘制、模拟画笔)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…