目标
使用 create-vue 脚手架创建项目,搭建V3+ts脚手架,封装axios进行请求函数自适应,保证pinia本地持久化存储。
实现如下:
自行选择执行创建命令(本人使用的是pnpm)
pnpm create vue
# or
npm init vue@latest
# or
yarn create vue
选择项目依赖内容 这里可以自行选择,本项目主要是V3+TS+pinia
✔ Project name: … 项目名
✔ Add TypeScript? … No / `Yes`
✔ Add JSX Support? … `No` / Yes
✔ Add Vue Router for Single Page Application development? … No / `Yes`
✔ Add Pinia for state management? … No / `Yes`
✔ Add Vitest for Unit Testing? … `No` / Yes
✔ Add Cypress for both Unit and End-to-End testing? … `No` / Yes
✔ Add ESLint for code quality? … No / `Yes`
✔ Add Prettier for code formatting? … No / `Yes`
Scaffolding project in /Users/zhousg/Desktop/patient-h5-100...
Done. Now run:
cd 项目名
pnpm install
pnpm lint
pnpm dev
``
husky配置(不需要可以跳过)
官网cv初始化与安装:typicode.github.io/husky/getti…
pnpm dlx husky-init
pnpm install
修改 .husky/pre-commit 文件
#!/usr/bin/env sh
pnpm lint
用于合并代码时调整代码格式,会自动运行pnpm lint。
项目目录解构(了解)
./src
├── assets # 静态资源,图片...
├── components # 通用组件
├── composable # 组合功能通用函数 - 新增的
├── icons # svg 图标
├── router # 路由
│ └── index.ts
├── services # 接口服务 API - 新增的
├── stores # 状态仓库
├── types # TS 类型 - 新增的
├── utils # 工具函数 - 新增的
├── views # 页面 - 新增的
├── App.vue # 根组件
└── main.ts # 入口文件
路由相关代码解析(了解)
import {createRouter,createWebHistory} from 'vue-router'
// createRouter 创建路由实例
// createWebHistory() 是开启history模块
// createWebHashHistory() 是开启hash模式
// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 /
// https://vitejs.dev/guide/build.html#public-base-path
// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts 添加配置 base: my-path,路由这就会加上 my-path 前缀了
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: []
})
export default route
设置用户状态(.d.ts)
准备用户信息的类型,types/user.d.ts
/* 用户信息 */
export type User = {
/* token令牌 */
token: string
/* 用户ID */
id: string
/* 用户名称 */
account: string
/* 手机号 */
mobile: string
/* 头像 */
avatar: string
}
数据的持久化(Pinia)
使用 pinia-plugin-persistedstate 实现 Pinia 仓库状态持久化。
安装
pnpm i pinia-plugin-persistedstate
# or
npm i pinia-plugin-persistedstate
# or
yarn add pinia-plugin-persistedstate
在main.ts中使用
import persist from 'pinia-plugin-persistedstate'
const app = createApp(App)
// 注意使用的方式
app.use(createPinia().use(persist))
配置stores/user.ts(user.ts新增)
import type { User } from '@/types/user'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export default defineStore(
'cp-user',
() => {
// 用户信息
const user = ref<User>()
// 设置用户,登录后使用
const setUser = (u: User) => {
user.value = u
}
// 清空用户,退出后使用
const delUser = () => {
user.value = undefined
}
return { user, setUser, delUser }
},
{
persist: true
}
)
抽取 Pinia 代码(Pinia)
stores/index(自建)
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
export * from './user'
// 创建 Pinia 实例
const pinia = createPinia()
// 使用 Pinia 插件
pinia.use(persist)
// 导出 Pinia 实例,给 main 使用
export default pinia
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from './stores'
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')
stores 统一导出(优雅)
// 写法 1
// import { useUserStore } from './user'
// export { useUserStore }
// 写法 2
// export { useUserStore } from './user'
// 写法 3
export * from './user'
App.vue中使用方法不变
import { useUserStore } from './stores'
移动端的适配(vw)
使用 vw 完成移动端适配
1.先安装
npm install postcss-px-to-viewport -D
# or
yarn add -D postcss-px-to-viewport
# or
pnpm add -D postcss-px-to-viewport
2.配置 postcss.config.js
// eslint-disable-next-line no-undef
module.exports = {
plugins: {
'postcss-px-to-viewport': {
// 以设备宽度 375 为基准计算 vw 的值
// 假如 100px 的 div
// 375 宽度下,最终转换出的 vw 应该是:x / 100vw = 100px / 375px,x 等于 26.66vw
// 而转换出来的 26.66vw 自然在不同的设备宽度下所表示的 div 宽度会不一样,例如 414 设备下
// 26.66vw / 100vw = div 宽度 / 414px
viewportWidth: 375,
},
},
};
请求实例封装
1.基本准备,utils/request.ts
import axios from 'axios'
const instance = axios.create({
// TODO 1. 基础地址,超时时间
})
instance.interceptors.request.use(
(config) => {
// TODO 2. 携带 token
return config
},
(err) => Promise.reject(err)
)
instance.interceptors.response.use(
(res) => {
// TODO 3. 处理业务失败
// TODO 4. 摘取核心响应数据
return res
},
(err) => {
// TODO 5. 处理 401 错误
return Promise.reject(err)
}
)
export default instance
2.基本封装
import axios from 'axios'
import { useUserStore } from '../stores'
import router from '../router'
import { showToast } from 'vant'
const baseURL = '基地址'
const instance = axios.create({
// TODO 1. 基础地址,超时时间
baseURL,
timeout: 10000
})
instance.interceptors.request.use(
(config) => {
// TODO 2. 携带 token
const store = useUserStore()
if (store.user?.token && config.headers) {
config.headers['Authorization'] = `Bearer ${store.user?.token}`
}
return config
},
(err) => Promise.reject(err)
)
instance.interceptors.response.use(
(res) => {
// TODO 3. 处理业务失败
if (res.data?.code !== 10000) {
showToast(res.data?.message || '网络异常')
return Promise.reject(res.data)
}
// TODO 4. 摘取核心响应数据
return res.data
},
(err) => {
// TODO 5. 处理 401 错误
if (err.response.status === 401) {
// 删除用户信息
const store = useUserStore()
store.delUser()
// 跳转登录,带上接口失效所在页面的地址,登录完成后回跳使用
router.push(`/login?returnUrl=${router.currentRoute.value.fullPath}`)
}
return Promise.reject(err)
}
)
export { baseURL, instance }
请求函数封装
导出一个通用的请求工具函数,支持设置响应数据类型。
import axios, { type Method } from 'axios'
const request = <T>(
url: string,
method: Method = 'get',
submitData?: object
) => {
return instance<T, { code: number; data: T; message: string }>({
url,
method,
// data, params
[method.toUpperCase() === 'GET' ? 'params' : 'data']: submitData
})
}
export { baseURL, instance, request }