今日本前端小白给各位大佬带来如何重第一步开始搭建出一个由Vue3、Typescript与Vant组件库的H5项目,本文有不足之处还望各位掘金大佬们不要手下留情,请狠狠的践踏我吧!!!
今日白熊镇楼
Vue3 + TS + Vant项目
项目起步
1.项目搭建
使用 create-vue 脚手架创建项目。
pnpm create vue
# or
npm init vue@latest
# or
yarn create vue
✔ Project name: … patients-h5-100
✔ 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 patient-h5-100
pnpm install
pnpm lint
pnpm dev
2.Eslint的配置
修改.eslintrc.cjs文件,补充配置项
{
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true,
semi: false,
printWidth: 80,
trailingComma: 'none',
endOfLine: 'auto'
}
],
'vue/multi-word-component-names': [
'warn',
{
ignores: ['index']
}
],
'vue/no-setup-props-destructure': ['off']
}
}
- 格式:单引号,没有分号,行宽度 80 字符,省略最后一个逗号,换行字符串自动(系统不一样换行符号不一样)。
- Vue 组件需要大驼峰命名,除去 index 之外,App 是默认支持的。
- 允许对 props 进行解构,因为我们会开启解构保持响应式的语法糖。
# 修复格式
pnpm lint
{
// 省略其他
"editor.codeActionsOnSave": {
"source.fixAll": true,
}
}
3.husky配置
安装
pnpm dlx husky-init && pnpm install
修改 .husky/pre-commit 文件
pnpm lint
测试效果
- 故意调整代码格式导致不符合eslint规则,例如多添加一些空行
- git add . git commit -m "xxx" 。会发现,提交时,会自动运行pnpm lint。
4.路由配置
配置路由
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 router
如何创建实例方式
createRouter()
如何设置路由模式
createWebHistory()
// or
createWebHashHistory()
import.meta.env. BASE_URL 值来自哪里?
# vite.config.ts 的 base 属性的值
base的作用
# 项目的基础路径前缀,默认是 /
5.pinia设置与创建持久化
a.pinia设置
import type {
User
} from '@/types/user'
import {
defineStore
} from 'pinia'
import {
ref
} from 'vue'
export const useUserStore = defineStore('cp-user', () => {
// 用户信息
const user = ref < User > ()
// 设置用户,登录后使用
const setUser = (u: User) => {
user.value = u
}
// 清空用户,退出后使用
const delUser = () => {
user.value = undefined
}
return {
user,
setUser,
delUser
}
})
Pinia 存储这个数据的意义?
答:数据共享,提供给项目中任何位置使用。
如果存储了数据,刷新页面后数据还在吗?
答:不在,现在仅仅是存储在了 JS 内存中,需要进行本地存储(持久化)。
b.数据持久化
使用 pinia-plugin-persistedstate 实现 Pinia 仓库状态持久化,且完成测试。
安装
pnpm i pinia-plugin-persistedstate
# or
npm i pinia-plugin-persistedstate
# or
yarn add pinia-plugin-persistedstate
配置
{
persist: true
}
c.抽取Pinia代码
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
import useUserStore from './user'
export const useStore = () => {
return {
userStore: useUserStore()
}
}
// 创建 Pinia 实例
const pinia = createPinia()
// 使用 Pinia 插件
pinia.use(persist)
// 导出 Pinia 实例,给 main 使用
export default pinia
统一导出:export * from './user'
在APP.vue中
import { useUserStore } from './stores'
统一导出是什么意思?
答:一个模块下的所有资源通过 index 进行导出。
6.Vant组件库使用
a.安装
# Vue3 项目,安装最新版 Vant
npm i vant
# 通过 yarn 安装
yarn add vant
# 通过 pnpm 安装
pnpm add vant
b.引入样式
//main.ts
import 'vant/lib/index.css'
c.按需引入
<script setup lang="ts">
import {
Button as VanButton
} from 'vant'
</script>
<template>
<van-button type="primary">按钮</van-button>
</template>
为什么不全局使用?
全局使用是全量加载,项目体积变大,加载慢。
d.自动按需加载
安装
pnpm add unplugin-vue-components -D
配置
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// #1
import Components from 'unplugin-vue-components/vite'
// #2
import { VantResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// #3 自动导入的插件
Components({
// #5 默认 true,开启自动生成组件的类型定义文件,而 vant 已经自带类型了,无需生成
dts: false,
// #4 main.ts 已经引入了所有的 vant 样式,不需要自动导入样式,只需要自动导入组件即可
resolvers: [VantResolver({ importStyle: false })]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
7.移动端适配
安装
npm install postcss-px-to-viewport -D
# or
yarn add -D postcss-px-to-viewport
# or
pnpm add -D postcss-px-to-viewport
配置 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,
},
},
};
8.请求实例封装
基本准备
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
基础封装
import { useUserStore } from '@/stores'
import router from '@/router'
import axios from 'axios'
import { showToast } from 'vant'
// 1. 新 axios 实例,基础配置
const baseURL = 'https://xxx.xxx/'
const instance = axios.create({
baseURL,
timeout: 10000
})
// 2. 请求拦截器,携带 token
instance.interceptors.request.use(
(config) => {
const store = useUserStore()
if (store.user?.token && config.headers) {
config.headers['Authorization'] = `Bearer ${store.user?.token}`
}
return config
},
(err) => Promise.reject(err)
)
// 3. 响应拦截器,剥离无效数据,401 拦截
instance.interceptors.response.use(
(res) => {
// 后台约定,响应成功,但是 code 不是 10000,是业务逻辑失败
if (res.data?.code !== 10000) {
showToast(res.data?.message || '网络异常')
return Promise.reject(res.data)
}
// 业务逻辑成功,返回响应数据,作为 axios 成功的结果
return res.data
},
(err) => {
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 }
请求函数的封装
添加上泛型
const request = <T>(
url: string,
method: Method = 'get',
submitData?: object
) => {
// #2
return instance.request<
T,
{
code: string
message: string
data: T
}
>({
url,
method,
[method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
})
}