- 收货人:{{ curAddress.receiver }}
- 联系方式:{{ curAddress.contact }}
- 收货地址:{{ curAddress.fullLocation }} {{ curAddress.address }}
目录
项目学习
初始化项目
建立项目
引入elementplus
elementPlus主题设置
配置axios
路由
引入静态资源
自动导入scss变量
Layout页
组件结构快速搭建
字体图标渲染
一级导航渲染
吸顶导航交互实现
Pinia优化重复请求
Home页
分类实现
新鲜好物实现
人气推荐实现
懒加载指令实现
产品列表实现
GoodsItem组件封装
一级分类页
导航栏
分类Banner渲染
导航激活
分类列表渲染
路由缓存问题
业务逻辑的函数拆分
二级分类页
实现
定制路由的滚动行为
商品详情
路由配置
数据渲染
热榜区域
适配热榜类型
图片预览组件封装
sku插件
登录页
表单
登录实现
登录失败的提示
Pinia管理用户
Pinia持久化存储
登录和非登录状态
请求拦截器携带token
退出登录
token失效处理
购物车
本地加入购物车
头部购物车
购物车页面
订单页
支付页
会员中心
基本页面
个人中心信息渲染
我的订单
分页
视频:黑马程序员-小兔鲜
黑马资料:文档
vue调试:vue-devtools
DEV:HBuilder
安装node
安装过node的需要查看node的版本是否大于或等于15,否则报错
Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree.
windows下更新node需要重新安装,即覆盖原有的node。
查看镜像地址
npm config get registry
设置淘宝镜像地址
npm config set registry https://registry.npm.taobao.org/
安装脚手架
npm i -g @vue/cli
下载create-vue
npm install [email protected]
注意版本,或者安装最新版。
初始化项目
安装依赖
npm install
配置别名路径
在根目录下新建jsconfig.json
{
"compilerOptions" : {
"baseUrl" : "./",
"paths" : {
"@/*":["src/*"]
}
}
}
官网
// 引入插件
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
// 配置插件
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
]
})
vit.config.js
Primary
测试以下elementplus是否生效。
在app.vue的template下引入
Primary
重启运行。
测试完成。
安装sass
npm i sass -D
样式文件
styles/element/index.scss
/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
// 主色
'base': #27ba9b,
),
'success': (
// 成功色
'base': #1dc779,
),
'warning': (
// 警告色
'base': #ffb302,
),
'danger': (
// 危险色
'base': #e26237,
),
'error': (
// 错误色
'base': #cf4444,
),
)
)
配置
vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
//elementplus按需导入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [
//配置elementPlus采用sass样式配色系统
ElementPlusResolver({importStyle:"sass"}),
],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
css: {
preprocessorOptions: {
scss: {
// 自动导入定制化样式文件进行样式覆盖
additionalData: `
@use "@/styles/element/index.scss" as *;
`,
}
},
}
})
测试
重启运行
安装axios
npm i axios
配置
utils/http.js
import axios from 'axios'
// 创建axios实例
let http = axios.create({
baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',
timeout: 5000
})
// axios请求拦截器
http.interceptors.request.use(config => {
return config
}, e => Promise.reject(e))
// axios响应式拦截器
http.interceptors.response.use(res => res.data, e => {
return Promise.reject(e)
})
export default http
测试接口
api/testAPI.js下
import http from '@/utils/http'
export function getCategory () {
return http({
url: 'home/category/head'
})
}
main.js下新增
//测试接口
import {getCategory} from '@/api/testAPI'
getCategory().then(res => {
console.log(res)
})
重启运行
views/Login/index.vue
我是登录页
views/Layout/index.vue
我是首页
views/Home/index.vue
我是home页
views/Category/index.vue
我是分类页
router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Login from '@/views/login/index.vue'
import Layout from '@/views/layout/index.vue'
import Home from '@/views/home/index.vue'
import Category from '@/views/category/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: Layout,
children: [
{
path: '',
component: Home
},
{
path: 'category',
component: Category
}
]
},
{
path: '/login',
component: Login
}
]
})
export default router
重启输入对应url可以看到不同页面。
链接:https://pan.baidu.com/s/15PoJhfpPDzf_WTsakO0jmg?pwd=3kgh
提取码:3kgh
var.scss
$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;
修改main.js
css: {
preprocessorOptions: {
scss: {
// 自动导入scss文件
additionalData: `
@use "@/styles/element/index.scss" as *;
@use "@/styles/var.scss" as *;
`,
}
}
}
测试
修改App.vue
test
测试成功,还原代码。
LayoutNav.vue
LayoutHeader.vue
小兔鲜
-
首页
-
居家
-
美食
-
服饰
LayoutFooter.vue
Layout/index.vue
重启项目
根目录下index.html引入
api/layout.js
import httpInstance from '@/utils/http'
export function getCategoryAPI () {
return httpInstance({
url: '/home/category/head'
})
}
LayoutHeader.vue
小兔鲜
-
{{ item.name }}
实现吸顶交互
components/LayoutFixed.vue
-
首页
-
居家
-
美食
-
服饰
-
母婴
-
个护
-
严选
-
数码
-
运动
-
杂项
品牌
专题
两个导航栏会分别发送一个请求,可以利用pinia只发送一个请求。
stores/category.js
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { getCategoryAPI } from '@/api/layout'
export const useCategoryStore = defineStore('category', () => {
// 导航列表的数据管理
// state 导航列表数据
const categoryList = ref([])
// action 获取导航数据的方法
const getCategory = async () => {
const res = await getCategoryAPI()
categoryList.value = res.result
}
return {
categoryList,
getCategory
}
})
layout/index.vue
layout/components/LayoutFixed.vue
-
{{ item.name }}
品牌
专题
layout/components/LayoutHeader.vue
-
{{ item.name }}
品牌
专题
发现只有一次请求。
在home/components下新建5个vue文件
我是分类
我是banner
人气推荐
新鲜好物
产品列表
home/index.vue
home/components/HomeCategory.vue
home/components/HomeBanner.vue
获取数据
api/home.js
import httpInstance from '@/utils/http'
export function getBannerAPI (){
return httpInstance({
url:'home/banner'
})
}
home/components/HomeBanner.vue
轮播图就实现了。
home/components/HomePanel.vue
{{ title }}{{ subTitle }}
api/home添加:
/**
* @description: 获取新鲜好物
* @param {*}
* @return {*}
*/
export const getNewAPI = () => {
return httpInstance({
url:'/home/new'
})
}
home/components/HomeNew.vue
-
{{ item.name }}
¥{{ item.price }}
api/home添加:
/**
* @description: 获取人气推荐
* @param {*}
* @return {*}
*/
export const getHotAPI = () => {
return httpInstance({
url:'/home/hot'
})
}
home/components/HomeHot.vue
-
{{ item.title }}
{{ item.alt }}
directives/index.js
// 定义懒加载插件
import { useIntersectionObserver } from '@vueuse/core'
export const directivePlugin = {
install (app) {
// 懒加载指令逻辑
app.directive('img-lazy', {
mounted (el, binding) {
// el: 指令绑定的那个元素 img
// binding: binding.value 指令等于号后面绑定的表达式的值 图片url
console.log(el, binding.value)
useIntersectionObserver(
el,
([{ isIntersecting }]) => {
console.log(isIntersecting)
if (isIntersecting) {
// 进入视口区域
el.src = binding.value
stop()
}
},
)
}
})
}
}
修改main.js
app.use(createPinia())
app.use(router)
app.use(directivePlugin)
app.mount('#app')
api/home.js新增
/**
* @description: 获取所有商品模块
* @param {*}
* @return {*}
*/
export const getGoodsAPI = () => {
return httpInstance({
url: '/home/goods'
})
}
home/components/HomeProduct.vue
{{ cate.name }}馆
{{ cate.saleInfo }}
-
{{ good.name }}
{{ good.desc }}
¥{{ good.price }}
这里没有用懒加载的形式,因为总有一些图片加载不出来,原因不明。
home/components/GoodsItem.vue
{{ goods.name }}
{{ goods.desc }}
¥{{ goods.price }}
home/components/HomeProduct.vue修改
import GoodsItem from './GoodsItem.vue'
-
router/index.js修改
children: [
{
path: '',
component: Home
},
{
path: 'category/:id',
component: Category
}
]
layout/components/LayoutHeader.vue修改
layout/components/LayoutFixed.vue修改
{{ item.name }}
api/category.vue
import httpInstance from '@/utils/http'
/**
* @description: 获取分类数据
* @param {*} id 分类id
* @return {*}
*/
export const findTopCategoryAPI = (id) => {
return httpInstance({
url:'/category',
params:{
id
}
})
}
views/category/index.vue
首页
{{ categoryData.name }}
api/home.js修改
export function getBannerAPI (params = {}) {
// 默认为1 商品为2
const { distributionSite = '1' } = params
return httpInstance({
url: '/home/banner',
params: {
distributionSite
}
})
}
category/index.vue
首页
{{ categoryData.name }}
layout/components/LayoutHeader.vue修改
layout/components/LayoutFixed.vue修改
{{ item.name }}
category/index.vue
首页
{{ categoryData.name }}
全部分类
-
{{ i.name }}
- {{ item.name }}-
banner与分类商品在刷新的时候都会请求一次,但banner是没必要刷新的,且导航栏需要刷新才能更新。
category/index.vue修改
import {useRoute,onBeforeRouteUpdate} from 'vue-router'
// 目标:路由参数变化的时候 可以把分类数据接口重新发送
onBeforeRouteUpdate((to) => {
// 存在问题:使用最新的路由参数请求最新的分类数据
getCategory(to.params.id)
})
将banner和分类抽象出来。
useBanner.js
// 封装banner轮播图相关的业务代码
import { ref, onMounted } from 'vue'
import { getBannerAPI } from '@/api/home'
export function useBanner () {
//获取banner
const bannerList = ref([])
const getBanner = async () => {
const res = await getBannerAPI()
console.log(res)
bannerList.value = res.result
}
onMounted(() => getBanner())
return {
bannerList
}
}
useCategory.js
// 封装分类数据业务相关代码
import { onMounted, ref } from 'vue'
import { findTopCategoryAPI } from '@/api/category'
import { useRoute } from 'vue-router'
import { onBeforeRouteUpdate } from 'vue-router'
export function useCategory () {
const categoryData = ref({})
const route = useRoute()
const getCategory = async (id) => {
// 如何在setup中获取路由参数 useRoute() -> route 等价于this.$route
const res = await findTopCategoryAPI(id)
categoryData.value = res.result
}
getCategory(route.params.id)
// 目标:路由参数变化的时候 可以把分类数据接口重新发送
onBeforeRouteUpdate((to) => {
// 存在问题:使用最新的路由参数请求最新的分类数据
getCategory(to.params.id)
})
return {
categoryData
}
}
index.vue修改
views下新建
index.vue
首页
居家
居家生活用品
router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Login from '@/views/login/index.vue'
import Layout from '@/views/layout/index.vue'
import Home from '@/views/home/index.vue'
import Category from '@/views/category/index.vue'
import SubCategory from '@/views/subCategory/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: Layout,
children: [
{
path: '',
component: Home
},
{
path: 'category/:id',
component: Category
},
{
path: 'category/sub/:id',
name: 'subCategory',
component: SubCategory
},
]
},
{
path: '/login',
component: Login
}
]
})
export default router
category/index.vue修改
全部分类
-
{{ i.name }}
api/category.js
/**
* @description: 获取二级分类列表数据
* @param {*} id 分类id
* @return {*}
*/
export const getCategoryFilterAPI = (id) => {
return httpInstance({
url:'/category/sub/filter',
params:{
id
}
})
}
/**
* @description: 获取导航数据
* @data {
categoryId: 1005000 ,
page: 1,
pageSize: 20,
sortField: 'publishTime' | 'orderNum' | 'evaluateNum'
}
* @return {*}
*/
export const getSubCategoryAPI = (data) => {
return httpInstance({
url:'/category/goods/temporary',
method:'POST',
data
})
}
subCategory/index.vue
首页
{{ filterData.parentName }}
{{ filterData.name }}
subCategory/index.vue修改
无限加载。
自动回到顶部。
router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Login from '@/views/login/index.vue'
import Layout from '@/views/layout/index.vue'
import Home from '@/views/home/index.vue'
import Category from '@/views/category/index.vue'
import SubCategory from '@/views/subCategory/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: Layout,
children: [
{
path: '',
component: Home
},
{
path: 'category/:id',
component: Category
},
{
path: 'category/sub/:id',
name: 'subCategory',
component: SubCategory
},
]
},
{
path: '/login',
component: Login
}
],
//路由滚动行为
scrollBehavior() {
return {
top: 0
}
}
})
export default router
views/detail/index.vue
首页
母婴
跑步鞋
抓绒保暖,毛毛虫子儿童运动鞋
-
销量人气
100+
销量人气
-
商品评价
200+
查看评价
-
收藏人气
300+
收藏商品
-
品牌信息
400+
品牌主页
router/index.js中children中添加
{
path: 'detail/:id',
component: Detail
}
home/components/HomeNew.vue修改
{{ item.name }}
¥{{ item.price }}
detail/index.vue
首页
{{goods.categories?.[1].name}}
{{goods.categories?.[0].name}}
{{goods.name}}
-
销量人气
{{goods.salesCount}}+
销量人气
-
商品评价
{{goods.commentCount}}+
查看评价
-
收藏人气
{{goods.collectCount}}+
收藏商品
-
品牌信息
{{goods.brand.name}}
品牌主页
{{goods.name}}
{{goods.desc}}
{{goods.oldPrice}}
{{goods.price}}
- 促销
- 12月好物放送,App领券购买直降120元
- 服务
-
无忧退货
快速退款
免费包邮
了解详情
加入购物车
api/detail.js添加
export const fetchHotGoodsAPI = ({ id, type, limit = 3 }) => {
return httpInstance({
url:'/goods/hot',
params:{
id,
type,
limit
}
})
}
DetailHot.vue
24小时热榜
{{ item.name }}
{{ item.desc }}
¥{{ item.price }}
detail/index.vue
首页
{{goods.categories?.[1].name}}
{{goods.categories?.[0].name}}
{{goods.name}}
-
销量人气
{{goods.salesCount}}+
销量人气
-
商品评价
{{goods.commentCount}}+
查看评价
-
收藏人气
{{goods.collectCount}}+
收藏商品
-
品牌信息
{{goods?.brand?.name}}
品牌主页
{{goods.name}}
{{goods.desc}}
{{goods.oldPrice}}
{{goods.price}}
- 促销
- 12月好物放送,App领券购买直降120元
- 服务
-
无忧退货
快速退款
免费包邮
了解详情
加入购物车
detail/components/DeatinHot.vue修改
detail/index.vue
src/components/imgView/index.vue
-
detail/index.vue修改
import ImageView from '@/components/imageView/index.vue'
XtxSku
链接:https://pan.baidu.com/s/1KQ5-OFMoD7bpL8DJjsmsIA?pwd=3kgh
提取码:3kgh
detain/index.vue修改
import XtxSku from '@/components/XtxSku/index.vue'
login/index.vue
小兔鲜
进入网站首页
我已同意隐私条款和服务条款
点击登录
账号:cdshi0006
密码:123456
login/index.vue修改
utils/http.js修改
import 'element-plus/theme-chalk/el-message.css'
import { ElMessage } from 'element-plus'
// axios响应式拦截器
http.interceptors.response.use(res=>res.data,e => {
ElMessage({
type: 'warning',
message: e.response.data.msg
})
console.log(e)
return Promise.reject(e)
})
stores/user.js
// 管理用户数据相关
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/api/user'
export const useUserStore = defineStore('user', () => {
// 1. 定义管理用户数据的state
const userInfo = ref({})
// 2. 定义获取接口数据的action函数
const getUserInfo = async ({ account, password }) => {
const res = await loginAPI({ account, password })
userInfo.value = res.result
}
// 3. 以对象的格式把state和action return
return {
userInfo,
getUserInfo
}
}, {
persist: true,
})
需要使用插件,自动本地存储,官网
下载pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate
main.js修改
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
login/index.vue修改
export const useUserStore = defineStore('user', () => {
//......
}, {
persist: true,
})
LayoutNav.vue修改
http.js
import {useUserStore} from '@/stores/user'
http.interceptors.request.use(config => {
// 1. 从pinia获取token数据
const userStore = useUserStore()
// 2. 按照后端的要求拼接token数据
const token = userStore.userInfo.token
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, e => Promise.reject(e))
user.js
// 管理用户数据相关
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/api/user'
export const useUserStore = defineStore('user', () => {
// 1. 定义管理用户数据的state
const userInfo = ref({})
// 2. 定义获取接口数据的action函数
const getUserInfo = async ({ account, password }) => {
const res = await loginAPI({ account, password })
userInfo.value = res.result
}
// 退出时清除用户信息
const clearUserInfo = () => {
userInfo.value = {}
}
// 3. 以对象的格式把state和action return
return {
userInfo,
getUserInfo,
clearUserInfo
}
}, {
persist: true,
})
LayoutNav.vue修改
http.js
import router from '@/router'
// axios响应式拦截器
http.interceptors.response.use(res=>res.data,e => {
const userStore = useUserStore()
ElMessage({
type: 'warning',
message: e.response.data.msg
})
if(e.response.status === 401) {
userStore.clearUserInfo()
router.push('/login')
}
return Promise.reject(e)
})
stores/car.js
// 封装购物车模块
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCartStore = defineStore('cart', () => {
// 1. 定义state - cartList
const cartList = ref([])
// 2. 定义action - addCart
const addCart = (goods) => {
console.log('添加', goods)
// 添加购物车操作
// 已添加过 - count + 1
// 没有添加过 - 直接push
// 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
const item = cartList.value.find((item) => goods.skuId === item.skuId)
if (item) {
// 找到了
item.count++
} else {
// 没找到
cartList.value.push(goods)
}
}
return {
cartList,
addCart
}
}, {
persist: true,
})
detail/index.vue修改
加入购物车
detail/index.vue修改
car.js
// 封装购物车模块
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCartStore = defineStore('cart', () => {
// 1. 定义state - cartList
const cartList = ref([])
// 2. 定义action - addCart
const addCart = (goods) => {
// console.log('添加', goods)
// 添加购物车操作
// 已添加过 - count + 1
// 没有添加过 - 直接push
// 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
const item = cartList.value.find((item) => goods.skuId === item.skuId)
if (item) {
// 找到了
item.count++
} else {
// 没找到
cartList.value.push(goods)
}
}
//删除购物车
const delCart = (skuId) => {
const idx = cartList.value.findIndex((item)=>skuId === item.skuId)
cartList.value.splice(idx,1)
}
//总数
const allCount = computed(()=>cartList.value.reduce((a,c)=>a+c.count,0))
//总价
const allPrice = computed(()=>cartList.value.reduce((a,c)=>a+c.price*c.count,0))
return {
cartList,
addCart,
delCart,
allPrice,
allCount
}
}, {
persist: true,
})
router.js添加
{
path: 'cartlist',
component: CartList
}
views/cartList/index.vue
商品信息
单价
数量
小计
操作
singleCheck(i,selected)"/>
![]()
{{ i.name }}
¥{{ i.price }}
¥{{ (i.price * i.count).toFixed(2) }}
随便逛逛
共 {{cartStore.allCount}} 件商品,已选择 {{cartStore.selectCount}} 件,商品合计:
¥ {{cartStore.selectPrice.toFixed(2)}}
下单结算
stores/car.js
// 封装购物车模块
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import {insertCartAPI,findNewCartListAPI,delCartAPI} from '@/api/cart'
import {useUserStore} from '@/stores/user'
export const useCartStore = defineStore('cart', () => {
const userStore = useUserStore()
const isLogin = computed(()=>userStore.userInfo.token)
// 1. 定义state - cartList
const cartList = ref([])
// 2. 定义action - addCart
const addCart = async (goods) => {
const {skuId,count} = goods
if(isLogin.value) {
//登录过后
await insertCartAPI({skuId,count})
const res = await findNewCartListAPI()
cartList.value = res.result
} else {
// console.log('添加', goods)
// 添加购物车操作
// 已添加过 - count + 1
// 没有添加过 - 直接push
// 思路:通过匹配传递过来的商品对象中的skuId能不能在cartList中找到,找到了就是添加过
const item = cartList.value.find((item) => goods.skuId === item.skuId)
if (item) {
// 找到了
item.count++
} else {
// 没找到
cartList.value.push(goods)
}
}
}
//更新
const updateNewList = async () => {
const res = await findNewCartListAPI()
cartList.value = res.result
}
//删除购物车
const delCart = async(skuId) => {
if(isLogin.value) {
await delCartAPI([skuId])
const res = await findNewCartListAPI()
cartList.value = res.result
} else {
const idx = cartList.value.findIndex((item)=>skuId === item.skuId)
cartList.value.splice(idx,1)
}
}
//总数
const allCount = computed(()=>cartList.value.reduce((a,c)=>a+c.count,0))
//总价
const allPrice = computed(()=>cartList.value.reduce((a,c)=>a+c.price*c.count,0))
// 单选功能
const singleCheck = (skuId, selected) => {
// 通过skuId找到要修改的那一项 然后把它的selected修改为传过来的selected
const item = cartList.value.find((item) => item.skuId === skuId)
item.selected = selected
}
//是否全选
const isAll = computed(()=>cartList.value.every((item) => item.selected))
//全选
const allCheck = (selected) => {
cartList.value.forEach(item => item.selected = selected)
}
//已选择的数量
const selectCount = computed(()=>cartList.value.filter(item=>item.selected).reduce((a,c)=>a+c.count,0))
//已选择价钱
const selectPrice = computed(()=>cartList.value.filter(item=>item.selected).reduce((a,c)=>a+c.price*c.count,0))
//清除购物车
const clearCart = () => {
cartList.value = []
}
return {
cartList,
addCart,
delCart,
allPrice,
allCount,
singleCheck,
isAll,
allCheck,
selectCount,
selectPrice,
clearCart,
updateNewList
}
}, {
persist: true,
})
api/cart.js
import httpInstance from '@/utils/http'
// 加入购物车
export const insertCartAPI = ({ skuId, count }) => {
return httpInstance({
url: '/member/cart',
method: 'POST',
data: {
skuId,
count
}
})
}
//登录购物车
export const findNewCartListAPI = () => {
return httpInstance({
url: '/member/cart'
})
}
// 删除购物车
export const delCartAPI = (ids) => {
return httpInstance({
url: '/member/cart',
method: 'DELETE',
data: {
ids
}
})
}
//合并购物车
export const mergeCartAPI = (data) => {
return httpInstance ({
url: '/member/cart/merge',
method: 'POST',
data
})
}
stores/user.js
// 管理用户数据相关
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { loginAPI } from '@/api/user'
import {useCartStore} from './car'
import {mergeCartAPI} from '@/api/cart'
export const useUserStore = defineStore('user', () => {
const cartStore = useCartStore()
// 1. 定义管理用户数据的state
const userInfo = ref({})
// 2. 定义获取接口数据的action函数
const getUserInfo = async ({ account, password }) => {
const res = await loginAPI({ account, password })
userInfo.value = res.result
mergeCartAPI(cartStore.cartList.map(item => {
return {
skuId: item.skuId,
selected: item.selected,
count: item.count
}
}))
cartStore.updateNewList()
}
// 退出时清除用户信息
const clearUserInfo = () => {
userInfo.value = {}
cartStore.clearCart()
}
// 3. 以对象的格式把state和action return
return {
userInfo,
getUserInfo,
clearUserInfo
}
}, {
persist: true,
})
单选按钮
全选按钮
加入、删除购物车
清空购物车
合并本地购物车
路由
{
path: 'checkout',
component: CheckOut
}
api/checkout.js
import httpInstance from '@/utils/http'
/**
* 获取结算信息
*/
export const getCheckoutInfoAPI = () => {
return httpInstance({
url:'/member/order/pre'
})
}
// 创建订单
export const createOrderAPI = (data) => {
return httpInstance({
url: '/member/order',
method: 'POST',
data
})
}
views/pay/index.vue
收货地址
您需要先添加收货地址才可提交订单。
- 收货人:{{ curAddress.receiver }}
- 联系方式:{{ curAddress.contact }}
- 收货地址:{{ curAddress.fullLocation }} {{ curAddress.address }}
切换地址
添加地址
商品信息
商品信息
单价
数量
小计
实付
{{ i.name }}
{{ i.attrsText }}
¥{{ i.price }}
{{ i.price }}
¥{{ i.totalPrice }}
¥{{ i.totalPayPrice }}
配送时间
支付方式
金额明细
- 商品件数:
- {{ checkInfo.summary?.goodsCount }}件
- 商品总价:
- ¥{{ checkInfo.summary?.totalPrice.toFixed(2) }}
- 运费:
- ¥{{ checkInfo.summary?.postFee.toFixed(2) }}
- 应付总额:
- {{ checkInfo.summary?.totalPayPrice.toFixed(2) }}
提交订单
- 收货人:{{ item.receiver }}
- 联系方式:{{ item.contact }}
- 收货地址:{{ item.fullLocation + item.address }}
router.js
{
path: 'pay',
component: Pay
}
api/pay.js
import httpInstance from '@/utils/http'
export const getOrderAPI = (id) => {
return httpInstance({
url: `/member/order/${id}`
})
}
composables/useCountDown.js
// 封装倒计时逻辑函数
import { computed, onUnmounted, ref } from 'vue'
import dayjs from 'dayjs'
export const useCountDown = () => {
// 1. 响应式的数据
let timer = null
const time = ref(0)
// 格式化时间 为 xx分xx秒
const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))
// 2. 开启倒计时的函数
const start = (currentTime) => {
// 开始倒计时的逻辑
// 核心逻辑的编写:每隔1s就减一
time.value = currentTime
timer = setInterval(() => {
time.value--
}, 1000)
}
// 组件销毁时清除定时器
onUnmounted(() => {
timer && clearInterval(timer)
})
return {
formatTime,
start
}
}
pay/index.vue
账号
登录密码
111111
支付密码
111111
router.js
import Member from '@/views/member/index.vue'
import MemberInfo from '@/views/member/components/UserInfo.vue'
import MemberOrder from '@/views/member/components/UserOrder.vue'
{
path: 'member',
component: Member,
children: [
{
path: 'user',
component: MemberInfo
},
{
path: 'order',
component: MemberOrder
},
]
}
LayoutNav.vue
会员中心
UserInfo.vue
猜你喜欢
UserOrder.vue
下单时间:{{ order.createTime }}
订单编号:{{ order.id }}
付款截止: {{order.countdown}}
api/user.js
export const getLikeListAPI = ({ limit = 4 }) => {
return httpInstance({
url:'/goods/relevant',
params: {
limit
}
})
}
UserInfo.vue
api/user.js
export const getUserOrder = (params) => {
return httpInstance({
url:'/member/order',
method:'GET',
params
})
}
UserOrder.vue
UserOrder.vue
// 页数切换
const pageChange = (page) => {
params.value.page = page
getOrderList()
}