技术栈
vue3.2(setup) + ts + vue-router@4 + pinia + vite + axios + vant3?
脚手架搭建
npm init vue@latest
cd Admin
npm i
?清理文件
组件安装
1.设置移动端适配
安装插件
npm i postcss-px-to-viewport -D
npm i amfe-flexible -D
配置vite.config.ts
import postCssPxToRem from 'postcss-pxtorem'
export default defineConfig({
plugins: [
... ],
resolve: {
...
},
css: {
// 此代码为适配移动端px2rem
postcss: {
plugins: [
postCssPxToRem({
rootValue: 37.5, // 1rem的大小
propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
}),
],
},
},
})
main.ts?
import 'amfe-flexible'
2.安装axios
npm i axios -S
axios二次封装?
utils/request.js
import axios from 'axios'
export let baseURL = 'http://10.7.162.150:8089'
/**
* process.env.NODE_ENV
* production 生产环境
* npm run build
*
* development 开发环境
* npm run dev
*
*/
switch (process.env.NODE_ENV) {
case 'production':
baseURL = 'https://api.yuguoxy.com'
break
case 'development':
baseURL = 'http://10.7.162.150:8089'
break
}
const axiosServer = axios.create({
baseURL,
timeout: 5000,
})
//请求拦截器
axiosServer.interceptors.request.use(
config => {
// console.log('请求拦截器 config ', config)
// 设置token到authorization头部
let token = localStorage.getItem('TOKEN')
if (token) {
// console.log('config.headers ',config.headers);
config.headers['Authorization'] = token
}
return config
},
error => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
//响应拦截器
axiosServer.interceptors.response.use(
function (response) {
return response.data
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error)
}
)
export default axiosServer
3.解决引入vue组件ts报错问题
en.d.ts文件
declare module "*.vue" {
import { DefineComponent } from "vue"
const component: DefineComponent<{}, {}, any>
export default component
}
4.vant组件安装
npm i vant
按需引入
npm i unplugin-vue-components -D
配置插件
//vite.config.js
const { VantResolver } = require('unplugin-vue-components/resolvers');
const ComponentsPlugin = require('unplugin-vue-components/webpack');
module.exports = {
configureWebpack: {
plugins: [
ComponentsPlugin({
resolvers: [VantResolver()],
}),
],
},
};
使用组件
<template>
<van-button type="primary" />
</template>
引入函数组件的样式
可以在项目的入口文件或公共模块中引入以上组件的样式,这样在业务代码中使用组件时,便不再需要重复引入样式了。
//main.ts
import 'vant/es/toast/style';
import 'vant/es/dialog/style';
import 'vant/es/notify/style';
import 'vant/es/image-preview/style';
//xx.vue 在setup里
import { Toast } from 'vant';
import { Dialog } from 'vant';
import { Notify } from 'vant';
import { ImagePreview } from 'vant';
import { Notify } from 'vant';
const username = ref('');
const password = ref('');
const onSubmit = () => {
Notify({ type: 'success', message: '登录成功' }); //登录成功弹出
};
5.项目样式重置
npm install normalize.css -S
// main.ts
import 'normalize.css'
6.安装css预处理器依赖 ?
npm i sass -S
7.路由vue-router
useRoute 和 useRouter 的区别
- useRoute 路由信息对象,获得路由参数
- useRouter 路由跳转
home.ts 路由跳转 暴露
import { useRouter } from "vue-router"; //useRouter(路由跳转)
//商品详情跳转
export const userDetailGoon = ()=>{
const router = useRouter()
const onDetail = (id:number) => {
router.push({ path: '/detail/'+id }) //动态传参 动态跳转路由界面 详情页需要路由接收
}
return {onDetail}
}
detail.ts 路由接收 暴露
import { ref,onMounted } from "vue";
import { useRoute } from "vue-router"; //导入 useRouter钩子函数useRoute 路由信息对象(获取路由参数)
export const useDetailMessage = ()=>{
const route = useRoute() //拿到路由信息对象
const onClickLeft = () =>{
history.back() //返回上一页
}
onMounted (()=>{
getProductDetail(Number(route.params.id)) //路由接收router.push({ path: '/detail/'+id })的id
})
const getProductDetail = async (id:number)=>{
let data =await RequestProductDetail(id)
let {resultCode,resultInfo} = data
if(resultCode == 1){
product.value =resultInfo
}
}
return {onMounted, onClickLeft}
}
引入
<template>
<div>
<van-nav-bar title="详情页" left-text="返回" left-arrow @click-left="onClickLeft" />
</div>
</template>
<script setup lang="ts">
import { useDetailMessage } from "@/hooks/detail"; //引入
const {onClickLeft} = useDetailMessage () //解构
</script>
8.pinia使用
在stores下脚手架帮忙生成了count.ts的文件,改为cart.ts
import { defineStore } from "pinia"; //1
/**interface ICartProduct{
id:number,
name:string,
url:string,
price:number,
state:boolean,
num:number
}*/
/*interface IState{
list:Array<ICartProduct>
}*/ //cart名字随便取
export const useCounterStore = defineStore("cart",{ //2
state:():IState=>{ //数据
return{
list:[] //购物车商品列表
}
},
actions:{//不用定义mutations
addProduct(product:ICartProduct){//购物车添加方法
this.list.push(product) //这里可以用this
}
},
getters:{
cartList:state => state.list //cartList不能和上面List名字一样
}
})
detail.ts?
import { useCounterStore } from "@/stores/cart";//引入pinia中暴露的函数
//pinia不能用平常的解构,解构了会失去响应性,在addCart里解构
import { Toast } from "vant";
export const useDetailMessage = ()=>{
//购物车
const addCart = ()=>{
const store = useCounterStore() //解构
let cartList = store.cartList //pinia getters
//判断购物车cartList相同商品是否存在,存在则商品数量+1
let oldProduct = cartList.find((item:IProduct)=>{
//购物车cartList的id是否和新加入购物车商品id相同
return item.id == product.value.id})
if(oldProduct){//存在则商品数量+1
oldProduct.num++
}else{//不存在则添加新商品
store.addProduct({ //pinia actions方法
id:product.value.id,
name:product.value.product,
url:product.value.picture,
price:product.value.price,
num:1,
state:false
})
}
Toast.success('加入成功!')
}
return {addCart}
}
pinia持久化存储
安装
npm i pinia-plugin-persist
集成插件
????????stores/index.ts
import { createPinia } from 'pinia'
// 引入持久化插件
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia() //创建pinia存储根store
store.use(piniaPluginPersist) //集成持久化存储插件
export default store
????????main.ts
//引入持久化插件
import store from "@/stores/index"; //默认暴露
app.use(store)
使用
stores/cart.ts
export const useCounterStore = defineStore("cart",{
state:()=>{ //数据
...
},
actions:{//不用定义mutations
....
},
getters:{
...
},
// 使用该插件,开启数据缓存
persist: {
//这里存储默认使用的是session
enabled: true,
strategies: [
{
//key的名称
key: 'cartKey',
//更改默认存储,我更改为localStorage
storage: localStorage,
// 可以选择哪些进入local存储,这样就不用全部都进去存储了
// 默认是全部进去存储
paths: ['list'],
},
],
},
}
9.购物车响应式商品列表
import {useCounterStore} from "@/stores/cart" //引入cart.ts pinia
import {ref} from "vue" //ref包裹为响应式 调用需要.value
// 购物车业务
export const useCart = ()=>{
const store = useCounterStore() //解构
const cartList = ref(store.cartList) //改为响应式购物车商品列表
return{}
}
10.自定义组件
components/HeaderSearch.vue
<template>
<div>
<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="user.headerimg"
:src="user.headerimg"
type="contain"
round
width="40px"
height="40px"
@click="$router.push('/login')"
/>
<router-link to="/login" v-else>登录</router-link>
</template>
</van-nav-bar>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
interface IUser {
nick: string
headerimg: string
}
const store = useUserStore()
const user:IUser = store.userInfo
</script>
?views/Home/index.vue
<template>
<div>
<HeaderSearch></HeaderSearch>
</div>
</template>
<script setup lang="ts">
import HeaderSearch from '@/components/HeaderSearch.vue' //引入
</script>
11.样式穿透
用 :deep()包裹将组件起来
:deep(.van-nav-bar__title) {
background-color: white;
height: 30px;
line-height: 30px;
border-radius: 30px;
box-sizing: border-box;
max-width: 70%;
flex: 1;
text-align: left;
padding-left: 30px;
a {
color: #232326;
font-size: 16px;
}
}
12.mock
下载?
npm i mockjs vite-plugin-mock
根目录下新建mock文件夹 新建文件user.ts 自建接口文档
export const hotType = {
url: '/api/type',
method: 'get',
response: () => {
return {
resultCode: 1,
resultInfo: {
list: [
{
id: 1001,
text: '京东超市',
url: 'https://m15.360buyimg.com/mobilecms/jfs/t1/175540/24/19329/6842/60ec0b0aEf35f7384/ec560dbf9b82b90b.png!q70.jpg',
},
],
},
}
},
}
export const testRegister = {
url: '/api/h52208/register',
method: 'get',
response: () => {
return {
code: 0,
message: 'ok',
data: {
id: '@increment()',
username: '',
password: '',
},
}
},
}
mock文件下新建index.ts ?
import {hotType,testRegister} from './user' 引入
export default [
hotType,
testRegister
]
修改vite.config.ts
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
vue(),
...,
viteMockServe({
// 更多配置见最下方
supportTs: true,
logger: false,
mockPath: './mock/', // 文件位置
}),
],
})
使用接口
api/index.ts
//import axiosServer from '../utils/request'
import axios from 'axios'
/**
* 热门商品
*/
export const RequesetHotProduct =()=>{
return axios({ //不是axiosServer 这里axiossever加了根地址
method:'get',
url:'/api/type'
})
}
动态渲染
import{RequesetHotProduct} from '@/api/index'
/**
* 热门商品
*/
export const useHotProduct = ()=>{
let hotList = ref([])
const getHotProduct = ()=>{
RequesetHotProduct().then(res=>{ //这里不像axiosserver把data过滤了,这里必须要res.data
let {resultCode,resultInfo} = res.data
if(resultCode == 1){
hotList.value = resultInfo.list
}
})
}
onMounted(()=>{
getHotProduct()
})
return {hotList}
}
|