vue3+TS+vite+pnpm 从0到1搭建移动端项目

1,337 阅读5分钟

长路漫漫,极少成多,静下心来,好好做自己,希望这篇文章会帮助到你。

pnpm介绍&安装

pnpm 本质是一个包管理工具,和npm yarn 没有用法上的区别,主要优势在于 安装包的速度极快·磁盘空间利用效率高.

安装:npm i pnpm -g
npm命令pnpm等效
npm installpnpm install
npm i axiospnpm add axios
npm i webpack -Dpnpm add webpack -D
npm run devpnpm dev

项目创建

使用 create-vue 脚手架创建项目

create-vue参考地址:github.com/vuejs/creat…

步骤:

  1. 执行创建命令
`pnpm create vue 
#or
npm init vue@latest 
#or 
yarn create vue`
  1. 选择项目依赖内容
Project name: … patients-h5-100Add TypeScript? … No / `Yes`Add JSX Support? … `No` / YesAdd Vue Router for Single Page Application development? … No / `Yes`Add Pinia for state management? … No / `Yes`Add Vitest for Unit Testing? … `No` / YesAdd Cypress for both Unit and End-to-End testing? … `No` / YesAdd 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

vscode插件安装#

安装:项目开发需要的一些插件

必装:

  • Vue Language Features (Volar) vue3语法支持
  • TypeScript Vue Plugin (Volar) vue3中更好的ts提示
  • Eslint 代码风格校验

注意

  • vscode 安装了 Prettier 插件的可以先 禁用,或者关闭保存自动格式化功能,避免和项目的 Eslint 风格冲突。

可选:

  • gitLens 代码git提交记录提示
  • json2ts json自动转ts类型
  • Error Lens 行内错误提示

代码检查工作流

husky 配置

  • 初始化与安装
pnpm dlx husky-init && pnpm install
  • 修改 .husky/pre-commit 文件
pnpm lint

lint-staged 配置

  • 安装
pnpm i lint-staged -D
  • 配置 package.json
{
  // ... 省略 ...
  "lint-staged": {
    "*.{js,ts,vue}": [
      "eslint --fix"
    ]
  }
}
{
  "scripts": {
    // ... 省略 ...
    "lint-staged": "lint-staged"
  }
}
  • 修改 .husky/pre-commit 文件
pnpm lint-staged

项目结构调整

每一个目录结构的作用

./src
├── assets        `静态资源,图片...`
├── components    `通用组件`
├── composable    `组合功能通用函数`
├── icons         `svg图标`
├── router        `路由`
│   └── index.ts
├── services      `接口服务API`
├── stores        `状态仓库`
├── styles        `样式`
│   └── main.scss
├── types         `TS类型`
├── utils         `工具函数`
├── views         `页面`
├── main.ts       `入口文件`
└──App.vue       `根组件`

项目使用sass预处理器,安装sass,即可支持scss语法:

pnpm add sass -D

路由代码解析

mport { 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

vant组件库#

实现:完整使用vant组件库

文档

安装:

# Vue 3 项目,安装最新版 Vant
npm i vant
# 通过 yarn 安装
yarn add vant
# 通过 pnpm 安装
pnpm add vant

样式:main.ts

import { createApp } from 'vue'
import App from './App.vue'
import pinia from './stores'
import router from './router'
// 样式全局使用
import 'vant/lib/index.css'
import './styles/main.scss'

const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')

组件按需使用:App.vue

<script setup lang="ts">
import { Button as VanButton } from 'vant'
</script>

<template>
  <van-button>按钮</van-button>
</template>

<style scoped></style>

移动端适配#

实现:使用 vw 完成移动端适配

VantUI文档

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的值
      viewportWidth: 375,
    },
  },
};

css变量主题定制#

实现:使用css变量定制项目主题,和修改vant主题

  • 如何定义 css 变量使用 css 变量
:root {
  --main: #999;
}
a {
  color: var(--main)
}
  • 定义项目的颜色风格,覆盖vant的主题色 官方文档

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);
}

App.vue

<script setup lang="ts"></script>

<template>
  <!-- 验证vant颜色被覆盖 -->
  <van-button type="primary">按钮</van-button>
  <a href="#">123</a>
</template>

<style scoped lang="scss">
// 使用 css 变量
a {
  color: var(--cp-primary);
}
</style>

数据持久化#

使用 pinia-plugin-persistedstate 实现pinia仓库状态持久化,且完成测试

参考文档

image-20220730222310940

  • 安装
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
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 代码简洁,职能单一,入口唯一

  • 抽取pinia实例代码,职能单一

stores/index

import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

// 创建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 pinia from './stores'
import router from './router'
import './styles/main.scss'

const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')
  • 统一导出,代码简洁,入口唯一

stores/index

export * from './modules/user'

App.vue

-import { useUserStore } from './stores/user'
+import { useUserStore } from './stores'

请求工具函数

拦截器逻辑

实现:token请求头携带,错误响应处理,401错误处理

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

代码实现:

import { useUserStore } from '@/stores'
import router from '@/router'
import axios from 'axios'
import { showToast } from 'vant'

// 1. 新axios实例,基础配置
const instance = axios.create({
  baseURL: 'https://consult-api.itheima.net/',
  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)
  }
)

export { baseURL, instance }

工具函数封装

导出一个通用的请求工具函数,支持设置响应数据类型

  • 导出一个通用的请求工具函数
import axios, { AxiosError, type Method } from 'axios'

// 4. 请求工具函数
const request = (url: string, method: Method = 'GET', submitData?: object) => {
  return instance.request({
    url,
    method,
    [method.toUpperCase() === 'GET' ? 'params' : 'data']: submitData
  })
}
  • 支持不同接口设不同的响应数据的类型

加上泛型

// 这个需要替换axsio.request默认的响应成功后的结果类型
// 之前是:传 { name: string } 然后res是   res = { data: { name: string } }
// 但现在:在响应拦截器中返回了 res.data  也就是将来响应成功后的结果,和上面的类型一致吗?
// 所以要:request<数据类型,数据类型>() 这样才指定了 res.data 的类型
// 但是呢:后台返回的数据结构相同,所以可以抽取相同的类型
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
  })
}

组件代码片段

配置:一个vue3页面的基础代码片段

  1. 打开代码片段设置界面:
  • windows:ctrl + shift + p
  • mac:cmmmand + shift + p
  1. 新建全局代码片段文件
  2. 拷贝一下代码,保存即可,输入vueps
{
  "Vue 页面 TS 版": {
		"scope": "vue,markdown",
		"prefix": "vpt",
		"body": [
			"<script setup lang="ts"></script>",
			"",
			"<template>",
			"  <div class="$1-page">$1</div>",
			"</template>",
			"",
			"<style lang="scss" scoped></style>",
			""
		],
		"description": "Vue 页面 TS 版"
	}
}  

或者安装:vue-vscode-snippets 插件,快捷键可以看插件文档

自动按需加载

:实现自动按需加载,和自动导入

官方文档

手动按需使用组件比较麻烦,需要先导入。配置函数自动按需导入后直接使用即可。

  • 安装:
# 通过 npm 安装
npm i unplugin-vue-components -D
# 通过 yarn 安装
yarn add unplugin-vue-components -D
# 通过 pnpm 安装
pnpm add unplugin-vue-components -D
  • 配置:
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    // 解析单文件组件的插件
    vue(),
    // 自动导入的插件,解析器可以是 vant element and-vue 
    Components({
      dts: false,
      // 原因:Toast Confirm 这类组件的样式还是需要单独引入,样式全局引入了,关闭自动引入
      resolvers: [VantResolver({ importStyle: false })]
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})