智慧商城(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. 写好基础静态页面,可以先往里面加一点假数据

2. 上面基本的渲染直接利用history渲染就可以了

智慧商城(continue)_第1张图片


    
最近搜索
{{ item }}
3. 搜索历史基本渲染结束了,开始点击搜索添加历史

给搜索和最近搜索标签添加点击事件,goSearch,在下面添加方法
智慧商城(continue)_第2张图片

搜索
{{ item }}
methods: { goSearch () { console.log('进行了搜索') } }
4. vant内用v-model=" ",可以快速拿到搜索框的值

搜索
{{ item }}
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中写方法


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.搜索列表 - 静态布局 & 渲染

智慧商城(continue)_第3张图片

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

智慧商城(continue)_第4张图片

要去找接口文档

智慧商城(continue)_第5张图片

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

智慧商城(continue)_第6张图片

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.商品详情 - 静态布局 & 渲染

智慧商城(continue)_第7张图片

1.图片部分
product.js

// 获取商品详情数据
export const getProDetail = (goodsId) => {
  return requset.get('/goods/detail', {
    params: {
      goodsId
    }
  })
}
prodetail/index.vue


      
        
      

      
    

    
    
¥{{ detail.goods_price_min }} ¥{{ detail.goods_price_max }}
已售 {{ detail.goods_sales }} 件
{{ detail.goods_name }}
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) } }

智慧商城(continue)_第8张图片

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


    
2.商品评价部分(获取接口)
product.js

// 获取商品评价
export const getProComments = (goodsId, limit) => {
  return request.get('/comment/listRows', {
    params: {
      goodsId,
      limit
    }
  })
}
index.vue


    
商品评价 ({{ total }})
查看更多
{{ item.user.nick_name }}
{{ item.content }}
{{ item.create_time }}
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 } } }

智慧商城(continue)_第9张图片

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

智慧商城(continue)_第10张图片
弹层用的是vant中的反馈组件

import { ActionSheet } from 'vant';

Vue.use(ActionSheet);

自定义面板

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


  
内容
index.vue

 
    
      
¥ {{ detail.goods_price_min }}
库存 {{ detail.stock_total }}
数量 数字框占位
加入购物车
立刻购买
该商品已抢完

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

智慧商城(continue)_第11张图片

components/CountBox.vue






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登录提示

智慧商城(continue)_第12张图片

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'])
}

动态渲染


{{ item.goods.goods_name }}
¥ {{ item.goods.goods_price_min }}

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']),
},
    

{{ cartTotal || 0 }}件商品 编辑

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

全选 getters
getters: {
  isAllChecked (state) {
    return state.cartList.every(item => item.isChecked)
  }
}
    
...mapGetters('cart', ['isAllChecked']),

全选

点击小选,修改状态


    
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
  },
}

点击全选,重置状态

全选
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
  })
}

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



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
  }
},

注册点击事件,修改状态


  
  编辑

底下按钮根据状态变化

去结算({{ selCount }})
删除

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

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
  })
}

注册删除点击事件

删除({{ selCount }})
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 判断

...
...
您的购物车是空的, 快去逛逛吧
去逛逛

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
  }
},

{{ selectAddress.name }} {{ selectAddress.phone }}
{{ longAddress }}

(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 调用接口,获取数据进行渲染






16. 个人中心 - 退出功能

1 注册点击事件


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

你可能感兴趣的:(vue.js,案例)