Skip to content
当前页大纲

搭建一个开箱即用的基于 Vue-Cli + Vue2 + Vuex + Vant + TailwindCSS + TypeScript 的工程

UI框架以 Vant 为例

本工程的Github地址

编写此笔记时所使用的Vue-Cli版本为5.0.0Vue版本为2.6.14

相关文档

初始化项目

sh
npm i -g @vue/cli
vue create vue-cli-starter

按照提示操作即可,这样一个基础项目就创建好了

💡

通过上述交互式命令的选项,我们创建了一个带有vue-routervuexESLintPrettier的基于 Vue-Cli 脚手架的 Vue2 项目

编辑tsconfig.json,关闭空检查

json
{
  "compilerOptions": {
    // ...
    "strictNullChecks": false, 
    "noImplicitAny": false 
  }
}

配置EditorConfig

新建.editorconfig,设置编辑器和 IDE 规范,内容根据自己的喜好或者团队规范

ini
# https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

配置ESLint和Prettier

新建.prettierrc.prettierignore文件,填入自己喜欢的配置

json
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": false,
  "tabWidth": 2,
  "printWidth": 120,
  "singleQuote": true,
  "trailingComma": "es5"
}
txt
node_modules
dist

编辑.eslintrc.js

js
module.exports = {
  root: true,
  env: {
    browser: true, 
    node: true,
  },
  extends: [
    'plugin:vue/essential',
    'eslint:recommended',
    '@vue/typescript/recommended',
    'prettier', 
    'plugin:prettier/recommended',
  ],
  plugins: ['prettier'], 
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module', 
  },
  rules: {
    'prettier/prettier': 'error', 
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  },
}

安装TailwindCSS

sh
pnpm add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

编辑tailwind.config.js

js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./public/index.html', './src/**/*.{vue,jsx,tsx}'],
  corePlugins: {
    preflight: true,
  },
  plugins: [],
}

新建src/assets/tailwind.css,填写如下内容,然后在src/main.ts引入它

css
@tailwind base;
@tailwind components;
@tailwind utilities;
ts
// ...
import './assets/tailwind.css' 

环境变量

关于 Vue-Cli 的环境变量,可以参考官方文档

新建.env文件,填入项目所需的环境变量。注意,自定义环境变量名必须以VUE_APP_开头,否则不会被识别,例如

ini
VUE_APP_NAME=vue-cli-starter
VUE_APP_HOST=localhost
VUE_APP_PORT=8080
API_HOST=http://localhost
API_PORT=8080
VUE_APP_BASE_API=$API_HOST:$API_PORT

之后就可以在代码中以 process.env.VUE_APP_XXX 的形式使用自定义环境变量了

迁移至Vue2.7

sh
pnpm update vue
pnpm update @vue/cli*
pnpm update eslint-plugin-vue@9
pnpm rm vue-template-compiler
rm pnpm-lock.yaml
rm -rf node_modules
pnpm install

编辑tsconfig.json,添加vueCompilerOptions字段

json
{
  // ...
  "vueCompilerOptions": {
    "target": 2.7
  }
}

由于vuexvue-router均为不支持组合式APIv3版本,需要通过中间函数来代替访问this下的一些实例方法。新建src/hooks/index.ts,添加如下代码

ts
import { getCurrentInstance } from 'vue'
export function useStore() {
  const { proxy } = getCurrentInstance()
  const store = proxy.$store
  return store
}
export function useRouter() {
  const { proxy } = getCurrentInstance()
  const router = proxy.$router
  return router
}
export function useRoute() {
  const { proxy } = getCurrentInstance()
  const route = proxy.$route
  return route
}

但是vuexmapStatemapGettersmapActionsmapMutations辅助函数依然是无法使用的,如果想使用这些辅助函数,可以尝试安装vuex-composition-helpers这个库

sh
pnpm add vuex-composition-helpers@1.2.0

助手函数

新建src/utils/utils.ts,封装一些辅助函数,具体代码参考我的助手函数封装

请求模块

sh
pnpm add axios

新建src/api/core/http.tssrc/api/core/config.ts,之后的封装逻辑参考我的Axios封装

Mock

sh
pnpm add -D vue-cli-plugin-mock mockjs @types/mockjs

根目录新建mock/index.js,示例如下,根据自己的情况添加添加接口

js
export default {
  'POST /api/login': {
    code: '200',
    message: 'ok',
    data: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MjMyODU2LCJzZXNzaW9uIjoiOTRlZTZjOThmMmY4NzgzMWUzNzRmZTBiMzJkYTIwMGMifQ.z5Llnhe4muNsanXQSV-p1DJ-89SADVE-zIkHpM0uoQs',
    success: true,
  },
}
  • 使用
ts
import { request } from './api'
request('/api/login', { method: 'POST' })

注意,vue-cli-plugin-mock默认是以当前开发服务器的hostpost作为baseURL

状态管理

示例

编辑src/store/index.tssrc/views/AboutView.vue

ts
export default new Vuex.Store({
  state: {
    count: 0, 
  },
  getters: {},
  mutations: {
    
    increment(state) {
      state.count++
    },
    decrement(state) {
      state.count--
    },
  },
  actions: {},
  modules,
})
vue
<template>
  <div class="about">
    <h1>This is an about page</h1>
    <div>
      <button @click="decrement()">-</button>
      <span class="mx-3">{{ count }}</span>
      <button @click="increment()">+</button>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { useState, useMutations } from 'vuex-composition-helpers'
const { count } = useState(['count'])
const { increment, decrement } = useMutations(['increment', 'decrement'])
</script>

持久化

sh
pnpm add vuex-persistedstate
  • 新建src/store/modules/user.ts
ts
type State = typeof state
const state = {
  token: '',
  isLogged: false,
}
const mutations = {
  setToken(state: State, token: string) {
    state.token = token
    state.isLogged = true
  },
  removeToken(state: State) {
    state.token = ''
    state.isLogged = false
  },
}
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions,
}
  • 编辑src/store/index.ts
ts
// ...
import createPersistedState from 'vuex-persistedstate'
// 导入其他vuex模块
const modulesFiles = require.context('./modules', true, /\.ts$/)
const modules = modulesFiles.keys().reduce((modules: Record<string, unknown>, modulePath) => {
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {})
export default new Vuex.Store({
  plugins: [createPersistedState()],
  modules,
  // ...
})
  • 使用
vue
<template>
  <div class="flex justify-center gap-3">
    <button @click="login">Login</button>
    <button @click="removeToken()">Logout</button>
  </div>
</template>
<script lang="ts" setup>
import { useMutations } from 'vuex-composition-helpers'
import { request } from '@/api'
const { setToken, removeToken } = useMutations('user', ['setToken', 'removeToken'])
const login = async () => {
  const res = await request('/api/login', { method: 'POST' })
  setToken(res.data)
}
</script>

UI框架

使用Vant

sh
pnpm add vant@latest-v2

按需引入

⚡ 注意

由于迁移至Vue2.7后,Vant2文档中的使用babel-plugin-import进行按需加载的方式失效了。需要改成用Vue3的方式进行按需加载

sh
pnpm add -D @vant/auto-import-resolver unplugin-vue-components

编辑vue.config.js

js
const { VantResolver } = require('@vant/auto-import-resolver') 
const ComponentsPlugin = require('unplugin-vue-components/webpack') 
module.exports = defineConfig({
  // ... 
  configureWebpack: {
    plugins: [
      ComponentsPlugin({
        resolvers: [VantResolver()],
      }),
    ],
  },
})

这样就完成了 Vant 的按需引入,就可以直接在模板中使用 Vant 组件了,unplugin-vue-components会解析模板并自动注册对应的组件,@vant/auto-import-resolver会自动引入对应的组件样式

移动端适配

此插件的参数配置文档看这里

sh
pnpm add -D postcss-px-to-viewport-8-plugin

⚡注意

由于Vant使用的设计稿宽度是375,而通常情况下,设计师使用的设计稿宽度更多是750,那么Vant组件在750设计稿下会出现样式缩小的问题

解决方案: 当读取的node_modules文件是vant时,那么就将设计稿宽度变为375,读取的文件不是vant时,就将设计稿宽度变为750

编辑postcss.config.js,增加如下postcss-px-to-viewport-8-plugin配置项

js
const path = require('path') 
module.exports = {
  plugins: {
    // ... 
    'postcss-px-to-viewport-8-plugin': {
      viewportWidth: (file) => {
        return path.resolve(file).includes(path.join('node_modules', 'vant')) ? 375 : 750
      },
      unitPrecision: 6,
      landscapeWidth: 1024,
    },
  },
}

MIT License.