网页设计教程期末教程,北京网站优化培训,qq营销网站源码,宣传片拍摄的意义鹏鹏老师的实战开发项目 文章目录 智慧商城项目01. 项目功能演示1.明确功能模块2.项目收获 02. 项目创建目录初始化vue-cli 建项目 03. 调整初始化目录结构1.删除文件2.修改文件3.新增目录 04. vant组件库及Vue周边的其他组件库05. 全部导入和按需导入的区别06. 全部导入07. 按…鹏鹏老师的实战开发项目 文章目录 智慧商城项目01. 项目功能演示1.明确功能模块2.项目收获 02. 项目创建目录初始化vue-cli 建项目 03. 调整初始化目录结构1.删除文件2.修改文件3.新增目录 04. vant组件库及Vue周边的其他组件库05. 全部导入和按需导入的区别06. 全部导入07. 按需导入08. 项目中的vw适配09. 路由配置 - 一级路由10. 路由配置-tabbar标签页11. 路由配置 - 二级路由12. 登录页静态布局(1) 准备工作(2) 登录静态布局 13. request模块 - axios封装14. 图形验证码功能完成15. 封装api接口 - 图片验证码接口16. toast 轻提示17. 短信验证倒计时功能(1) 倒计时基础效果(2) 验证码请求校验处理(3) 封装接口请求获取验证码 18. 封装api接口 - 登录功能19. 响应拦截器统一处理错误提示20. 将登录权证信息存入 vuex21. vuex持久化处理22. 优化添加请求 loading 效果23. 登录访问拦截 - 路由前置守卫24. 首页 - 静态结构准备25. 首页 - 动态渲染26. 搜索 - 静态布局准备27. 搜索 - 历史记录 - 基本管理28. 搜索 - 历史记录 - 持久化29. 搜索列表 - 静态布局30. 搜索列表 - 动态渲染(1) 搜索关键字搜索(2) 分类id搜索 31. 商品详情 - 静态布局32. 商品详情 - 动态渲染介绍33. 商品详情 - 动态渲染评价34. 加入购物车 - 唤起弹窗35. 加入购物车 - 封装数字框组件36. 加入购物车 - 判断 token 登录提示37. 加入购物车 - 封装接口进行请求38. 购物车 - 静态布局39. 购物车 - 构建 vuex 模块 - 获取数据存储40. 购物车 - mapState - 渲染购物车列表41. 购物车 - 封装 getters - 动态计算展示42. 购物车 - 全选反选功能43. 购物车 - 数字框修改数量44. 购物车 - 编辑切换状态45. 购物车 - 删除功能完成46. 购物车 - 空购物车处理47. 订单结算台(1) 静态布局(2) 获取收货地址列表(3) 订单结算 - 封装通用接口(4) 订单结算 - 购物车结算(5) 订单结算 - 立即购买结算(6) mixins 复用 - 处理登录确认框的弹出 48. 提交订单并支付49. 订单管理(1) 静态布局(2) 点击 tab 切换渲染 50. 个人中心 - 基本渲染51. 个人中心 - 退出功能52. 项目打包优化(1) 打包命令(2) 配置publicPath(3) 路由懒加载 智慧商城项目
接口文档安全问题需要私信即可
演示地址跳转项目地址
01. 项目功能演示
1.明确功能模块
启动准备好的代码演示移动端面经内容明确功能模块
在这里插入图片描述 2.项目收获 02. 项目创建目录初始化
vue-cli 建项目
全局安装过的可以跳过第一步 1.安装脚手架 (已安装)
npm i vue/cli -g2.创建项目
vue create hm-shopping选项
Vue CLI v5.0.8
? Please pick a preset:Default ([Vue 3] babel, eslint)Default ([Vue 2] babel, eslint)Manually select features 选自定义手动选择功能 选择vue的版本 3.x2.x是否使用history模式 选择css预处理 选择eslint的风格 eslint 代码规范的检验工具检验代码是否符合规范比如const age 18; 报错多加了分号后面有工具一保存全部格式化成最规范的样子 选择校验的时机 直接回车 选择配置文件的生成方式 直接回车 是否保存预设下次直接使用 不保存输入 N 等待安装项目初始化完成 启动项目
npm run serve03. 调整初始化目录结构 强烈建议大家严格按照老师的步骤进行调整为了符合企业规范 为了更好的实现后面的操作我们把整体的目录结构做一些调整。
目标:
删除初始化的一些默认文件修改没删除的文件新增我们需要的目录结构
1.删除文件
src/assets/logo.pngsrc/components/HelloWorld.vuesrc/views/AboutView.vuesrc/views/HomeView.vue
2.修改文件
main.js 不需要修改
router/index.js
删除默认的路由配置
import Vue from vue
import VueRouter from vue-routerVue.use(VueRouter)const routes [
]const router new VueRouter({routes
})export default router
App.vue
templatediv idapprouter-view//div
/template3.新增目录
src/api 目录 存储接口模块 (发送ajax请求接口的模块) src/utils 目录 存储一些工具模块 (自己封装的方法)
目录效果如下: 04. vant组件库及Vue周边的其他组件库 组件库第三方封装好了很多很多的组件整合到一起就是一个组件库。 https://vant-contrib.gitee.io/vant/v2/#/zh-CN/ 2)
比如日历组件、键盘组件、打分组件、下拉筛选组件等
组件库并不是唯一的常用的组件库还有以下几种
pc: element-ui element-plus iview ant-design
移动vant-ui Mint UI (饿了么) Cube UI (滴滴)
05. 全部导入和按需导入的区别
目标明确 全部导入 和 按需导入 的区别 区别
1.全部导入会引起项目打包后的体积变大进而影响用户访问网站的性能
2.按需导入只会导入你使用的组件进而节约了资源
06. 全部导入
安装vant-ui
yarn add vantlatest-v2在main.js中
import Vant from vant;
import vant/lib/index.css;
// 把vant中所有的组件都导入了
Vue.use(Vant)即可使用
van-button typeprimary主要按钮/van-button
van-button typeinfo信息按钮/van-buttonvant-ui提供了很多的组件全部导入会导致项目打包变得很大。
07. 按需导入
安装vant-ui
yarn add vantlatest-v2安装一个插件
yarn add babel-plugin-import -D在babel.config.js中配置
module.exports {presets: [vue/cli-plugin-babel/preset],plugins: [[import, {libraryName: vant,libraryDirectory: es,style: true}, vant]]
}按需加载在main.js
import { Button, Icon } from vantVue.use(Button)
Vue.use(Icon)app.vue中进行测试
van-button typeprimary主要按钮/van-button
van-button typeinfo信息按钮/van-button
van-button typedefault默认按钮/van-button
van-button typewarning警告按钮/van-button
van-button typedanger危险按钮/van-button把引入组件的步骤抽离到单独的js文件中比如 utils/vant-ui.js
import { Button, Icon } from vantVue.use(Button)
Vue.use(Icon)main.js中进行导入
// 导入按需导入的配置文件
import /utils/vant-ui08. 项目中的vw适配
官方说明https://vant-contrib.gitee.io/vant/v2/#/zh-CN/advanced-usage
yarn add postcss-px-to-viewport1.1.1 -D项目根目录 新建postcss的配置文件postcss.config.js
// postcss.config.js
module.exports {plugins: {postcss-px-to-viewport: {viewportWidth: 375,},},
};viewportWidth:设计稿的视口宽度
vant-ui中的组件就是按照375的视口宽度设计的恰好面经项目中的设计稿也是按照375的视口宽度设计的所以此时 我们只需要配置375就可以了如果设计稿不是按照375而是按照750的宽度设计那此时这个值该怎么填呢
09. 路由配置 - 一级路由
但凡是单个页面独立展示的都是一级路由
路由设计
登录页首页架子 首页 - 二级分类页 - 二级购物车 - 二级我的 - 二级 搜索页搜索列表页商品详情页结算支付页我的订单页
router/index.js 配置一级路由新建对应的页面文件
import Vue from vue
import VueRouter from vue-router
import Layout from /views/layout
import Search from /views/search
import SearchList from /views/search/list
import ProDetail from /views/prodetail
import Login from /views/login
import Pay from /views/pay
import MyOrder from /views/myorderVue.use(VueRouter)const router new VueRouter({routes: [{path: /login,component: Login},{path: /,component: Layout},{path: /search,component: Search},{path: /searchlist,component: SearchList},{path: /prodetail/:id,component: ProDetail},{path: /pay,component: Pay},{path: /myorder,component: MyOrder}]
})export default router10. 路由配置-tabbar标签页 https://vant-contrib.gitee.io/vant/v2/#/zh-CN/tabbar
vant-ui.js 引入组件
import { Tabbar, TabbarItem } from vant
Vue.use(Tabbar)
Vue.use(TabbarItem)layout.vue
复制官方代码修改显示文本及显示的图标配置高亮颜色
templatediv!-- 二级路由出口 --van-tabbar active-color#ee0a24 inactive-color#000van-tabbar-item iconwap-home-o首页/van-tabbar-itemvan-tabbar-item iconapps-o分类页/van-tabbar-itemvan-tabbar-item iconshopping-cart-o购物车/van-tabbar-itemvan-tabbar-item iconuser-o我的/van-tabbar-item/van-tabbar/div
/template11. 路由配置 - 二级路由
router/index.js配置二级路由
import Vue from vue
import VueRouter from vue-router
import Layout from /views/layout
import Search from /views/search
import SearchList from /views/search/list
import ProDetail from /views/prodetail
import Login from /views/login
import Pay from /views/pay
import MyOrder from /views/myorderimport Home from /views/layout/home
import Category from /views/layout/category
import Cart from /views/layout/cart
import User from /views/layout/userVue.use(VueRouter)const router new VueRouter({routes: [{path: /login,component: Login},{path: /,component: Layout,redirect: /home,children: [{path: home,component: Home},{path: category,component: Category},{path: cart,component: Cart},{path: user,component: User}]},{path: /search,component: Search},{path: /searchlist,component: SearchList},{path: /prodetail/:id,component: ProDetail},{path: /pay,component: Pay},{path: /myorder,component: MyOrder}]
})export default router准备对应的组件文件 layout/home.vuelayout/category.vuelayout/cart.vuelayout/user.vue layout.vue 配置路由出口, 配置 tabbar
templatedivrouter-view/router-viewvan-tabbar route active-color#ee0a24 inactive-color#000van-tabbar-item to/home iconwap-home-o首页/van-tabbar-itemvan-tabbar-item to/category iconapps-o分类页/van-tabbar-itemvan-tabbar-item to/cart iconshopping-cart-o购物车/van-tabbar-itemvan-tabbar-item to/user iconuser-o我的/van-tabbar-item/van-tabbar/div
/template12. 登录页静态布局
(1) 准备工作
新建 styles/common.less 重置默认样式
// 重置默认样式
* {margin: 0;padding: 0;box-sizing: border-box;
}// 文字溢出省略号
.text-ellipsis-2 {overflow: hidden;-webkit-line-clamp: 2;text-overflow: ellipsis;display: -webkit-box;-webkit-box-orient: vertical;
}main.js 中导入应用
import /styles/common.less将准备好的一些图片素材拷贝到 assets 目录【备用】 (2) 登录静态布局 使用组件
van-nav-bar
vant-ui.js 注册
import { NavBar } from vant
Vue.use(NavBar)Login.vue 使用
templatediv classloginvan-nav-bar title会员登录 left-arrow click-left$router.go(-1) /div classcontainerdiv classtitleh3手机号登录/h3p未注册的手机号登录后将自动注册/p/divdiv classformdiv classform-iteminput classinp maxlength11 placeholder请输入手机号码 typetext/divdiv classform-iteminput classinp maxlength5 placeholder请输入图形验证码 typetextimg src/assets/code.png alt/divdiv classform-iteminput classinp placeholder请输入短信验证码 typetextbutton获取验证码/button/div/divdiv classlogin-btn登录/div/div/div
/templatescript
export default {name: LoginPage
}
/scriptstyle langless scoped
.container {padding: 49px 29px;.title {margin-bottom: 20px;h3 {font-size: 26px;font-weight: normal;}p {line-height: 40px;font-size: 14px;color: #b8b8b8;}}.form-item {border-bottom: 1px solid #f3f1f2;padding: 8px;margin-bottom: 14px;display: flex;align-items: center;.inp {display: block;border: none;outline: none;height: 32px;font-size: 14px;flex: 1;}img {width: 94px;height: 31px;}button {height: 31px;border: none;font-size: 13px;color: #cea26a;background-color: transparent;padding-right: 9px;}}.login-btn {width: 100%;height: 42px;margin-top: 39px;background: linear-gradient(90deg,#ecb53c,#ff9211);color: #fff;border-radius: 39px;box-shadow: 0 10px 20px 0 rgba(0,0,0,.1);letter-spacing: 2px;display: flex;justify-content: center;align-items: center;}
}
/style添加通用样式
styles/common.less 设置导航条返回箭头颜色
// 设置导航条 返回箭头 颜色
.van-nav-bar {.van-icon-arrow-left {color: #333;}
}13. request模块 - axios封装
接口文档(安全问题需要私信)
演示地址http://cba.itlike.com/public/mweb/#/
基地址http://cba.itlike.com/public/index.php?s/api/
我们会使用 axios 来请求后端接口, 一般都会对 axios 进行一些配置 (比如: 配置基础地址,请求响应拦截器等等)
一般项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个模块中, 便于使用
目标将 axios 请求方法封装到 request 模块
安装 axios
npm i axios新建 utils/request.js 封装 axios 模块 利用 axios.create 创建一个自定义的 axios 来使用
axios
/* 封装axios用于发送请求 */
import axios from axios// 创建一个新的axios实例
const request axios.create({baseURL: http://cba.itlike.com/public/index.php?s/api/,timeout: 5000
})// 添加请求拦截器
request.interceptors.request.use(function (config) {// 在发送请求之前做些什么return config
}, function (error) {// 对请求错误做些什么return Promise.reject(error)
})// 添加响应拦截器
request.interceptors.response.use(function (response) {// 对响应数据做点什么return response.data
}, function (error) {// 对响应错误做点什么return Promise.reject(error)
})export default request获取图形验证码请求测试
import request from /utils/request
export default {name: LoginPage,async created () {const res await request.get(/captcha/image)console.log(res)}
}14. 图形验证码功能完成 准备数据获取图形验证码后存储图片路径存储图片唯一标识
async created () {this.getPicCode()
},
data () {return {picUrl: ,picKey: }
},
methods: {// 获取图形验证码async getPicCode () {const { data: { base64, key } } await request.get(/captcha/image)this.picUrl base64this.picKey key}
}动态渲染图形验证码并且点击时要重新刷新验证码
img v-ifpicUrl :srcpicUrl clickgetPicCode15. 封装api接口 - 图片验证码接口
**1.目标**将请求封装成方法统一存放到 api 模块与页面分离
2.原因以前的模式 页面中充斥着请求代码 可阅读性不高 相同的请求没有复用请求没有统一管理
3.期望 请求与页面逻辑分离相同的请求可以直接复用请求进行了统一管理
4.具体实现
新建 api/login.js 提供获取图形验证码 Api 函数
import request from /utils/request// 获取图形验证码
export const getPicCode () {return request.get(/captcha/image)
}login/index.vue页面中调用测试
async getPicCode () {const { data: { base64, key } } await getPicCode()this.picUrl base64this.picKey key
},16. toast 轻提示
vant
两种使用方式
导入调用 ( 组件内 或 非组件中均可 )
import { Toast } from vant;
Toast(提示内容);通过this直接调用 ( **组件内 **)
main.js 注册绑定到原型
import { Toast } from vant;
Vue.use(Toast)this.$toast(提示内容)17. 短信验证倒计时功能 (1) 倒计时基础效果
准备 data 数据
data () {return {totalSecond: 60, // 总秒数second: 60, // 倒计时的秒数timer: null // 定时器 id}
},给按钮注册点击事件
button clickgetCode{{ second totalSecond ? 获取验证码 : second 秒后重新发送}}
/button开启倒计时时
async getCode () {if (!this.timer this.second this.totalSecond) {// 开启倒计时this.timer setInterval(() {this.second--if (this.second 1) {clearInterval(this.timer)this.timer nullthis.second this.totalSecond}}, 1000)// 发送请求获取验证码this.$toast(发送成功请注意查收)}
}离开页面销毁定时器
destroyed () {clearInterval(this.timer)
}(2) 验证码请求校验处理
输入框 v-model 绑定变量
data () {return {mobile: , // 手机号picCode: // 图形验证码}
},input v-modelmobile classinp maxlength11 placeholder请输入手机号码 typetext
input v-modelpicCode classinp maxlength5 placeholder请输入图形验证码 typetextmethods中封装校验方法
// 校验输入框内容
validFn () {if (!/^1[3-9]\d{9}$/.test(this.mobile)) {this.$toast(请输入正确的手机号)return false}if (!/^\w{4}$/.test(this.picCode)) {this.$toast(请输入正确的图形验证码)return false}return true
},请求倒计时前进行校验
// 获取短信验证码
async getCode () {if (!this.validFn()) {return}...
}(3) 封装接口请求获取验证码
封装接口 api/login.js
// 获取短信验证码
export const getMsgCode (captchaCode, captchaKey, mobile) {return request.post(/captcha/sendSmsCaptcha, {form: {captchaCode,captchaKey,mobile}})
}调用接口添加提示
// 获取短信验证码
async getCode () {if (!this.validFn()) {return}if (!this.timer this.second this.totalSecond) {// 发送请求获取验证码await getMsgCode(this.picCode, this.picKey, this.mobile)this.$toast(发送成功请注意查收)// 开启倒计时...}
}18. 封装api接口 - 登录功能
api/login.js 提供登录 Api 函数
// 验证码登录
export const codeLogin (mobile, smsCode) {return request.post(/passport/login, {form: {isParty: false,mobile,partyData: {},smsCode}})
}login/index.vue 登录功能
input classinp v-modelmsgCode maxlength6 placeholder请输入短信验证码 typetext
div classlogin-btn clicklogin登录/divdata () {return {msgCode: ,}
},
methods: {async login () {if (!this.validFn()) {return}if (!/^\d{6}$/.test(this.msgCode)) {this.$toast(请输入正确的手机验证码)return}await codeLogin(this.mobile, this.msgCode)this.$router.push(/)this.$toast(登录成功)}
}19. 响应拦截器统一处理错误提示
响应拦截器是咱们拿到数据的 第一个 “数据流转站”可以在里面统一处理错误只要不是 200 默认给提示抛出错误
utils/request.js
import { Toast } from vant...// 添加响应拦截器
request.interceptors.response.use(function (response) {const res response.dataif (res.status ! 200) {Toast(res.message)return Promise.reject(res.message)}// 对响应数据做点什么return res
}, function (error) {// 对响应错误做点什么return Promise.reject(error)
})20. 将登录权证信息存入 vuex
新建 vuex user 模块 store/modules/user.js
export default {namespaced: true,state () {return {userInfo: {token: ,userId: },}},mutations: {},actions: {}
}挂载到 vuex 上
import Vue from vue
import Vuex from vuex
import user from ./modules/userVue.use(Vuex)export default new Vuex.Store({modules: {user,}
})提供 mutations
mutations: {setUserInfo (state, obj) {state.userInfo obj},
},页面中 commit 调用
// 登录按钮校验 提交
async login () {if (!this.validFn()) {return}...const res await codeLogin(this.mobile, this.msgCode)this.$store.commit(user/setUserInfo, res.data)this.$router.push(/)this.$toast(登录成功)
}21. vuex持久化处理
新建 utils/storage.js 封装方法
const INFO_KEY hm_shopping_info// 获取个人信息
export const getInfo () {const result localStorage.getItem(INFO_KEY)return result ? JSON.parse(result) : {token: ,userId: }
}// 设置个人信息
export const setInfo (info) {localStorage.setItem(INFO_KEY, JSON.stringify(info))
}// 移除个人信息
export const removeInfo () {localStorage.removeItem(INFO_KEY)
}vuex user 模块持久化处理
import { getInfo, setInfo } from /utils/storage
export default {namespaced: true,state () {return {userInfo: getInfo()}},mutations: {setUserInfo (state, obj) {state.userInfo objsetInfo(obj)}},actions: {}
}22. 优化添加请求 loading 效果
请求时打开 loading
// 添加请求拦截器
request.interceptors.request.use(function (config) {// 在发送请求之前做些什么Toast.loading({message: 请求中...,forbidClick: true,loadingType: spinner,duration: 0})return config
}, function (error) {// 对请求错误做些什么return Promise.reject(error)
})响应时关闭 loading
// 添加响应拦截器
request.interceptors.response.use(function (response) {const res response.dataif (res.status ! 200) {Toast(res.message)return Promise.reject(res.message)} else {// 清除 loading 中的效果Toast.clear()}// 对响应数据做点什么return res
}, function (error) {// 对响应错误做点什么return Promise.reject(error)
})23. 登录访问拦截 - 路由前置守卫
目标基于全局前置守卫进行页面访问拦截处理
说明智慧商城项目大部分页面游客都可以直接访问, 如遇到需要登录才能进行的操作提示并跳转到登录
但是对于支付页订单页等必须是登录的用户才能访问的游客不能进入该页面需要做拦截处理 路由导航守卫 - 全局前置守卫
1.所有的路由一旦被匹配到都会先经过全局前置守卫
2.只有全局前置守卫放行才会真正解析渲染组件才能看到页面内容
router.beforeEach((to, from, next) {// 1. to 往哪里去 到哪去的路由信息对象 // 2. from 从哪里来 从哪来的路由信息对象// 3. next() 是否放行// 如果next()调用就是放行// next(路径) 拦截到某个路径页面
})const authUrl [/pay, /myorder]
router.beforeEach((to, from, next) {const token store.getters.tokenif (!authUrl.includes(to.path)) {next()return}if (token) {next()} else {next(/login)}
})24. 首页 - 静态结构准备 静态结构和样式 layout/home.vue
templatediv classhome!-- 导航条 --van-nav-bar title智慧商城 fixed /!-- 搜索框 --van-searchreadonlyshaperoundbackground#f1f1f2placeholder请在此输入搜索关键词click$router.push(/search)/!-- 轮播图 --van-swipe classmy-swipe :autoplay3000 indicator-colorwhitevan-swipe-itemimg src/assets/banner1.jpg alt/van-swipe-itemvan-swipe-itemimg src/assets/banner2.jpg alt/van-swipe-itemvan-swipe-itemimg src/assets/banner3.jpg alt/van-swipe-item/van-swipe!-- 导航 --van-grid column-num5 icon-size40van-grid-itemv-foritem in 10 :keyitemiconhttp://cba.itlike.com/public/uploads/10001/20230320/58a7c1f62df4cb1eb47fe83ff0e566e6.pngtext新品首发click$router.push(/category)//van-grid!-- 主会场 --div classmainimg src/assets/main.png alt/div!-- 猜你喜欢 --div classguessp classguess-title—— 猜你喜欢 ——/pdiv classgoods-listGoodsItem v-foritem in 10 :keyitem/GoodsItem/div/div/div
/templatescript
import GoodsItem from /components/GoodsItem.vue
export default {name: HomePage,components: {GoodsItem}
}
/scriptstyle langless scoped
// 主题 padding
.home {padding-top: 100px;padding-bottom: 50px;
}// 导航条样式定制
.van-nav-bar {z-index: 999;background-color: #c21401;::v-deep .van-nav-bar__title {color: #fff;}
}// 搜索框样式定制
.van-search {position: fixed;width: 100%;top: 46px;z-index: 999;
}// 分类导航部分
.my-swipe .van-swipe-item {height: 185px;color: #fff;font-size: 20px;text-align: center;background-color: #39a9ed;
}
.my-swipe .van-swipe-item img {width: 100%;height: 185px;
}// 主会场
.main img {display: block;width: 100%;
}// 猜你喜欢
.guess .guess-title {height: 40px;line-height: 40px;text-align: center;
}// 商品样式
.goods-list {background-color: #f6f6f6;
}
/style新建components/GoodsItem.vue
templatediv classgoods-item click$router.push(/prodetail)div classleftimg src/assets/product.jpg alt //divdiv classrightp classtit text-ellipsis-2三星手机 SAMSUNG Galaxy S23 8GB256GB 超视觉夜拍系统 超清夜景 悠雾紫5G手机 游戏拍照旗舰机s23/pp classcount已售104件/pp classpricespan classnew¥3999.00/spanspan classold¥6699.00/span/p/div/div
/templatescript
export default {}
/scriptstyle langless scoped
.goods-item {height: 148px;margin-bottom: 6px;padding: 10px;background-color: #fff;display: flex;.left {width: 127px;img {display: block;width: 100%;}}.right {flex: 1;font-size: 14px;line-height: 1.3;padding: 10px;display: flex;flex-direction: column;justify-content: space-evenly;.count {color: #999;font-size: 12px;}.price {color: #999;font-size: 16px;.new {color: #f03c3c;margin-right: 10px;}.old {text-decoration: line-through;font-size: 12px;}}}
}
/style
组件按需引入
import { Search, Swipe, SwipeItem, Grid, GridItem } from vantVue.use(GridItem)
Vue.use(Search)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Grid)25. 首页 - 动态渲染
封装准备接口 api/home.js
import request from /utils/request// 获取首页数据
export const getHomeData () {return request.get(/page/detail, {params: {pageId: 0}})
}页面中请求调用
import GoodsItem from /components/GoodsItem.vue
import { getHomeData } from /api/home
export default {name: HomePage,components: {GoodsItem},data () {return {bannerList: [],navList: [],proList: []}},async created () {const { data: { pageData } } await getHomeData()this.bannerList pageData.items[1].datathis.navList pageData.items[3].datathis.proList pageData.items[6].data}
}轮播图、导航、猜你喜欢渲染
!-- 轮播图 --
van-swipe classmy-swipe :autoplay3000 indicator-colorwhitevan-swipe-item v-foritem in bannerList :keyitem.imgUrlimg :srcitem.imgUrl alt/van-swipe-item
/van-swipe!-- 导航 --
van-grid column-num5 icon-size40van-grid-itemv-foritem in navList :keyitem.imgUrl:iconitem.imgUrl:textitem.textclick$router.push(/category)/
/van-grid!-- 猜你喜欢 --
div classguessp classguess-title—— 猜你喜欢 ——/pdiv classgoods-listGoodsItem v-foritem in proList :itemitem :keyitem.goods_id/GoodsItem/div
/div商品组件内动态渲染
templatediv v-ifitem.goods_name classgoods-item click$router.push(/prodetail/${item.goods_id})div classleftimg :srcitem.goods_image alt //divdiv classrightp classtit text-ellipsis-2{{ item.goods_name }}/pp classcount已售 {{ item.goods_sales }}件/pp classpricespan classnew¥{{ item.goods_price_min }}/spanspan classold¥{{ item.goods_price_max }}/span/p/div/div
/templatescript
export default {props: {item: {type: Object,default: () {return {}}}}
}
/script26. 搜索 - 静态布局准备 静态结构和代码
templatediv classsearchvan-nav-bar title商品搜索 left-arrow click-left$router.go(-1) /van-search show-action placeholder请输入搜索关键词 clearabletemplate #actiondiv搜索/div/template/van-search!-- 搜索历史 --div classsearch-historydiv classtitlespan最近搜索/spanvan-icon namedelete-o size16 //divdiv classlistdiv classlist-item click$router.push(/searchlist)炒锅/divdiv classlist-item click$router.push(/searchlist)电视/divdiv classlist-item click$router.push(/searchlist)冰箱/divdiv classlist-item click$router.push(/searchlist)手机/div/div/div/div
/templatescript
export default {name: SearchIndex
}
/scriptstyle langless scoped
.search {.searchBtn {background-color: #fa2209;color: #fff;}::v-deep .van-search__action {background-color: #c21401;color: #fff;padding: 0 20px;border-radius: 0 5px 5px 0;margin-right: 10px;}::v-deep .van-icon-arrow-left {color: #333;}.title {height: 40px;line-height: 40px;font-size: 14px;display: flex;justify-content: space-between;align-items: center;padding: 0 15px;}.list {display: flex;justify-content: flex-start;flex-wrap: wrap;padding: 0 10px;gap: 5%;}.list-item {width: 30%;text-align: center;padding: 7px;line-height: 15px;border-radius: 50px;background: #fff;font-size: 13px;border: 1px solid #efefef;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;margin-bottom: 10px;}
}
/style组件按需导入
import { Icon } from vant
Vue.use(Icon)27. 搜索 - 历史记录 - 基本管理
data 中提供数据和搜索框双向绑定 (实时获取用户内容)
data () {return {search: }
}van-search v-modelsearch show-action placeholder请输入搜索关键词 clearabletemplate #actiondiv搜索/div/template
/van-search准备假数据进行基本的历史纪录渲染
data () {return {...history: [手机, 空调, 白酒, 电视]}
},div classsearch-history v-ifhistory.length 0...div classlistdiv v-foritem in history :keyitem clickgoSearch(item) classlist-item{{ item }}/div/div
/div点击搜索或者下面搜索历史按钮都要进行搜索历史记录更新 (去重新搜索的内容置顶)
div clickgoSearch(search)搜索/divdiv classlistdiv v-foritem in history :keyitem clickgoSearch(item) classlist-item{{ item }}/div
/divgoSearch (key) {const index this.history.indexOf(key)if (index ! -1) {this.history.splice(index, 1)}this.history.unshift(key)this.$router.push(/searchlist?search${key})
}清空历史
van-icon clickclear namedelete-o size16 /clear () {this.history []
}28. 搜索 - 历史记录 - 持久化
持久化到本地 - 封装方法
const HISTORY_KEY hm_history_list// 获取搜索历史
export const getHistoryList () {const result localStorage.getItem(HISTORY_KEY)return result ? JSON.parse(result) : []
}// 设置搜索历史
export const setHistoryList (arr) {localStorage.setItem(HISTORY_KEY, JSON.stringify(arr))
}页面中调用 - 实现持久化
data () {return {search: ,history: getHistoryList()}
},
methods: {goSearch (key) {...setHistoryList(this.history)this.$router.push(/searchlist?search${key})},clear () {this.history []setHistoryList([])this.$toast.success(清空历史成功)}
}29. 搜索列表 - 静态布局 templatediv classsearchvan-nav-bar fixed title商品列表 left-arrow click-left$router.go(-1) /van-searchreadonlyshaperoundbackground#ffffffvalue手机show-actionclick$router.push(/search)template #actionvan-icon classtool nameapps-o //template/van-search!-- 排序选项按钮 --div classsort-btnsdiv classsort-item综合/divdiv classsort-item销量/divdiv classsort-item价格 /div/divdiv classgoods-listGoodsItem v-foritem in 10 :keyitem/GoodsItem/div/div
/templatescript
import GoodsItem from /components/GoodsItem.vue
export default {name: SearchIndex,components: {GoodsItem}
}
/scriptstyle langless scoped
.search {padding-top: 46px;::v-deep .van-icon-arrow-left {color: #333;}.tool {font-size: 24px;height: 40px;line-height: 40px;}.sort-btns {display: flex;height: 36px;line-height: 36px;.sort-item {text-align: center;flex: 1;font-size: 16px;}}
}// 商品样式
.goods-list {background-color: #f6f6f6;
}
/style30. 搜索列表 - 动态渲染
(1) 搜索关键字搜索 计算属性基于query 解析路由参数
computed: {querySearch () {return this.$route.query.search}
}根据不同的情况设置输入框的值
van-search...:valuequerySearch || 搜索商品
/van-searchapi/product.js 封装接口获取搜索商品
import request from /utils/request// 获取搜索商品列表数据
export const getProList (paramsObj) {const { categoryId, goodsName, page } paramsObjreturn request.get(/goods/list, {params: {categoryId,goodsName,page}})
}页面中基于 goodsName 发送请求动态渲染
data () {return {page: 1,proList: []}
},
async created () {const { data: { list } } await getProList({goodsName: this.querySearch,page: this.page})this.proList list.data
}div classgoods-listGoodsItem v-foritem in proList :keyitem.goods_id :itemitem/GoodsItem
/div(2) 分类id搜索 1 封装接口 api/category.js
import request from /utils/request// 获取分类数据
export const getCategoryData () {return request.get(/category/list)
}2 分类页静态结构
templatediv classcategory!-- 分类 --van-nav-bar title全部分类 fixed /!-- 搜索框 --van-searchreadonlyshaperoundbackground#f1f1f2placeholder请输入搜索关键词click$router.push(/search)/!-- 分类列表 --div classlist-boxdiv classleftulli v-for(item, index) in list :keyitem.category_ida :class{ active: index activeIndex } clickactiveIndex index hrefjavascript:;{{ item.name }}/a/li/ul/divdiv classrightdiv click$router.push(/searchlist?categoryId${item.category_id}) v-foritem in list[activeIndex]?.children :keyitem.category_id classcate-goodsimg :srcitem.image?.external_url altp{{ item.name }}/p/div/div/div/div
/templatescript
import { getCategoryData } from /api/category
export default {name: CategoryPage,created () {this.getCategoryList()},data () {return {list: [],activeIndex: 0}},methods: {async getCategoryList () {const { data: { list } } await getCategoryData()this.list list}}
}
/scriptstyle langless scoped
// 主题 padding
.category {padding-top: 100px;padding-bottom: 50px;height: 100vh;.list-box {height: 100%;display: flex;.left {width: 85px;height: 100%;background-color: #f3f3f3;overflow: auto;a {display: block;height: 45px;line-height: 45px;text-align: center;color: #444444;font-size: 12px;.active {color: #fb442f;background-color: #fff;}}}.right {flex: 1;height: 100%;background-color: #ffffff;display: flex;flex-wrap: wrap;justify-content: flex-start;align-content: flex-start;padding: 10px 0;overflow: auto;.cate-goods {width: 33.3%;margin-bottom: 10px;img {width: 70px;height: 70px;display: block;margin: 5px auto;}p {text-align: center;font-size: 12px;}}}}
}// 导航条样式定制
.van-nav-bar {z-index: 999;
}// 搜索框样式定制
.van-search {position: fixed;width: 100%;top: 46px;z-index: 999;
}
/style3 搜索页基于分类 ID 请求
async created () {const { data: { list } } await getProList({categoryId: this.$route.query.categoryId,goodsName: this.querySearch,page: this.page})this.proList list.data
}31. 商品详情 - 静态布局 静态结构 和 样式
templatediv classprodetailvan-nav-bar fixed title商品详情页 left-arrow click-left$router.go(-1) /van-swipe :autoplay3000 changeonChangevan-swipe-item v-for(image, index) in images :keyindeximg :srcimage //van-swipe-itemtemplate #indicatordiv classcustom-indicator{{ current 1 }} / {{ images.length }}/div/template/van-swipe!-- 商品说明 --div classinfodiv classtitlediv classpricespan classnow0.01/spanspan classoldprice6699.00/span/divdiv classsellcount已售1001件/div/divdiv classmsg text-ellipsis-2三星手机 SAMSUNG Galaxy S23 8GB256GB 超视觉夜拍系统 超清夜景 悠雾紫 5G手机 游戏拍照旗舰机s23/divdiv classservicediv classleft-wordsspanvan-icon namepassed /七天无理由退货/spanspanvan-icon namepassed /48小时发货/span/divdiv classright-iconvan-icon namearrow //div/div/div!-- 商品评价 --div classcommentdiv classcomment-titlediv classleft商品评价 (5条)/divdiv classright查看更多 van-icon namearrow / /div/divdiv classcomment-listdiv classcomment-item v-foritem in 3 :keyitemdiv classtopimg srchttp://cba.itlike.com/public/uploads/10001/20230321/a0db9adb2e666a65bc8dd133fbed7834.png altdiv classname神雕大侠/divvan-rate :size16 :value5 color#ffd21e void-iconstar void-color#eee//divdiv classcontent质量很不错 挺喜欢的/divdiv classtime2023-03-21 15:01:35/div/div/div/div!-- 商品描述 --div classdescimg srchttps://uimgproxy.suning.cn/uimg1/sop/commodity/kHgx21fZMWwqirkMhawkAw.jpg altimg srchttps://uimgproxy.suning.cn/uimg1/sop/commodity/0rRMmncfF0kGjuK5cvLolg.jpg altimg srchttps://uimgproxy.suning.cn/uimg1/sop/commodity/2P04A4Jn0HKxbKYSHc17kw.jpg altimg srchttps://uimgproxy.suning.cn/uimg1/sop/commodity/MT4k-mPd0veQXWPPO5yTIw.jpg alt/div!-- 底部 --div classfooterdiv classicon-homevan-icon namewap-home-o /span首页/span/divdiv classicon-cartvan-icon nameshopping-cart-o /span购物车/span/divdiv classbtn-add加入购物车/divdiv classbtn-buy立刻购买/div/div/div
/templatescript
export default {name: ProDetail,data () {return {images: [https://img01.yzcdn.cn/vant/apple-1.jpg,https://img01.yzcdn.cn/vant/apple-2.jpg],current: 0}},methods: {onChange (index) {this.current index}}
}
/scriptstyle langless scoped
.prodetail {padding-top: 46px;::v-deep .van-icon-arrow-left {color: #333;}img {display: block;width: 100%;}.custom-indicator {position: absolute;right: 10px;bottom: 10px;padding: 5px 10px;font-size: 12px;background: rgba(0, 0, 0, 0.1);border-radius: 15px;}.desc {width: 100%;overflow: scroll;::v-deep img {display: block;width: 100%!important;}}.info {padding: 10px;}.title {display: flex;justify-content: space-between;.now {color: #fa2209;font-size: 20px;}.oldprice {color: #959595;font-size: 16px;text-decoration: line-through;margin-left: 5px;}.sellcount {color: #959595;font-size: 16px;position: relative;top: 4px;}}.msg {font-size: 16px;line-height: 24px;margin-top: 5px;}.service {display: flex;justify-content: space-between;line-height: 40px;margin-top: 10px;font-size: 16px;background-color: #fafafa;.left-words {span {margin-right: 10px;}.van-icon {margin-right: 4px;color: #fa2209;}}}.comment {padding: 10px;}.comment-title {display: flex;justify-content: space-between;.right {color: #959595;}}.comment-item {font-size: 16px;line-height: 30px;.top {height: 30px;display: flex;align-items: center;margin-top: 20px;img {width: 20px;height: 20px;}.name {margin: 0 10px;}}.time {color: #999;}}.footer {position: fixed;left: 0;bottom: 0;width: 100%;height: 55px;background-color: #fff;border-top: 1px solid #ccc;display: flex;justify-content: space-evenly;align-items: center;.icon-home, .icon-cart {display: flex;flex-direction: column;align-items: center;justify-content: center;font-size: 14px;.van-icon {font-size: 24px;}}.btn-add,.btn-buy {height: 36px;line-height: 36px;width: 120px;border-radius: 18px;background-color: #ffa900;text-align: center;color: #fff;font-size: 14px;}.btn-buy {background-color: #fe5630;}}
}.tips {padding: 10px;
}
/styleLazyload 是 Vue 指令使用前需要对指令进行注册。
import { Lazyload } from vant
Vue.use(Lazyload)32. 商品详情 - 动态渲染介绍
动态路由参数获取商品 id
computed: {goodsId () {return this.$route.params.id}
},封装 api 接口 api/product.js
// 获取商品详情数据
export const getProDetail (goodsId) {return request.get(/goods/detail, {params: {goodsId}})
}一进入页面发送请求获取商品详情数据
data () {return {images: [https://img01.yzcdn.cn/vant/apple-1.jpg,https://img01.yzcdn.cn/vant/apple-2.jpg],current: 0,detail: {},}
},async created () {this.getDetail()
},methods: {...async getDetail () {const { data: { detail } } await getProDetail(this.goodsId)this.detail detailthis.images detail.goods_images}
}动态渲染
div classprodetail v-ifdetail.goods_namevan-swipe :autoplay3000 changeonChangevan-swipe-item v-for(image, index) in images :keyindeximg v-lazyimage.external_url //van-swipe-itemtemplate #indicatordiv classcustom-indicator{{ current 1 }} / {{ images.length }}/div/template
/van-swipe!-- 商品说明 --
div classinfodiv classtitlediv classpricespan classnow{{ detail.goods_price_min }}/spanspan classoldprice{{ detail.goods_price_max }}/span/divdiv classsellcount已售{{ detail.goods_sales }}件/div/divdiv classmsg text-ellipsis-2{{ detail.goods_name }}/divdiv classservicediv classleft-wordsspanvan-icon namepassed /七天无理由退货/spanspanvan-icon namepassed /48小时发货/span/divdiv classright-iconvan-icon namearrow //div/div
/div!-- 商品描述 --
div classtips商品描述/div
div classdesc v-htmldetail.content/div33. 商品详情 - 动态渲染评价
封装接口 api/product.js
// 获取商品评价
export const getProComments (goodsId, limit) {return request.get(/comment/listRows, {params: {goodsId,limit}})
}页面调用获取数据
import defaultImg from /assets/default-avatar.pngdata () {return {...total: 0,commentList: [],defaultImg
},async created () {...this.getComments()
},async getComments () {const { data: { list, total } } await getProComments(this.goodsId, 3)this.commentList listthis.total total
},动态渲染评价
!-- 商品评价 --
div classcomment v-iftotal 0div classcomment-titlediv classleft商品评价 ({{ total }}条)/divdiv classright查看更多 van-icon namearrow / /div/divdiv classcomment-listdiv classcomment-item v-foritem in commentList :keyitem.comment_iddiv classtopimg :srcitem.user.avatar_url || defaultImg altdiv classname{{ item.user.nick_name }}/divvan-rate :size16 :valueitem.score / 2 color#ffd21e void-iconstar void-color#eee//divdiv classcontent{{ item.content }}/divdiv classtime{{ item.create_time }}/div/div /div
/div34. 加入购物车 - 唤起弹窗 按需导入 van-action-sheet
import { ActionSheet } from vant
Vue.use(ActionSheet)准备 van-action-sheet 基本结构
van-action-sheet v-modelshowPannel :titlemode cart ? 加入购物车 : 立刻购买111
/van-action-sheetdata () {return {...mode: cartshowPannel: false}
},注册点击事件点击时唤起弹窗
div classbtn-add clickaddFn加入购物车/div
div classbtn-buy clickbuyFn立刻购买/divaddFn () {this.mode cartthis.showPannel true
},
buyFn () {this.mode buyNowthis.showPannel true
}完善结构
van-action-sheet v-modelshowPannel :titlemode cart ? 加入购物车 : 立刻购买div classproductdiv classproduct-titlediv classleftimg srchttp://cba.itlike.com/public/uploads/10001/20230321/8f505c6c437fc3d4b4310b57b1567544.jpg alt/divdiv classrightdiv classpricespan¥/spanspan classnowprice9.99/span/divdiv classcountspan库存/spanspan55/span/div/div/divdiv classnum-boxspan数量/span数字框占位/divdiv classshowbtn v-iftruediv classbtn v-iftrue加入购物车/divdiv classbtn now v-else立刻购买/div/divdiv classbtn-none v-else该商品已抢完/div/div
/van-action-sheet.product {.product-title {display: flex;.left {img {width: 90px;height: 90px;}margin: 10px;}.right {flex: 1;padding: 10px;.price {font-size: 14px;color: #fe560a;.nowprice {font-size: 24px;margin: 0 5px;}}}}.num-box {display: flex;justify-content: space-between;padding: 10px;align-items: center;}.btn, .btn-none {height: 40px;line-height: 40px;margin: 20px;border-radius: 20px;text-align: center;color: rgb(255, 255, 255);background-color: rgb(255, 148, 2);}.btn.now {background-color: #fe5630;}.btn-none {background-color: #cccccc;}
}动态渲染
van-action-sheet v-modelshowPannel :titlemode cart ? 加入购物车 : 立刻购买div classproductdiv classproduct-titlediv classleftimg :srcdetail.goods_image alt/divdiv classrightdiv classpricespan¥/spanspan classnowprice{{ detail.goods_price_min }}/span/divdiv classcountspan库存/spanspan{{ detail.stock_total }}/span/div/div/divdiv classnum-boxspan数量/span数字框组件/divdiv classshowbtn v-ifdetail.stock_total 0div classbtn v-ifmode cart加入购物车/divdiv classbtn now v-ifmode buyNow立刻购买/div/divdiv classbtn-none v-else该商品已抢完/div/div
/van-action-sheet35. 加入购物车 - 封装数字框组件 封装组件 components/CountBox.vue
templatediv classcount-boxbutton clickhandleSub classminus-/buttoninput :valuevalue changehandleChange classinp typetextbutton clickhandleAdd classadd/button/div
/templatescript
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)}}
}
/scriptstyle langless 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
使用组件
import CountBox from /components/CountBox.vueexport default {name: ProDetail,components: {CountBox},data () {return {addCount: 1...}},
}div classnum-boxspan数量/spanCountBox v-modeladdCount/CountBox
/div36. 加入购物车 - 判断 token 登录提示
说明加入购物车是一个登录后的用户才能进行的操作所以需要进行鉴权判断判断用户 token 是否存在
若存在继续加入购物车操作不存在提示用户未登录引导到登录页 按需注册 dialog 组件
import { Dialog } from vant
Vue.use(Dialog)按钮注册点击事件
div classbtn v-ifmode cart clickaddCart加入购物车/div添加 token 鉴权判断跳转携带回跳地址
async addCart () {// 判断用户是否有登录if (!this.$store.getters.token) {this.$dialog.confirm({title: 温馨提示,message: 此时需要先登录才能继续操作哦,confirmButtonText: 去登录,cancelButtonText: 再逛逛}).then(() {this.$router.replace({path: /login,query: {backUrl: this.$route.fullPath}})}).catch(() {})return}console.log(进行加入购物车操作)
}登录后若有回跳地址则回跳页面
// 判断有无回跳地址
const url this.$route.query.backUrl || /
this.$router.replace(url)37. 加入购物车 - 封装接口进行请求 封装接口 api/cart.js
// 加入购物车
export const addCart (goodsId, goodsNum, goodsSkuId) {return request.post(/cart/add, {goodsId,goodsNum,goodsSkuId})
}页面中调用请求
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
},请求拦截器中统一携带 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)
})准备小图标
div classicon-cartspan v-ifcartTotal 0 classnum{{ cartTotal }}/spanvan-icon nameshopping-cart-o /span购物车/span
/div定制样式
.footer .icon-cart {position: relative;padding: 0 6px;.num {z-index: 999;position: absolute;top: -2px;right: 0;min-width: 16px;padding: 0 4px;color: #fff;text-align: center;background-color: #ee0a24;border-radius: 50%;}
}38. 购物车 - 静态布局 基本结构
templatediv classcartvan-nav-bar title购物车 fixed /!-- 购物车开头 --div classcart-titlespan classall共i4/i件商品/spanspan classeditvan-icon nameedit /编辑/span/div!-- 购物车列表 --div classcart-listdiv classcart-item v-foritem in 10 :keyitemvan-checkbox/van-checkboxdiv classshowimg srchttp://cba.itlike.com/public/uploads/10001/20230321/a072ef0eef1648a5c4eae81fad1b7583.jpg alt/divdiv classinfospan classtit text-ellipsis-2新Pad 14英寸 12128 远峰蓝 M6平板电脑 智能安卓娱乐十核游戏学习二合一 低蓝光护眼超清4K全面三星屏5GWIFI全网通 蓝魔快本平板/spanspan classbottomdiv classprice¥ span1247.04/span/divdiv classcount-boxbutton classminus-/buttoninput classinp :value4 typetext readonlybutton classadd/button/div/span/div/div/divdiv classfooter-fixeddiv classall-checkvan-checkbox icon-size18/van-checkbox全选/divdiv classall-totaldiv classpricespan合计/spanspan¥ i classtotalPrice99.99/i/span/divdiv v-iftrue classgoPay结算(5)/divdiv v-else classdelete删除/div/div/div/div
/templatescript
export default {name: CartPage
}
/scriptstyle langless scoped
// 主题 padding
.cart {padding-top: 46px;padding-bottom: 100px;background-color: #f5f5f5;min-height: 100vh;.cart-title {height: 40px;display: flex;justify-content: space-between;align-items: center;padding: 0 10px;font-size: 14px;.all {i {font-style: normal;margin: 0 2px;color: #fa2209;font-size: 16px;}}.edit {.van-icon {font-size: 18px;}}}.cart-item {margin: 0 10px 10px 10px;padding: 10px;display: flex;justify-content: space-between;background-color: #ffffff;border-radius: 5px;.show img {width: 100px;height: 100px;}.info {width: 210px;padding: 10px 5px;font-size: 14px;display: flex;flex-direction: column;justify-content: space-between;.bottom {display: flex;justify-content: space-between;.price {display: flex;align-items: flex-end;color: #fa2209;font-size: 12px;span {font-size: 16px;}}.count-box {display: flex;width: 110px;.add,.minus {width: 30px;height: 30px;outline: none;border: none;}.inp {width: 40px;height: 30px;outline: none;border: none;background-color: #efefef;text-align: center;margin: 0 5px;}}}}}
}.footer-fixed {position: fixed;left: 0;bottom: 50px;height: 50px;width: 100%;border-bottom: 1px solid #ccc;background-color: #fff;display: flex;justify-content: space-between;align-items: center;padding: 0 10px;.all-check {display: flex;align-items: center;.van-checkbox {margin-right: 5px;}}.all-total {display: flex;line-height: 36px;.price {font-size: 14px;margin-right: 10px;.totalPrice {color: #fa2209;font-size: 18px;font-style: normal;}}.goPay, .delete {min-width: 100px;height: 36px;line-height: 36px;text-align: center;background-color: #fa2f21;color: #fff;border-radius: 18px;.disabled {background-color: #ff9779;}}}}
/style按需导入组件
import { Checkbox } from vant
Vue.use(Checkbox)39. 购物车 - 构建 vuex 模块 - 获取数据存储 新建 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/cartVue.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)}
},40. 购物车 - mapState - 渲染购物车列表
将数据映射到页面
import { mapState } from vuexcomputed: {...mapState(cart, [cartList])
}动态渲染
!-- 购物车列表 --
div classcart-listdiv classcart-item v-foritem in cartList :keyitem.goods_idvan-checkbox icon-size18 :valueitem.isChecked/van-checkboxdiv classshow click$router.push(/prodetail/${item.goods_id})img :srcitem.goods.goods_image alt/divdiv classinfospan classtit text-ellipsis-2{{ item.goods.goods_name }}/spanspan classbottomdiv classprice¥ span{{ item.goods.goods_price_min }}/span/divCountBox :valueitem.goods_num/CountBox/span/div/div
/div41. 购物车 - 封装 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 classcart-titlespan classall共i{{ cartTotal || 0 }}/i件商品/spanspan classeditvan-icon nameedit /编辑/span
/divdiv classfooter-fixeddiv classall-checkvan-checkbox icon-size18/van-checkbox全选/divdiv classall-totaldiv classpricespan合计/spanspan¥ i classtotalPrice{{ selPrice }}/i/span/divdiv v-iftrue :class{ disabled: selCount 0 } classgoPay结算({{ selCount }})/divdiv v-else :class{ disabled: selCount 0 } classdelete删除({{ selCount }})/div/div
/div42. 购物车 - 全选反选功能
全选 getters
getters: {isAllChecked (state) {return state.cartList.every(item item.isChecked)}
}...mapGetters(cart, [isAllChecked]),div classall-checkvan-checkbox :valueisAllChecked icon-size18/van-checkbox全选
/div点击小选修改状态
van-checkbox clicktoggleCheck(item.goods_id) .../van-checkboxtoggleCheck (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 clicktoggleAllCheck classall-checkvan-checkbox :valueisAllChecked icon-size18/van-checkbox全选
/divtoggleAllCheck () {this.$store.commit(cart/toggleAllCheck, !this.isAllChecked)
},mutations: {toggleAllCheck (state, flag) {state.cartList.forEach(item {item.isChecked flag})},
}43. 购物车 - 数字框修改数量
封装 api 接口
// 更新购物车商品数量
export const changeCount (goodsId, goodsNum, goodsSkuId) {return request.post(/cart/update, {goodsId,goodsNum,goodsSkuId})
}页面中注册点击事件传递数据
CountBox :valueitem.goods_num inputvalue changeCount(value, item.goods_id, item.goods_sku_id)/CountBoxchangeCount (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)},
}44. 购物车 - 编辑切换状态
data 提供数据, 定义是否在编辑删除的状态
data () {return {isEdit: false}
},注册点击事件修改状态
span classedit clickisEdit !isEditvan-icon nameedit /编辑
/span底下按钮根据状态变化
div v-if!isEdit :class{ disabled: selCount 0 } classgoPay去结算{{ selCount }}
/div
div v-else :class{ disabled: selCount 0 } classdelete删除/div监视编辑状态动态控制复选框状态
watch: {isEdit (value) {if (value) {this.$store.commit(cart/toggleAllCheck, false)} else {this.$store.commit(cart/toggleAllCheck, true)}}
}45. 购物车 - 删除功能完成
查看接口封装 API ( 注意此处 id 为获取回来的购物车数据的 id )
// 删除购物车
export const delSelect (cartIds) {return request.post(/cart/clear, {cartIds})
}注册删除点击事件
div v-else :class{ disabled: selCount 0 } clickhandleDel classdelete删除({{ selCount }})
/divasync 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)}
},46. 购物车 - 空购物车处理
外面包个大盒子添加 v-if 判断
div classcart-box v-ifisLogin cartList.length 0!-- 购物车开头 --div classcart-title.../div!-- 购物车列表 --div classcart-list.../divdiv classfooter-fixed.../div
/divdiv classempty-cart v-elseimg src/assets/empty.png altdiv classtips您的购物车是空的, 快去逛逛吧/divdiv classbtn click$router.push(/)去逛逛/div
/div相关样式
.empty-cart {padding: 80px 30px;img {width: 140px;height: 92px;display: block;margin: 0 auto;}.tips {text-align: center;color: #666;margin: 30px;}.btn {width: 110px;height: 32px;line-height: 32px;text-align: center;background-color: #fa2c20;border-radius: 16px;color: #fff;display: block;margin: 0 auto;}
}47. 订单结算台
所谓的 “立即结算”本质就是跳转到订单结算台并且跳转的同时需要携带上对应的订单参数。
而具体需要哪些参数就需要基于 【订单结算台】 的需求来定。
(1) 静态布局 准备静态页面
templatediv classpayvan-nav-bar fixed title订单结算台 left-arrow click-left$router.go(-1) /!-- 地址相关 --div classaddressdiv classleft-iconvan-icon namelogistics //divdiv classinfo v-iftruediv classinfo-contentspan classname小红/spanspan classmobile13811112222/span/divdiv classinfo-address江苏省 无锡市 南长街 110号 504/div/divdiv classinfo v-else请选择配送地址/divdiv classright-iconvan-icon namearrow //div/div!-- 订单明细 --div classpay-listdiv classlistdiv classgoods-itemdiv classleftimg srchttp://cba.itlike.com/public/uploads/10001/20230321/8f505c6c437fc3d4b4310b57b1567544.jpg alt //divdiv classrightp classtit text-ellipsis-2三星手机 SAMSUNG Galaxy S23 8GB256GB 超视觉夜拍系统 超清夜景 悠雾紫 5G手机 游戏拍照旗舰机s23/pp classinfospan classcountx3/spanspan classprice¥9.99/span/p/div/div/divdiv classflow-num-boxspan共 12 件商品合计/spanspan classmoney1219.00/span/divdiv classpay-detaildiv classpay-cellspan订单总金额/spanspan classred1219.00/span/divdiv classpay-cellspan优惠券/spanspan无优惠券可用/span/divdiv classpay-cellspan配送费用/spanspan v-iffalse请先选择配送地址/spanspan v-else classred0.00/span/div/div!-- 支付方式 --div classpay-wayspan classtit支付方式/spandiv classpay-cellspanvan-icon namebalance-o /余额支付可用 ¥ 999919.00 元/span!-- span请先选择配送地址/span --span classredvan-icon namepassed //span/div/div!-- 买家留言 --div classbuytipstextarea placeholder选填买家留言50字内 name id cols30 rows10/textarea/div/div!-- 底部提交 --div classfooter-fixeddiv classleft实付款span999919/span/divdiv classtipsbtn提交订单/div/div/div
/templatescript
export default {name: PayIndex,data () {return {}},methods: {}
}
/scriptstyle langless scoped
.pay {padding-top: 46px;padding-bottom: 46px;::v-deep {.van-nav-bar__arrow {color: #333;}}
}
.address {display: flex;align-items: center;justify-content: flex-start;padding: 20px;font-size: 14px;color: #666;position: relative;background: url(/assets/border-line.png) bottom repeat-x;background-size: 60px auto;.left-icon {margin-right: 20px;}.right-icon {position: absolute;right: 20px;top: 50%;transform: translateY(-7px);}
}
.goods-item {height: 100px;margin-bottom: 6px;padding: 10px;background-color: #fff;display: flex;.left {width: 100px;img {display: block;width: 80px;margin: 10px auto;}}.right {flex: 1;font-size: 14px;line-height: 1.3;padding: 10px;padding-right: 0px;display: flex;flex-direction: column;justify-content: space-evenly;color: #333;.info {margin-top: 5px;display: flex;justify-content: space-between;.price {color: #fa2209;}}}
}.flow-num-box {display: flex;justify-content: flex-end;padding: 10px 10px;font-size: 14px;border-bottom: 1px solid #efefef;.money {color: #fa2209;}
}.pay-cell {font-size: 14px;padding: 10px 12px;color: #333;display: flex;justify-content: space-between;.red {color: #fa2209;}
}
.pay-detail {border-bottom: 1px solid #efefef;
}.pay-way {font-size: 14px;padding: 10px 12px;border-bottom: 1px solid #efefef;color: #333;.tit {line-height: 30px;}.pay-cell {padding: 10px 0;}.van-icon {font-size: 20px;margin-right: 5px;}
}.buytips {display: block;textarea {display: block;width: 100%;border: none;font-size: 14px;padding: 12px;height: 100px;}
}.footer-fixed {position: fixed;background-color: #fff;left: 0;bottom: 0;width: 100%;height: 46px;line-height: 46px;border-top: 1px solid #efefef;font-size: 14px;display: flex;.left {flex: 1;padding-left: 12px;color: #666;span {color:#fa2209;}}.tipsbtn {width: 121px;background: linear-gradient(90deg,#f9211c,#ff6335);color: #fff;text-align: center;line-height: 46px;display: block;font-size: 14px;}
}
/style(2) 获取收货地址列表
1 封装获取地址的接口
import request from /utils/request// 获取地址列表
export const getAddressList () {return request.get(/address/list)
}2 页面中 - 调用获取地址
data () {return {addressList: []}
},
computed: {selectAddress () {// 这里地址管理不是主线业务直接获取默认第一条地址return this.addressList[0] }
},
async created () {this.getAddressList()
},
methods: {async getAddressList () {const { data: { list } } await getAddressList()this.addressList list}
}3 页面中 - 进行渲染
computed: {longAddress () {const region this.selectAddress.regionreturn region.province region.city region.region this.selectAddress.detail}
},div classinfo v-ifselectAddress?.address_iddiv classinfo-contentspan classname{{ selectAddress.name }}/spanspan classmobile{{ selectAddress.phone }}/span/divdiv classinfo-address{{ longAddress }}/div
/div(3) 订单结算 - 封装通用接口
**思路分析**这里的订单结算有两种情况 购物车结算需要两个参数 ① mode“cart” ② cartIds“cartId, cartId” 立即购买结算需要三个参数 ① mode“buyNow” ② goodsId“商品id” ③ goodsSkuId“商品skuId”
都需要跳转时将参数传递过来 封装通用 API 接口 api/order
import request from /utils/requestexport const checkOrder (mode, obj) {return request.get(/checkout/order, {params: {mode,delivery: 0,couponId: 0,isUsePoints: 0,...obj}})
}(4) 订单结算 - 购物车结算
1 跳转时传递查询参数
layout/cart.vue
div clickgoPay结算({{ selCount }})/divgoPay () {if (this.selCount 0) {this.$router.push({path: /pay,query: {mode: cart,cartIds: this.selCartList.map(item item.id).join(,)}})}
}2 页面中接收参数, 调用接口获取数据
data () {return {order: {},personal: {}}
},computed: {mode () {return this.$route.query.mode},cartIds () {return this.$route.query.cartIds}
}async created () {this.getOrderList()
},async getOrderList () {if (this.mode cart) {const { data: { order, personal } } await checkOrder(this.mode, { cartIds: this.cartIds })this.order orderthis.personal personal}
}3 基于数据进行渲染
!-- 订单明细 --
div classpay-list v-iforder.goodsListdiv classlistdiv classgoods-item v-foritem in order.goodsList :keyitem.goods_iddiv classleftimg :srcitem.goods_image alt //divdiv classrightp classtit text-ellipsis-2{{ item.goods_name }}/pp classinfospan classcountx{{ item.total_num }}/spanspan classprice¥{{ item.total_pay_price }}/span/p/div/div/divdiv classflow-num-boxspan共 {{ order.orderTotalNum }} 件商品合计/spanspan classmoney{{ order.orderTotalPrice }}/span/divdiv classpay-detaildiv classpay-cellspan订单总金额/spanspan classred{{ order.orderTotalPrice }}/span/divdiv classpay-cellspan优惠券/spanspan无优惠券可用/span/divdiv classpay-cellspan配送费用/spanspan v-if!selectAddress请先选择配送地址/spanspan v-else classred0.00/span/div/div!-- 支付方式 --div classpay-wayspan classtit支付方式/spandiv classpay-cellspanvan-icon namebalance-o /余额支付可用 ¥ {{ personal.balance }} 元/span!-- span请先选择配送地址/span --span classredvan-icon namepassed //span/div/div!-- 买家留言 --div classbuytipstextarea placeholder选填买家留言50字内 name id cols30 rows10/textarea/div
/div!-- 底部提交 --
div classfooter-fixeddiv classleft实付款span{{ order.orderTotalPrice }}/span/divdiv classtipsbtn提交订单/div
/div(5) 订单结算 - 立即购买结算
1 点击跳转传参
prodetail/index.vue
div classbtn v-ifmode buyNow clickgoBuyNow立刻购买/divgoBuyNow () {this.$router.push({path: /pay,query: {mode: buyNow,goodsId: this.goodsId,goodsSkuId: this.detail.skuList[0].goods_sku_id,goodsNum: this.addCount}})
}2 计算属性处理参数
computed: {...goodsId () {return this.$route.query.goodsId},goodsSkuId () {return this.$route.query.goodsSkuId},goodsNum () {return this.$route.query.goodsNum}
}3 基于请求时携带参数发请求渲染
async getOrderList () {...if (this.mode buyNow) {const { data: { order, personal } } await checkOrder(this.mode, {goodsId: this.goodsId,goodsSkuId: this.goodsSkuId,goodsNum: this.goodsNum})this.order orderthis.personal personal}
}(6) mixins 复用 - 处理登录确认框的弹出
1 新建一个 mixin 文件 mixins/loginConfirm.js
export default {methods: {// 是否需要弹登录确认框// (1) 需要返回 true并直接弹出登录确认框// (2) 不需要返回 falseloginConfirm () {if (!this.$store.getters.token) {this.$dialog.confirm({title: 温馨提示,message: 此时需要先登录才能继续操作哦,confirmButtonText: 去登陆,cancelButtonText: 再逛逛}).then(() {// 如果希望跳转到登录 登录后能回跳回来需要在跳转去携带参数 (当前的路径地址)// this.$route.fullPath (会包含查询参数)this.$router.replace({path: /login,query: {backUrl: this.$route.fullPath}})}).catch(() {})return true}return false}}
}
2 页面中导入混入方法
import loginConfirm from /mixins/loginConfirmexport default {name: ProDetail,mixins: [loginConfirm],...
}3 页面中调用 混入的方法
async addCart () {if (this.loginConfirm()) {return}const { data } await addCart(this.goodsId, this.addCount, this.detail.skuList[0].goods_sku_id)this.cartTotal data.cartTotalthis.$toast(加入购物车成功)this.showPannel falseconsole.log(this.cartTotal)
},goBuyNow () {if (this.loginConfirm()) {return}this.$router.push({path: /pay,query: {mode: buyNow,goodsId: this.goodsId,goodsSkuId: this.detail.skuList[0].goods_sku_id,goodsNum: this.addCount}})
}48. 提交订单并支付
1 封装 API 通用方法统一余额支付
// 提交订单
export const submitOrder (mode, params) {return request.post(/checkout/submit, {mode,delivery: 10, // 物流方式 配送方式 (10快递配送 20门店自提)couponId: 0, // 优惠券 idpayType: 10, // 余额支付isUsePoints: 0, // 是否使用积分...params})
}2 买家留言绑定
data () {return {remark: }
},
div classbuytipstextarea v-modelremark placeholder选填买家留言50字内 name id cols30 rows10/textarea
/div3 注册点击事件提交订单并支付
div classtipsbtn clicksubmitOrder提交订单/div// 提交订单
async submitOrder () {if (this.mode cart) {await submitOrder(this.mode, {remark: this.remark,cartIds: this.cartIds})}if (this.mode buyNow) {await submitOrder(this.mode, {remark: this.remark,goodsId: this.goodsId,goodsSkuId: this.goodsSkuId,goodsNum: this.goodsNum})}this.$toast.success(支付成功)this.$router.replace(/myorder)
}49. 订单管理
(1) 静态布局
1 基础静态结构
templatediv classordervan-nav-bar title我的订单 left-arrow click-left$router.go(-1) /van-tabs v-modelactivevan-tab title全部/van-tabvan-tab title待支付/van-tabvan-tab title待发货/van-tabvan-tab title待收货/van-tabvan-tab title待评价/van-tab/van-tabsOrderListItem/OrderListItem/div
/templatescript
import OrderListItem from /components/OrderListItem.vue
export default {name: OrderPage,components: {OrderListItem},data () {return {active: 0}}
}
/scriptstyle langless scoped
.order {background-color: #fafafa;
}
.van-tabs {position: sticky;top: 0;
}
/style2 components/OrderListItem
templatediv classorder-list-itemdiv classtitdiv classtime2023-07-01 12:02:13/divdiv classstatusspan待支付/span/div/divdiv classlistdiv classlist-itemdiv classgoods-imgimg srchttp://cba.itlike.com/public/uploads/10001/20230321/c4b5c61e46489bb9b9c0630002fbd69e.jpg alt/divdiv classgoods-content text-ellipsis-2Apple iPhone 14 Pro Max 256G 银色 移动联通电信5G双卡双待手机/divdiv classgoods-tradep¥ 1299.00/ppx 3/p/div/divdiv classlist-itemdiv classgoods-imgimg srchttp://cba.itlike.com/public/uploads/10001/20230321/c4b5c61e46489bb9b9c0630002fbd69e.jpg alt/divdiv classgoods-content text-ellipsis-2Apple iPhone 14 Pro Max 256G 银色 移动联通电信5G双卡双待手机/divdiv classgoods-tradep¥ 1299.00/ppx 3/p/div/divdiv classlist-itemdiv classgoods-imgimg srchttp://cba.itlike.com/public/uploads/10001/20230321/c4b5c61e46489bb9b9c0630002fbd69e.jpg alt/divdiv classgoods-content text-ellipsis-2Apple iPhone 14 Pro Max 256G 银色 移动联通电信5G双卡双待手机/divdiv classgoods-tradep¥ 1299.00/ppx 3/p/div/div/divdiv classtotal共12件商品总金额 ¥29888.00/divdiv classactionsspan v-iffalse立刻付款/spanspan v-iftrue申请取消/spanspan v-iffalse确认收货/spanspan v-iffalse评价/span/div/div
/templatescript
export default {}
/scriptstyle langless scoped
.order-list-item {margin: 10px auto;width: 94%;padding: 15px;background-color: #ffffff;box-shadow: 0 0.5px 2px 0 rgba(0,0,0,.05);border-radius: 8px;color: #333;font-size: 13px;.tit {height: 24px;line-height: 24px;display: flex;justify-content: space-between;margin-bottom: 20px;.status {color: #fa2209;}}.list-item {display: flex;.goods-img {width: 90px;height: 90px;margin: 0px 10px 10px 0;img {width: 100%;height: 100%;}}.goods-content {flex: 2;line-height: 18px;max-height: 36px;margin-top: 8px;}.goods-trade {flex: 1;line-height: 18px;text-align: right;color: #b39999;margin-top: 8px;}}.total {text-align: right;}.actions {text-align: right;span {display: inline-block;height: 28px;line-height: 28px;color: #383838;border: 0.5px solid #a8a8a8;font-size: 14px;padding: 0 15px;border-radius: 5px;margin: 10px 0;}}
}
/style3 导入注册
import { Tab, Tabs } from vant
Vue.use(Tab)
Vue.use(Tabs)(2) 点击 tab 切换渲染
1 封装获取订单列表的 API 接口
// 订单列表
export const getMyOrderList (dataType, page) {return request.get(/order/list, {params: {dataType,page}})
}2 给 tab 绑定 name 属性
van-tabs v-modelactive stickyvan-tab nameall title全部/van-tabvan-tab namepayment title待支付/van-tabvan-tab namedelivery title待发货/van-tabvan-tab namereceived title待收货/van-tabvan-tab namecomment title待评价/van-tab
/van-tabsdata () {return {active: this.$route.query.dataType || all,page: 1,list: []}
},3 封装调用接口获取数据
methods: {async getOrderList () {const { data: { list } } await getMyOrderList(this.active, this.page)list.data.forEach((item) {item.total_num 0item.goods.forEach(goods {item.total_num goods.total_num})})this.list list.data}
},
watch: {active: {immediate: true,handler () {this.getOrderList()}}
}4 动态渲染
OrderListItem v-foritem in list :keyitem.order_id :itemitem/OrderListItemtemplatediv classorder-list-item v-ifitem.order_iddiv classtitdiv classtime{{ item.create_time }}/divdiv classstatusspan{{ item.state_text }}/span/div/divdiv classlist div classlist-item v-for(goods, index) in item.goods :keyindexdiv classgoods-imgimg :srcgoods.goods_image alt/divdiv classgoods-content text-ellipsis-2{{ goods.goods_name }}/divdiv classgoods-tradep¥ {{ goods.total_pay_price }}/ppx {{ goods.total_num }}/p/div/div/divdiv classtotal共 {{ item.total_num }} 件商品总金额 ¥{{ item.total_price }}/divdiv classactionsdiv v-ifitem.order_status 10span v-ifitem.pay_status 10立刻付款/spanspan v-else-ifitem.delivery_status 10申请取消/spanspan v-else-ifitem.delivery_status 20 || item.delivery_status 30确认收货/span/divdiv v-ifitem.order_status 30span评价/span/div/div/div
/templatescript
export default {props: {item: {type: Object,default: () {return {}}}}
}
/script50. 个人中心 - 基本渲染
1 封装获取个人信息 - API接口
import request from /utils/request// 获取个人信息
export const getUserInfoDetail () {return request.get(/user/info)
}2 调用接口获取数据进行渲染
templatediv classuserdiv classhead-page v-ifisLogindiv classhead-imgimg src/assets/default-avatar.png alt //divdiv classinfodiv classmobile{{ detail.mobile }}/divdiv classvipvan-icon namediamond-o /普通会员/div/div/divdiv v-else classhead-page click$router.push(/login)div classhead-imgimg src/assets/default-avatar.png alt //divdiv classinfodiv classmobile未登录/divdiv classwords点击登录账号/div/div/divdiv classmy-assetdiv classasset-leftdiv classasset-left-itemspan{{ detail.pay_money || 0 }}/spanspan账户余额/span/divdiv classasset-left-itemspan0/spanspan积分/span/divdiv classasset-left-itemspan0/spanspan优惠券/span/div/divdiv classasset-rightdiv classasset-right-itemvan-icon namebalance-pay /span我的钱包/span/div/div/divdiv classorder-navbardiv classorder-navbar-item click$router.push(/myorder?dataTypeall)van-icon namebalance-list-o /span全部订单/span/divdiv classorder-navbar-item click$router.push(/myorder?dataTypepayment)van-icon nameclock-o /span待支付/span/divdiv classorder-navbar-item click$router.push(/myorder?dataTypedelivery)van-icon namelogistics /span待发货/span/divdiv classorder-navbar-item click$router.push(/myorder?dataTypereceived)van-icon namesend-gift-o /span待收货/span/div/divdiv classservicediv classtitle我的服务/divdiv classcontentdiv classcontent-itemvan-icon namerecords /span收货地址/span/divdiv classcontent-itemvan-icon namegift-o /span领券中心/span/divdiv classcontent-itemvan-icon namegift-card-o /span优惠券/span/divdiv classcontent-itemvan-icon namequestion-o /span我的帮助/span/divdiv classcontent-itemvan-icon namebalance-o /span我的积分/span/divdiv classcontent-itemvan-icon namerefund-o /span退换/售后/span/div/div/divdiv classlogout-btnbutton退出登录/button/div/div
/templatescript
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)}}
}
/scriptstyle langless 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;}
}
/style51. 个人中心 - 退出功能
1 注册点击事件
button clicklogout退出登录/button2 提供方法
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 })}
},52. 项目打包优化
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) 路由懒加载
路由懒加载 异步组件 不会一上来就将所有的组件都加载而是访问到对应的路由了才加载解析这个路由对应的所有组件
官网链接跳转 当打包构建应用时JavaScript 包会变得非常大影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块然后当路由被访问的时候才加载对应组件这样就更加高效了。 const ProDetail () import(/views/prodetail)
const Pay () import(/views/pay)
const MyOrder () import(/views/myorder)到此Vue实战项目就告一段落学海无涯需要不断的努力加油