智慧商城(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.js

import requset from '@/utils/request'

// 获取搜索商品列表的数据
export const getProList = (obj) => {
  const { categoryId, goodsName, page } = obj
  return 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 = detail
      this.images = detail.goods_images
      console.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 = detail
      this.images = detail.goods_images
      console.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.value
        return
      }

      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.js

import 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.cartTotal
  this.$toast('加入购物车成功')
  this.showPannel = false
},
3.请求拦截器中,统一携带 token
// 自定义配置 - 请求/响应 拦截器
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  ...
  const token = store.getters.token
  if (token) {
    config.headers['Access-Token'] = token
    config.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 } = obj
    context.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) return
  await this.$store.dispatch('cart/delSelect')
  this.isEdit = false
},

提供 actions

actions: {
    // 删除购物车数据
    async delSelect (context) {
      const selCartList = context.getters.selCartList
      const 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.region
    return 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 = userInfo
      console.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/#publicpath
  publicPath: './'
}

(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')

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DevOps】产品需求文档(PRD)与常见原型软件

文章目录 1、PRD介绍1.1、概述1.2、前提条件1.3、主要目的1.4、关键内容1.5、表述方式1.6、需求评审人员1.7、一般内容结构 2、需求流程3、常见原型软件3.1、Word3.2、Axure3.2.1、详细介绍3.2.2、应用分类3.2.3、优缺点 3.3、摹客RP3.4、蓝湖3.5、GUI Design Studio 1、PRD介绍…

TQ15EG开发板教程:在VIVADO2023.1 以及VITIS环境下 检测DDR4

打开VIVADO2023.1 创建一个新的工程&#xff0c;设置工程名称和地址 选择RTL工程&#xff0c;勾选不添加文件 搜索15eg&#xff0c;选择xqzu15eg-ffrb1156-2-i 完成创建工程 添加设计模块 设置模块名称 在模块中添加mpsoc器件 双击器件进行配置 若有配置文件预设可以直接导入配…

分布式任务调度框架XXL-JOB详解

分布式任务调度 概述 场景: 如12306网站根据不同车次设置放票时间点&#xff0c;商品成功发货后向客户发送短信提醒等任务,某财务系统需要在每天上午10天前统计前一天的账单数据 任务的调度是指系统为了完成特定业务&#xff0c;基于给定的时间点&#xff0c;时间间隔&#…

C# SSH.NET 长命令及时返回

在SSH中执行长时间的命令&#xff0c;SSH.NET及时在文本框中返回连续显示结果。 c# - Execute long time command in SSH.NET and display the results continuously in TextBox - Stack Overflow 博主管理了一个服务器集群&#xff0c;准备上自动巡检工具&#xff0c;测试在…

计算机网络实验二

目录 实验二 交换机的基本配置 1、实验目的 2、实验设备 &#xff08;1&#xff09;实验内容&#xff1a; &#xff08;2&#xff09;练习&#xff1a; 1.实验内容一&#xff1a;&#xff08;交换机的配置方式&#xff09; 2.实验内容二&#xff1a;&#xff08;交换机…

LabVIEW汽车自燃监测预警系统

LabVIEW汽车自燃监测预警系统 随着汽车行业的飞速发展&#xff0c;汽车安全问题日益受到公众的关注。其中&#xff0c;汽车自燃现象因其突发性和破坏性&#xff0c;成为一个不可忽视的安全隐患。为了有效预防和减少自燃事故的发生&#xff0c;提出了LabVIEW的汽车自燃监测预警…

算法学习——华为机考题库4(HJ26 - HJ30)

算法学习——华为机考题库4&#xff08;HJ26 - HJ30&#xff09; HJ26 字符串排序 描述 编写一个程序&#xff0c;将输入字符串中的字符按如下规则排序。 规则 1 &#xff1a;英文字母从 A 到 Z 排列&#xff0c;不区分大小写。 如&#xff0c;输入&#xff1a; Type 输出…

2024年 复习 HTML5+CSS3+移动web 笔记 之CSS遍 第5天

第 五 天 整个网站例 5.1 准备工作 项目目录与版心 base.css 5.2 网页制作思路 5.3 header 区域-整体布局 5.4 header区域-logo 5.5 header区域-导航 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&l…

qt -chart控件设计器可拖拉

qt -chart控件设计器可拖拉 一、演示效果二、安装过程三、核心程序四、程序链接 一、演示效果 二、安装过程 三、核心程序 #include <QtGui> #include <QColor>#include <cstdlib> #include <cassert> #include <numeric>#include <chartwor…

Python 数据分析(PYDA)第三版(六)

原文&#xff1a;wesmckinney.com/book/ 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 十二、Python 建模库介绍 原文&#xff1a;wesmckinney.com/book/modeling 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 此开放访问网络版本的《Python 数据分析第三版…

如何在win系统部署开源云图床Qchan并无公网ip访问本地存储图片

文章目录 前言1. Qchan网站搭建1.1 Qchan下载和安装1.2 Qchan网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar云端设置2.2 Cpolar本地设置 3. 公网访问测试总结 前言 图床作为云存储的一项重要应用场景&#xff0c;在大量开发人员的努力下&#xff0c;已经开发出大…

01-Java工厂模式 ( Factory Pattern )

工厂模式 Factory Pattern 摘要实现范例 工厂模式&#xff08;Factory Pattern&#xff09;提供了一种创建对象的最佳方式 工厂模式在创建对象时不会对客户端暴露创建逻辑&#xff0c;并且是通过使用一个共同的接口来指向新创建的对象 工厂模式属于创建型模式 摘要 1. 意图 …

Mac M1使用PD虚拟机运行win10弹出“内部版本已过期立即安装新的windows内部版本”

一、问题 内部版本已过期立即安装新的windows内部版本 二、解决 1、如图所示打开zh-CN目录 C:\windows\system32\zh-CN找到licensingui.exe文件 将该文件重命名为licensingui_bak.exe 2、修改完成效果如下 &#xff08;1&#xff09;但操作中发现&#xff0c;需要TrustedIns…

店群如何防关联?抖音小店被限流怎么办?——站斧浏览器云桌面

无论是抖音小店店铺&#xff0c;还是其他店铺&#xff1b;使用相同法人、相同类目&#xff0c;多开都会被限流&#xff0c;甚至严重到全部店铺迟迟不出单。 下面小编根据不同情况给出解决方案&#xff1a; 1.不同法人、相同类目的情况 使用云服务器&#xff08;站斧云桌面&am…

webpack配置

一、很多基础方面的配置被vuecli所集成一般项目都是使用vuecli,不会真正的去从0-1进行webpack配置: 1、vuecli中的webpack基础配置: (1)入口文件默认在src/main;输出在dist; (2)集成了大量的插件和加载器:babel-loader 处理 JavaScript 文件、使用 css-loader 和 style-load…

简单说说redis分布式锁

什么是分布式锁 分布式锁&#xff08;多服务共享锁&#xff09;在分布式的部署环境下&#xff0c;通过锁机制来让多客户端互斥的对共享资源进行访问/操作。 为什么需要分布式锁 在单体应用服务里&#xff0c;不同的客户端操作同一个资源&#xff0c;我们可以通过操作系统提供…

Jupyter Notebook中的%matplotlib inline详解

Jupyter Notebook中的%matplotlib inline详解 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;什么是魔术命令&#x1f333;&#x1f333;%matplotlib inline详解&#x1f333;(&#x1f448;直入主题请点击)&#x1f333;小结&#x1f333;&…

【乳腺肿瘤诊断分类及预测】基于Elman神经网络

课题名称&#xff1a;基于Elman神经网络的乳腺肿瘤诊断分类及预测 版本日期&#xff1a;2023-05-15 运行方式: 直接运行Elman0501.m 文件即可 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 威斯康辛大学医学院经过多年的收集和整理&a…

前端 reduce()用法总结

定义 reduce()方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行)&#xff0c;将其结果汇总为单个返回值。语法为&#xff1a; array.reduce(function(accumulator, currentValue, currentIndex, arr), initialValue); /*accumulator: 必需。累计器currentValu…

Android13源码下载及全编译流程

目录 一、源码下载 1.1、配置要求 1.1.1、硬件配置要求 1.1.2、软件要求 1.2、下载环境搭建 1.2.1、依赖安装 1.2.2、工具安装 1.2.3、git配置 1.2.4、repo配置 1.3、源码下载 1.3.1、明确下载版本 1.3.2、替换为清华源 1.3.3、初始化仓库并指定分支 1.3.4、同步全部源码 二、…