官网:vant-contrib.gitee.io/vant/v3/#/z...
1. 安装
npm i vant@latest-v3 安装vant组件库
npm i unplugin-vue-components -D 安装vant按需引入插件。vite.config.ts中引入集成
npm i postcss-pxtorem -D安装移动端适配插件,将px单位转化为rem单位。vite.config.ts中引入集成
npm i amfe-flexible -D安装移动端适配插件,适配不同屏幕尺寸。main.ts中引入
2. vite.config.ts中集成配置插件
javascript
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'//引入按需引入vant插件
import { VantResolver } from 'unplugin-vue-components/resolvers'//引入按需引入vant插件
import postCssPxToRem from 'postcss-pxtorem'//引入移动端适配插件
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [VantResolver()],//集成按需引入vant插件
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
css:{
postcss:{
plugins:[
postCssPxToRem({//自适应,px转rem
rootValue:75,//换算的基数(设计图750的根字体为75)
propList:['*']//需要转换的属性,*代表全部
})
]
}
},
server:{
proxy: {
'/api': {
target: 'http://124.71.63.13:8088/',
changeOrigin: true,
// rewrite: path => path.replace(/^\/api/, '')
}
}
},
})
3. main.ts导入vant函数组件样式,引入适配插件(因为使用函数组件时,unplugin-vue-components无法自动引入对应的样式,需手动引入样式。)
javascript
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './stores'
import 'normalize.css' // 重置样式
// 导入vant函数组件样式
import 'vant/es/toast/style'
import 'vant/es/dialog/style'
import 'vant/es/notify/style'
import 'vant/es/image-preview/style'
//引入amfe-flexible屏幕适配插件
import 'amfe-flexible'
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
4. 解决引入vue组件ts报错(因为ts不识别.vue文件,需在env.d.ts中声明)
env.d.ts
typescript
// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
5. 项目中使用组件示例
按钮组件van-button、Toast轻提示、导航栏组件van-nav-bar、单元格组件van-cell、商品卡片van-card、
列表组件van-list(上拉加载更多)、下拉刷新van-pull-refresh、轮播van-swipe/van-swipe-item
分类选择van-tree-select、宫格组件van-grid/van-grid-item
标签栏van-tabbar/van-tabbar-item
滑动单元格van-swipe-cell、步进器van-stepper
示例一:
Home.vue
xml
<template>
<!-- 按钮组件 -->
<van-button type="primary" @click="bindConfirm">主要按钮</van-button>
<!-- nav-bar导航栏组件 -->
<van-nav-bar title="首页" />
<!-- list列表组件
通过 loading 和 finished 两个变量控制加载状态
滚动到底部时,会触发 load 事件。
loading-true进行异步更新--loading-false更新完成
数据全部加载完毕,则直接将 finished 设置成 true
-->
<!-- <van-list
v-model:loading="loading"
:finished="finished"
@load="onLoad"
finished-text="没有更多了"
>
<van-cell v-for="item in list" :key="item.id" :title="item.product" />
</van-list> -->
<van-card v-for="item in list" :key="item.id"
:num="item.putaway"
:price="item.price"
:desc="item.detail"
:title="item.product"
:thumb="item.picture"
:origin-price="item.oldprice"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Toast } from 'vant'
import { RequestShopList } from '@/api/index'
//Toast轻提示
const bindConfirm = () =>{
Toast('轻提示内容')
}
// 商品列表数据
const list = ref([])
// 1.触发 load 事件,loading 自动设置成 true。
// 2.此时可以发起异步操作并更新数据
// 3.数据更新完毕后,将 loading 设置成 false
const loading = ref(false)
// 若数据已全部加载完毕,则直接将 finished 设置成 true 即可
const finished = ref(false)
// 当组件滚动到底部时,会触发 load 事件
const onLoad = () => {
getProductList(1, 15)
}
const getProductList = async (pageNo: number, pageSize: number) => {
const data = await RequestShopList(pageNo, pageSize)
const { resultCode, resultInfo } = data
if (resultCode === 1) {
list.value = resultInfo.list
} else {
// 数据全部加载完成
finished.value = true
}
// 加载状态结束
loading.value = false
}
</script>
<style scoped></style>
示例二:(购物商城APP示例。ts类型定义,hooks组合式函数封装)
types文件夹→types.ts ts类型定义
typescript
export interface IResponse {
resultCode: number
resultInfo: any
}
/**
* 商品
*/
export interface IGoods {
id: number
shop: string
picture: string
product: string
price: number
oldprice: number
putaway: number
detail: string
categoryname: string
categoryId: number
}
/**
* 轮播
*/
export interface IBanner {
id: number
url: string
content: string
}
/**
* 商品分类
*/
export interface ICategory {
id: number
name: string
text: string
}
/**
* 购物车商品
*/
export interface ICartGoods {
id:number
name:string // 商品名称
price:number // 价格
url:string //图片地址
num:number //数量
state:boolean //状态
}
/**
* 账户
*/
export interface IAccount{
id:string
nick:string
name?:string
url:string
}
hooks文件夹→home.ts首页商品列表
hooks函数中调用引入并调用另一个hooks函数,会重复执行两次
解决1:定义一个全局开关变量,执行一次后关闭。但是会引起另一个问题,刷新数据会丢失。
解决2:不直接引入,通过vue文件传参形式传入并调用
typescript
import { ref, type Ref, onMounted } from 'vue'
import { RequestShopList, RequestBanner } from '@/api/index'
import type { IGoods, IBanner } from '@/types/types'
import { useRouter } from 'vue-router'
//let isBanner = true//全局开关变量
/**
* 轮播
*/
export const useBanner = () => {
// 轮播列表数据
const bannerList: Ref<Array<IBanner>> = ref([])
/**
* 轮播
*/
const getBanner = async () => {
const data = await RequestBanner()
const { resultCode, resultInfo } = data
if (resultCode === 1) {
bannerList.value = resultInfo.list
}
}
onMounted(() => {
// if(isBanner){
getBanner()
// isBanner = false
// }
})
return { bannerList, getBanner }
}
/**
* 商品列表
* 商品列表显示
* 加载更多
* 下拉刷新
* hooks组全式函数
*/
export const useGoods = (getBanner:Function) => {
// 商品列表数据
const list: Ref<Array<IGoods>> = ref([]) //ref()返回ref对象的数据类型是Ref
// 加载数据状态,true表示加载中,false加载完成
const loading: Ref<boolean> = ref(false)
// 加载完成数据状态, 所有数据加载完成设置为true
const finished: Ref<boolean> = ref(false)
// 下拉刷新状态
const refreshLoading:Ref<boolean> = ref(false)
// 当前页号
let currentNo: number = 1
/**
* 1.第一次进入自动调用,loadding设置为true,
* 2.触底自动调用
*/
const onLoad = () => {
getProductList(currentNo++, 10)
}
/**
* 下拉刷新
*/
// const { getBanner } = useBanner()
const onRefresh = () => {
// 重置
currentNo = 1
finished.value = false
loading.value = false
getProductList(currentNo, 10)
currentNo++ //刷新后currentNo加一,加载更多从第二页开始
getBanner()
}
/*
*商品列表
* @param pageNo
* @param pageSize
*/
const getProductList = async (pageNo: number, pageSize: number) => {
const data = await RequestShopList(pageNo, pageSize)
// 关闭下拉刷新, 手动下拉刷新时自动设置为true
if( refreshLoading.value){
list.value = []
refreshLoading.value = false
}
const { resultCode, resultInfo } = data
if (resultCode === 1) {
list.value = [...list.value, ...resultInfo.list]
// list.value = resultInfo.list
}
loading.value = false // loadding加载完成|关闭下拉刷新
if (resultCode === -1) {
finished.value = true //所有数据加载完成
}
}
// 模板使用的数据方法
return { list, loading, finished,refreshLoading, onLoad, onRefresh }
}
/**
* 商品详情
*/
export const useGoodsDetail = ()=>{
const router = useRouter()
//跳转到商品详情页
const bindGoodsDetail =(id:number)=>{
router.push({path:'detail',query:{id}})
}
return{bindGoodsDetail}
}
Home.vue首页商品列表
xml
<template>
<van-nav-bar title="首页" />
<!-- <van-search
v-model="value"
shape="round"
background="#f50718"
placeholder="请输入搜索关键词"
/> -->
<SearchHome></SearchHome>
<van-pull-refresh v-model="refreshLoading" @refresh="onRefresh">
<!-- 轮播 -->
<van-swipe :autoplay="3000" lazy-render>
<van-swipe-item v-for="item in bannerList" :key="item.id">
<img style="height: 150px; width: 100%" :src="item.url" />
</van-swipe-item>
</van-swipe>
<!-- 商品列表 -->
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-card v-for="item in list" :key="item.id"
:num="item.putaway"
:price="item.price"
:desc="item.detail"
:title="item.product"
:thumb="item.picture"
:origin-price="item.oldprice"
@click="bindGoodsDetail(item.id)"
/>
</van-list>
</van-pull-refresh>
</template>
<!-- ts编程,写代码时,变量指定数据类型,函数参数返回值类型,
如果没有显示指定,ts类型推论 -->
<script setup lang="ts">
import { useBanner,useGoods,useGoodsDetail } from '@/hooks/home'
const { bannerList,getBanner } = useBanner()
const {goodsList,loading,finished,refreshLoading,onLoad,onRefresh} = useGoods(getBanner)
const {bindGoodsDetail} = useGoodsDetail()
</script>
<style scoped></style>
SearchHome.vue修改第三方组件
xml
<template>
<van-nav-bar>
<template #left>
<router-link to="/city">城市</router-link>
</template>
<template #title>
<router-link to="/search">搜索</router-link>
</template>
<template #right>
<van-image v-if="accountStore.account.url" :src="accountStore.account.url" round type="contain" width="40px" height="40px" />
<router-link v-else to="/login">登录</router-link>
</template>
</van-nav-bar>
</template>
<script setup lang="ts">
import { useAccountStore } from '@/stores/user'
const accountStore = useAccountStore()
console.log('url ',accountStore.account.url);
</script>
<style scoped lang="scss">
.van-nav-bar{
background-color: #d30607;
.van-nav-bar__left,.van-nav-bar__right{
a{
color: white;
font-weight: bold;
}
}
//样式穿透
:deep(.van-nav-bar__title){
background-color: white;
width: 80%;
border-radius: 30px;
height: 35px;
line-height: 35px;
box-sizing: border-box;
text-align: left;
padding-left: 30px;
font-size: 14px;
a{
color: gray;
}
}
}
</style>
hooks文件夹→login.ts登录
typescript
import { ref,type Ref } from 'vue'
import { RequestLogin } from '@/api/home'
import { useAccountStore } from '@/stores/user'
import type{ IAccount} from '@/types/types'
import { Toast } from 'vant'
import { useRouter} from 'vue-router'
export const useAccount = ()=>{
// const username = ref<string>('')
const username:Ref<string> = ref('')
const password:Ref<string> = ref('')
const accountStore = useAccountStore()
const router = useRouter()
const bindBack = ()=>history.back()
/**
* 登录
*/
const onSubmit = async()=>{
const data = await RequestLogin(username.value,password.value)
const {resultCode,resultInfo,token} = data
if(resultCode === 1){
// 保存用户信息,主界面登录按钮换成用户头像
const account:IAccount = {id:resultInfo.id,nick:resultInfo.nick,url:resultInfo.headerimg}
accountStore.saveAccount(account)
// 保存token
sessionStorage.setItem('TOKEN',token as string)
// 登录成功提示,跳转到主界面
Toast.success('登录成功!')
router.replace('/')
}else{
Toast.fail('账户出错!')
}
}
return {username,password, bindBack,onSubmit}
}
Login.vue登录
ini
<template>
<van-nav-bar
title="登录界面"
left-text="返回"
left-arrow
@click-left="bindBack"
/>
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field
v-model="username"
name="username"
label="用户名"
placeholder="用户名"
:rules="[{ required: true, message: '请填写用户名' }]"
/>
<van-field
v-model="password"
type="password"
name="password"
label="密码"
placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
</van-cell-group>
<div style="margin: 16px">
<van-button round block type="primary" native-type="submit">
确定
</van-button>
</div>
</van-form>
</template>
<script setup lang="ts">
import { useAccount } from '@/hooks/login'
const {username,password ,bindBack,onSubmit } = useAccount()
</script>
<style scoped></style>
hooks文件夹→category.ts商品分类
typescript
import { ref, type Ref,onMounted } from 'vue'
import { RequestCategory, RequestGoodsByCategoryId } from '@/api/Category'
import type { ICategory , IGoods} from '@/types/types'
/**
* 商品分类
*/
export const useCategory = () =>{
//右侧索引
const activeId:Ref<number> = ref(1)
//左侧索引
const activeIndex:Ref<number> = ref(0)
// 根据分类id获取的商品列表
const goodsList:Ref<Array<IGoods>> = ref([])
// 商品分类列表
const categoryList: Ref<Array<ICategory>> = ref([])
/**
* 商品分类
*/
const getCategory = async()=>{
const data = await RequestCategory()
const {resultCode,resultInfo} = data
if(resultCode === 1){
//左侧数据过滤
//组件库中数据与接口中数据不同,因此要遍历返回一个新数组
// categoryList.value = resultInfo.list
categoryList.value = resultInfo.list.map((item:ICategory)=>{
return {id:item.id,name:item.name,text:item.name}
})
// 加载第一件商品
const categoryId = categoryList.value[0].id
getGoodsByCategoryId(categoryId)
}
}
/**
* 根据分类id获取商品列表
*/
const getGoodsByCategoryId = async (categoryId: number) => {
const data = await RequestGoodsByCategoryId(categoryId)
const { resultCode, resultInfo } = data
if (resultCode === 1) {
goodsList.value = resultInfo.list
}
}
/**
* 切换分类
*/
const bindClickNav = (index:number) => {
const categoryId = categoryList.value[index].id
getGoodsByCategoryId(categoryId)
}
onMounted(()=>{
getCategory()
})
return{activeId,activeIndex,goodsList,categoryList,bindClickNav}
}
Category.vue商品分类
xml
<template>
<van-nav-bar title="分类" />
<van-tree-select
v-model:active-id="activeId"
v-model:main-active-index="activeIndex"
height="90%"
:items="categoryList"
@click-nav="bindClickNav"
>
<template #content>
<!-- <van-card
v-for="item in goodsList"
:key="item.id"
:price="item.price"
:title="item.product"
:thumb="item.picture"
/> -->
<van-grid :border="false" :column-num="3">
<van-grid-item v-for="item in goodsList" :key="item.id">
<div class="g-goods">
<img :src="item.picture" :alt="item.product" />
<p>{{ item.product }}</p>
</div>
</van-grid-item>
</van-grid>
</template>
</van-tree-select>
</template>
<script lang="ts" setup>
import {useCategory} from '@/hooks/Category'
const {activeId,activeIndex,goodsList,categoryList, bindClickNav} = useCategory()
</script>
<style lang="scss" scoped>
.g-goods {
img {
width: 60px;
height: 60px;
}
p {
max-width: 60px;
white-space: nowrap;
overflow: hidden; //文本超出隐藏
text-overflow: ellipsis; //文本超出省略号替代
}
}
</style>
hooks文件夹→detail.ts首页商品列表点击跳转到商品详情
typescript
import {ref, type Ref, onMounted} from 'vue'
import type {ICartGoods,IGoods} from '@/types/types'
import {RequestDetail} from '@/api/Detail'
import { useCartStore } from '@/stores/cart'
import { useRoute } from 'vue-router'
import { Toast } from 'vant'
/**
* 商品详情
*/
export const useDetail = ()=>{
const cartStore = useCartStore()
//路由信息对象接受参数id
const route = useRoute()
const id: any = route.query.id
//商品详情数据
const goods:Ref<IGoods> = ref({
id: 0,
shop: '',
picture: '',
product: '',
price: 0,
oldprice: 0,
putaway: 0,
detail: '',
categoryname: '',
})
//返回上一页
const bindBack = ()=>history.back()
//商品详情接口,调用接口渲染数据
const getDetail = async()=>{
const data = await RequestDetail(id)
const { resultCode, resultInfo } = data
if (resultCode === 1) {
goods.value = resultInfo
}
}
/**
* 加入购物车
*/
const bindAddCart = ()=>{
//购物车列表数据
const cartGoods:ICartGoods = {
id:goods.value.id,
name: goods.value.product,
price: goods.value.price,
url: goods.value.picture,
num: 1,
state: false,
}
//存储到pinia中(stores-cart.ts)
cartStore.saveGoods(cartGoods)
Toast.success('加入购物车成功!')
}
onMounted(()=>{
getDetail()
})
return {bindBack,goods,bindAddCart}
}
Detail.vue首页商品列表点击跳转到商品详情
xml
<template>
<van-nav-bar title="商品详情"
left-text="返回"
left-arrow
@click-left="bindBack"/>
<div>
<img :src="goods.picture" style="width:100%;height:280px;" :alt="goods.picture">
<h2>{{goods.product}}</h2>
<van-cell>
<template #title>
<span>商品价格:{{goods.price}}</span>
</template>
<van-button class="" round type="primary" size="small" @click="bindAddCart">加入购物车</van-button>
</van-cell>
<p>: </p>
</div>
<van-tabbar v-model="active">
<van-button round type="success" size="large" to="Cart">购物车</van-button>
</van-tabbar>
</template>
<script setup>
import {useDetail} from '@/hooks/Detail'
const {bindBack,goods,bindAddCart} = useDetail()
</script>
<style lang="scss" scoped>
span{
font-size: 20px;
}
</style>
hooks文件夹→cart.ts购物车
typescript
import { useCartStore } from '@/stores/cart'
import { useRouter } from 'vue-router'
export const useCart = ()=>{
const cartStore = useCartStore()
const router = useRouter()
const bindBack = ()=> router.replace({path:'/'})
// const bindBack = ()=> history.back()
/**
* 删除商品
*/
const bindDelete = (id:number) =>{
cartStore.deleteGoods(id)
}
/**
* 提交订单
*/
const onSubmit =()=>{
}
return {cartStore,bindBack,bindDelete,onSubmit}
}
Cart.vue
xml
<template>
<!-- <van-nav-bar title="购物车" /> -->
<van-nav-bar
title="购物车"
left-text="返回"
left-arrow
@click-left="bindBack"
/>
<!-- SwipeCell 滑动单元格 -->
<van-swipe-cell v-for="item in cartStore.list" :key="item.id">
<van-card
:num="item.num"
:price="item.price"
:title="item.name"
class="goods-card"
>
<template #thumb>
<div class="g-thumb">
<van-checkbox :checked="item.state" @click="cartStore.singleCheck(item.id)"></van-checkbox>
<van-image :src="item.url" width="70%"></van-image>
</div>
</template>
<template #footer>
<!-- Stepper 步进器 -->
<van-stepper
v-model="item.num"
theme="round"
button-size="22"
disable-input
/>
</template>
</van-card>
<template #right>
<van-button
square
text="删除"
type="danger"
class="delete-button"
@click="bindDelete(item.id)"
/>
</template>
</van-swipe-cell>
<van-submit-bar :price="cartStore.totalPrice" button-text="提交订单" @submit="onSubmit">
<van-checkbox :checked="cartStore.stateAll" @click="cartStore.checkAll">全选</van-checkbox>
</van-submit-bar>
</template>
<script setup lang="ts">
import { useCart } from '@/hooks/cart'
const { cartStore,bindBack,bindDelete,onSubmit } = useCart()
</script>
<style lang="scss" scoped>
.g-thumb {
display: flex;
}
.delete-button{
height:100%;
}
</style>
Layout.vue
xml
<template>
<!-- 子路由输出 -->
<router-view ></router-view>
<van-tabbar route class="g-footer" active-color="#ee0a24" inactive-color="#000">
<van-tabbar-item replace to="/home" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item replace to="/category" icon="search">分类</van-tabbar-item>
<van-tabbar-item replace to="/cart" icon="shopping-cart-o">购物车</van-tabbar-item>
<van-tabbar-item replace to="/my" icon="friends-o">我的</van-tabbar-item>
</van-tabbar>
</template>
<script lang="ts" setup>
</script>
<style lang="scss" scoped>
#app {
display: flex;
flex-direction: column;
.g-footer {
height: 60px;
}
.g-container {
flex: 1;
}
}
</style>
api文件夹→index.ts接口定义
typescript
import axiosInstance from '@/utils/request'
import type{IResponse} from '@/types/types'
/**
* 产品列表
* shopKey: 店铺名称
* productKey: 产品名称
* @returns
*/
export const RequestShopList = (pageNo:number,pageSize:number):Promise<IResponse>=>{
return axiosInstance({
method:'get',
url:'/api/shop',
params:{
pageSize,
pageNo,
}
})
}
/**
* 轮播
*/
export const RequestBanner = ():Promise<IResponse>=>{
return axiosInstance({
method:'get',
url:'/api/banner'
})
}
api文件夹→category.ts接口定义
typescript
import axiosInstance from '@/utils/request'
import type{IResponse} from '@/types/types'
/**
* 商品分类
*/
export const RequestCategory = ():Promise<IResponse>=>{
return axiosInstance({
method:'get',
url:'/api/category'
})
}
/**
* 根据分类id获取商品列表
*/
export const RequestGoodsByCategoryId = (categoryId:number):Promise<IResponse>=>{
return axiosInstance({
method:'get',
url:'/api/shop/list',
params:{
categoryId
}
})
}
api文件夹→detail.ts接口定义
typescript
import axiosInstance from '@/utils/request'
import type{IResponse} from '@/types/types'
/**
* 商品详情
*/
export const RequestDetail = (id:number):Promise<IResponse>=>{
return axiosInstance({
method:'get',
url:'/api/shop/find',
params:{
id
}
})
}
api文件夹→cart.ts接口定义
typescript
import axiosInstance from '@/utils/request'
import type{IResponse} from '@/types/types'
/**
* 删除商品
*/
export const RequestDeleteGoods = (id:number):Promise<IResponse>=>{
return axiosInstance({
method:'get',
url:'/api/shop/delete',
params:{
id
}
})
}
utils文件夹→request.ts(创建axios实例,封装请求/响应拦截器)
javascript
import axios from 'axios'
import { Toast } from 'vant';
// 服务根地址
export const baseURL = 'http://10.7.163.165:8089'
/**
* 创建axios实例
* 封装baseURL
*/
const axiosInstance = axios.create({
baseURL, // 服务根地址
timeout: 3000, // 超时时间
})
/**
* 请求拦截器
*/
axiosInstance.interceptors.request.use(
config => {
const token = localStorage.getItem('TOKEN')
if(token){
config.headers['Authorization'] = token
}
return config
},
error => {
return Promise.reject(error)
}
)
/**
* 响应拦截器
*/
axiosInstance.interceptors.response.use(
response => {
return response.data
},
error => {
const { response } = error
if (response) {
const status = response.status
switch (status) {
case 404:
Toast('资源不存在 404')
break
case 401:
Toast('Unauthorized 身份验证凭证缺失!')
break
case 403:
Toast('403 Forbidden - 拒绝访问!')
break
case 500:
Toast('服务器出错')
break
default:
Toast('出现异想不到的错误!')
break
}
}else {
// 说明服务器连结果都没有返回,可能的原因有两种:
/**
* 1. 服务器崩掉了
* 2. 前端客户端断网状态
*/
if (!window.navigator.onLine) {
// 判断为断网,可以跳转到断网页面
Toast('网络不可用,请检查您的网络连接!')
return
} else {
Toast('连接服务端出错!' + error?.message)
return Promise.reject(error)
}
}
return Promise.reject(error)
}
)
export default axiosInstance
router文件夹→index.ts路由封装
php
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Layout from '@/views/Layout.vue'
import Category from '@/views/Category.vue'
import Cart from '@/views/Cart.vue'
import My from '@/views/My.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
// {
// path: '/',
// component: Home,
// },
// {
// path: '/home',
// name: 'home',
// component: Home
// },
{
path: '/',
component: Layout,
redirect: 'home',
children: [
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/category',
name: 'category',
component: Category,
},
{
path: '/cart',
name: 'cart',
component: Cart,
},
{
path: '/my',
name: 'my',
component: My,
},
]
},
{
path:'/detail',
name:'detail',
component: ()=>import('@/views/Detail.vue')
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login.vue'),
},
]
})
export default router
stores文件夹→index.js(创建pinia根存储,集成插件)
javascript
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'//引入pinia持久化存储插件
const storeRoot = createPinia()
storeRoot.use(piniaPluginPersist)//集成插件
export default storeRoot
stores→cart.ts
typescript
import { defineStore } from 'pinia'
import type { ICartGoods } from '@/types/types'
export const useCartStore = defineStore('cart', {
state() {
return {
list: [] as Array<ICartGoods>,
stateAll: false, // 全选状态
}
},
actions: {
/**
* 全选
*/
checkAll() {
this.stateAll = !this.stateAll
this.list.forEach(item => (item.state = this.stateAll))
},
/**
* 单选
*/
singleCheck(id: number) {
const goods: ICartGoods | undefined = this.list.find(
item => item.id === id
)
if (goods) {
goods.state = !goods.state
}
this.stateAll = this.list.every(item => item.state)
},
/**
* 保存
*/
saveGoods(goods: ICartGoods) {
// 相同商品数量加一
// 判断购物车是否有此要添加商品,如果有数量加,如果没有,作为新商品添加
const oldGoods:ICartGoods|undefined = this.list.find(item=>item.id === goods.id)
if(oldGoods){
oldGoods.num++
}else{
// 新加商品,全选状态设置false
this.stateAll = false
this.list.push(goods)
}
},
/**
* 删除商品
*/
deleteGoods(id:number){
const index = this.list.findIndex(item=>item.id === id)
this.list.splice(index,1)
},
},
getters: {
totalPrice(): number {
const sum = this.list.reduce(
(preventvalue, currentvalue) =>
preventvalue +
(currentvalue.state
? currentvalue.price * currentvalue.num
: 0),
0
)
return Number(sum.toFixed(2))*100
},
},
persist: {//持久化存储
enabled: true,
strategies: [
{
key: 'cart',
storage: localStorage,
paths: ['list', 'stateAll'],
},
],
},
})
stores→user.ts
php
import { defineStore } from 'pinia'
import type{ IAccount} from '@/types/types'
export const useAccountStore = defineStore('account',{
state(){
return {
account:{id:'',nick:'',url:''}
}
},
actions:{
saveAccount(account:IAccount){
this.account = account
}
},
getters:{
},
persist: {
enabled: true,
strategies: [
{
key: 'account',
storage: localStorage,
paths: ['account'],
},
],
},
})