vue3药企项目第一天
初始化项目:
npm i pnpm -g
pnpm 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 yaoqi-yume
pnpm install
pnpm dev
下载包:
pnpm add sass -D
pnpm add vant
pnpm add -D postcss-px-to-viewport //使用 vw 完成移动端适配
pnpm i pinia-plugin-persistedstate //pinia数据持久化
路由代码解析
import { createRouter, createWebHistory } from 'vue-router'
// createRouter 创建路由实例,===> new VueRouter()
// history 是路由模式,hash模式,history模式
// createWebHistory() 是开启history模块 http://xxx/user
// createWebHashHistory() 是开启hash模式 http://xxx/#/user
// 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
main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import pinia from './stores'
import App from './App.vue'
import router from './router'
// 导入 vant 的样式
import 'vant/lib/index.css'
// 导入全局样式
import './styles/main.scss'
// 导入 svg 的精灵图
// import 'virtual:svg-icons-register'
import persist from 'pinia-plugin-persistedstate'
const app = createApp(App)
app.use(createPinia().use(persist))
app.use(pinia)
app.use(router)
app.mount('#app')
测试vant代码引入:app.vue
<script setup lang="ts">
import { Button as VanButton } from 'vant'
</script>
<template>
<van-button>按钮</van-button>
</template>
<style scoped></style>
移动端适配
配置: postcss.config.cjs
// eslint-disable-next-line no-undef
module.exports = {
plugins: {
'postcss-px-to-viewport': {
// 设备宽度375计算vw的值
viewportWidth: 375,
},
},
};
css变量主题定制
styles/main.scss
:root {
// 问诊患者:色板
--cp-primary: #16C2A3;
--cp-plain: #EAF8F6;
--cp-orange: #FCA21C;
--cp-text1: #121826;
--cp-text2: #3C3E42;
--cp-text3: #6F6F6F;
--cp-tag: #848484;
--cp-dark: #979797;
--cp-tip: #C3C3C5;
--cp-disable: #D9DBDE;
--cp-line: #EDEDED;
--cp-bg: #F6F7F9;
--cp-price: #EB5757;
// 覆盖vant主体色
--van-primary-color: var(--cp-primary);
}
用户状态仓库
types/user.d.ts
// 用户信息
export type User = {
/** token令牌 */
token: string
/** 用户ID */
id: string
/** 用户名称 */
account: string
/** 手机号 */
mobile: string
/** 头像 */
avatar: string
}
stores/user.ts
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 };
},
{
persist: true,
}
);
stores统一导出
stores/index
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
// 创建pinia实例
const pinia = createPinia()
// 使用pinia插件
pinia.use(persist)
export default pinia
// 统一导出 store
export * from './user'
请求拦截器逻辑
utils/request.ts
import { useUserStore } from '@/stores'
import router from '@/router'
import axios, { AxiosError, type Method } from 'axios'
import { showToast } from 'vant'
const baseURL = 'https://consult-api.itheima.net'
// 1. 新axios实例,基础配置
const instance = axios.create({
baseURL: 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({
path: '/login',
query: { returnUrl: router.currentRoute.value.fullPath }
})
}
return Promise.reject(err)
}
)
type Data<T> = {
code: number
message: string
data: T
}
// 4. 请求工具函数
const request = <T>(url: string, method: Method = 'get', submitData?: object) => {
return instance.request<T, Data<T>>({
url,
method,
[method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
})
}
export { baseURL, instance, request }
测试请求工具
App.vue
<script setup lang="ts">
import { request } from '@/utils/request'
import type { User } from './types/user'
import { Button as VanButton } from 'vant'
import { useUserStore } from './stores'
// 测试,请求拦截器,是否携带token,响应拦截器401拦截到登录地址
const getUserInfo = async () => {
const res = await request('/patient/myUser')
console.log(res)
}
// 测试,响应拦截器,出现非10000的情况,和返回剥离后的数据
const store = useUserStore()
const login = async () => {
const res = await request<User>('/login/password', 'POST', {
mobile: '13211112222',
// 密码 abc123456 测试:出现非10000的情况
password: 'abc12345'
})
store.setUser(res.data)
}
</script>
<template>
<van-button type="primary" @click="getUserInfo">获取个人信息</van-button>
<van-button type="primary" @click="login">登录</van-button>
</template>