Vue3 项目模版搭建(一)

1,593 阅读5分钟

模板技术信息:

模版工程化配置:

文章系列目录

Vue3 项目模版搭建(一)

Vue3 项目模版搭建(二)

前言

近期接触了不少 vue 相关项目,而官方提供的模版相对简易,并没有集成过多的工程化配置。在这里就自己搭建一个相对而言比较完备的 vue 模版。由于内容相对还是比较多的,所以作为两篇文章进行叙述,文章目录及技术 如上所示。废话就不多说了,下面开始进入主题。

项目github 地址: vue3-vite-ts-pnpm

项目初始化

这里直接使用 vite 官方 提供的 vue-ts 模版进行基础项目构建。自行配置 ts 环境也可以。【网上有很多类似教程这里就不多赘述了】

PS:本文会尽可能多的配上图文说明,便于更好的理解。构建过程中使用的最近比较🔥热的 pnpm 包管理工具,当然如果你想使用 npmyarn 也是完全 👌 的 。

全局安装 pnpm,如果之前安装过就不用再安装了

npm i pnpm -g

初始化 vue-ts 模版

pnpm init vite@latest

image-20220110193325636.png 进入项目目录并安装依赖

cd vue3-vite-ts-pnpm && pnpm i

image-20220110194838271.png 启动项目,浏览器输入 http://localhost:3000/ 即可

pnpm run dev 

image-20220110195212570.png

vite 配置

更新 vite 基本配置,修改配置文件 vite.config.ts 内容如下

import { UserConfigExport, ConfigEnv } from 'vite';
import { viteMockServe } from 'vite-plugin-mock';
import vue from '@vitejs/plugin-vue';
// import eslintPlugin from "vite-plugin-eslint";
import { loadEnv } from 'vite';
import { resolve } from 'path';

export default (): UserConfigExport => {

  return {
    plugins: [
      vue()
    ],

    resolve: {
      // 配置 src 目录映射
      alias: {
        '@': resolve(__dirname, 'src')
      }
    },

    base: './', // 打包路径

    server: {
      port: 3000, // 端口
      open: true, // 启动打开浏览器
      cors: true, // 跨域
      proxy: {    // 代理
        "/api": {
          target: "http://localhost:8080/api/", // 目标地址
          changeOrigin: true, // 修改源
          secure: false, // ssl
          rewrite: path => path.replace("/api/", "/")
        }
      }
    }
  };
};

这里 path 可能会报 ts 模块类型警告,开发环境下安装相应模块类型声明包即可!

pnpm i @types/node -D

image-20220110200701477.png

路由配置

安装 vue-router

安装 vue3 配套语法的 vue-router@4.x 版本路由

pnpm i vue-router@4

image-20220110201134821.png

动态路由

动态路由:依据 src/views 视图目录下文件或目录自动生成对应路由信息。如:src/views/404.vue 文件对应路由为:/404src/views/about/me/index.vue 文件对应路由为:/about/me 等等。

src 目录下,新建 router 文件夹并创建 index.ts 文件,如下 image-20220112135259941.png 安装第三方工具库 lodash-es 及类型声明文件 @types/lodash-es,选择 lodash-es 而不选择 lodash Correct way to import lodash

pnpm i lodash-es -D
pnpm i @types/lodash-es -D

依据已有的文件 全局导入 动态添加路由,在src/router/index.ts 文件添加下面内容

import { createRouter , createWebHistory, RouteRecordRaw } from 'vue-router';
import { camelCase, forEach } from 'lodash-es';
import HelloWorld from '@/components/HelloWorld.vue';

const modules = import.meta.glob('../views/**/index.vue');
const pages = import.meta.glob('../views/*.vue');

/**
 * 动态添加路由配置
 * @param routers 文件模块对象
 * @param routes 路由对象数组
 * @param type 动态添加到路由页面类型
 */
const addRoutes = (routers: any, routes: Array<RouteRecordRaw>, type: string = 'pages') => {
  forEach(routers, item => {
    const routePath = item.name.slice(9, type === 'pages' ? -4 : -10);
    routes.push({
        path: `/${routePath}`,
        name: camelCase(routePath),
        // fix: The above dynamic import cannot be analyzed by vite.(warning)
        component: routers[item.name],
    })
  })
}

// 初始化首页
const routes: Array<RouteRecordRaw> =  [
	{
		path: '/',
		name: 'Index',
		component: HelloWorld,
	},
];

addRoutes(modules, routes, 'modules');
addRoutes(pages, routes);

// 404
routes.push({
  path: '/:pathMath(.*)*',
  component: () => import('@/views/404.vue'),
})

const router = createRouter({
  history: createWebHistory(),
  routes
});

// 路由拦截器, 自定义拦截内容
router.beforeEach((to, from, next) => {
  next();
});

export default router;

解决 vite 别名 @ 引入文件 ts 警告及路径提示问题,在 tsconfig.ts 文件中添加下面内容即可:

 "compilerOptions": {
    ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },

挂载路由

app 下挂载路由,修改 main.ts 文件内容如下

import { createApp } from 'vue'
import App from './App.vue'
import router from './router';

const app = createApp(App);

// 挂载路由
app.use(router);

app.mount('#app')

测试路由

添加简单测试页面,同时配置路由页面,修改 App.vue 文件内容,⚠️ 在模板中替换为 <router-view /> 组件。

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <router-view></router-view>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

添加测试路由页面目录如下(只截取了当前视图目录结构) image-20220112002551392.png 只是为了测试,页面内容可以随便填写,这里给出一个简易内容模版

<template>
  <div>home page</div>
</template>

状态管理(Pinia & Vuex)

vuexpiniaPiniaVue.js 的轻量级状态管理库,用法与 vuex 基本相同,具体异同可参考 Pinia与Vuex的对比,模版 demo 中以 Pinia 为例;不过下面也给出了 Vuex 简单示例说明。

Pinia

pinia 安装

pnpm i pinia

pinia 状态配置

src 目录下,新建 store 文件夹并创建 index.ts 文件;新建 modules 文件夹及 app.js,如下 image-20220112142032018.png

src/store/index.ts 添加内容如下

import type { App } from 'vue';
import { createPinia } from 'pinia';

const store = createPinia();

// 注入 pinia
export function setupStore(app: App<Element>) {
  app.use(store);
}

export { store };

src/store/modules/app.ts 添加内容如下

import { defineStore } from 'pinia';

interface AppState {
  count: number;
}

export const useAppStore = defineStore({
  // 必填项,store 唯一标识
  id: 'app',
  // 状态数据
  state: (): AppState => ({
    count: 0
  }),
  // 计算操作
  getters: {
    countPow2(): number {
      return this.count ** 2;
    }
  },
  // 方法操作
  actions: {
    increment() {
      this.count++;
    },
    decrease() {
      this.count--;
    }
  }
})

pinia 状态注入

src/main.ts 添加内容如下

import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
import { setupStore } from '@/store';

const app = createApp(App);

// 挂载路由
app.use(router);

// 注入 store
setupStore(app);

app.mount('#app')

pinia 状态管理测试

首页路由配置修改,src/router/index.ts 添加修改内容部分如下

import Home from '@/views/index.vue';

// 初始化首页
const routes: Array<RouteRecordRaw> =  [
	{
		path: '/',
		name: 'index',
		component: Home,
	},
];

src/views/index.vue 添加测试 store 示例内容如下

<template>
  <h2>home page</h2>
  <div>count: {{appStore.count}}</div>
  <div>countPow2: {{appStore.countPow2}}</div>
  <button @click="appStore.increment">count + 1</button>
  <button @click="appStore.decrease">count - 1</button>
</template>

<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';

const appStore = useAppStore();
</script>

Vuex

vuex 安装

pnpm i vuex

vuex 状态配置

src 目录下,新建 store 文件夹并创建 index.ts 文件,添加内容如下

import { createStore, Store, useStore as useVuexStore } from 'vuex';
import { InjectionKey } from 'vue';

export interface State {
  count: number;
}

export const key: InjectionKey<Store<State>> = Symbol('store-key'); // 页面中使用 state 时会有对应提示说明

// 处理 state 类型
export const useStore = () => {
  return useVuexStore(key);
};

const store = createStore<State>({
  // 数据源
  state: {
    count: 0
  },
  // 计算操作
  getters: {},
  // 同步操作
  mutations: {
    increment(state) {
      state.count += 1;
    }
  },
  // 异步操作
  actions: {
    incrementAction: ({ commit }) => {
      setTimeout(() => {
        commit('increment');
      }, 2000);
    }
  },
  // 模块划分
  modules: {}
});

export default store;

vuex 状态注入

src/main.ts 添加内容如下

import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
import store, { key } from '@/store';

const app = createApp(App);

// 挂载路由
app.use(router);

// 注入 store
app.use(store, key);

app.mount('#app')

vuex 状态管理测试

首页路由配置修改,src/router/index.ts 添加修改内容部分如下

import Home from '@/views/index.vue';

// 初始化首页
const routes: Array<RouteRecordRaw> =  [
	{
		path: '/',
		name: 'index',
		component: Home,
	},
];

src/views/index.vue 添加测试 store 示例内容如下

<template>
  <h2>home page</h2>
  <button @click="increment">同步 count is: {{ store.state.count }}</button>
  <button @click="incrementAction"> 异步 count is: {{ store.state.count }}</button>
</template>

<script setup lang="ts">
import { useStore } from '../store';

const store = useStore();

// 同步操作
const increment = () => {
  store.commit('increment');
};

// 异步操作
const incrementAction = () => {
  store.dispatch('incrementAction');
};
</script>

网络请求

axios 安装

pnpm i axios

axios 封装

src 目录下新增 utils/http 文件目录及添加文件,具体内容 image-20220112195754699.png

// 目录文件说明
.
└── http
    ├── Axios.ts // axios 封装类
    ├── constant.ts // 静态变量
    └── index.ts

axios 测试

src/views/index.vue 文件 script 中添加下面测试内容

<script setup lang="ts">
  import { defHttp } from '@/utils/http';

  (async () => {
    // 测试封装的 axios
    const res = await defHttp.request({
      url: '/api/sug?code=utf-8&q=电脑',
      method: 'GET',
    });

    console.log(res);
  })()
</script>

请求跨域 vite 代理配置,修改 proxy 配置内容如下:

// vite.config.ts
proxy: {
  "/api": {
    target: "https://suggest.taobao.com", // 目标地址
      changeOrigin: true, // 修改源
      secure: false, // ssl
      rewrite: path => path.replace("/api/", "/")
  }
}

全局环境变量配置

项目根目录下新建 .env.development.env.production 环境配置文件 【可依据项目需要是否添加全局环境变量配置】 image-20220120174741506.png 添加下面全局环境变量至 .env.development.env.production 文件

# 定义请求的基础 URL,方便跨域请求时使用
VITE_GLOB_API_URL = /api

# 接口服务地址
VITE_PROXY = https://suggest.taobao.com

使用全局环境变量修改替换 proxy 配置相关信息

// vite.config.ts
...
import { loadEnv } from 'vite';

export default ({ mode }): UserConfigExport => {
  const env = loadEnv(mode, process.cwd());

  return {
    ...
    server: {
     	...
      proxy: {
        // 依据环境获取 .env.development 或 .env.production 文件中的配置
        [env.VITE_GLOB_API_URL]: {
          // '/api'
          target: env.VITE_PROXY, // 在 .env.development 中配置
          changeOrigin: true, // 修改源
          // ws: true, // if you want to proxy websockets
          rewrite: path =>
            path.replace(new RegExp(`^${env.VITE_GLOB_API_URL}`), '')
        }
      }
    }
  };
};

Mock 数据

PS:在开发环境选择使用 vite-plugin-mock 插件而不使用 mockjs 进行数据接口的模拟,原因是 mockjs 本地开发看不到请求参数和响应结果,所以本地 mock 采用 Node.js 中间件进行参数拦截,具体可参考 Mock 服务

安装

pnpm i mockjs -S
pnpm i vite-plugin-mock -D

PS:

  • 在使用 pnpm 安装 mockjs 的时候会出现下图 peer 内声明的包需要手动进行安装的提示信息,目前 pnpm 是无法自动安装的,需要自行手动进行安装。具体可查看 automatically install peerDependencies
  • 创建的 mock 目录需和 public 目录同级,否则使用 fetch 可以访问(但结果不正确),使用 axios 访问 404

image-20220124105436045.png 手动安装 peer 内声明的依赖包

pnpm i rollup

vite.config.ts 添加 vite-plugin-mock 插件配置

import { viteMockServe } from 'vite-plugin-mock';

export default ({ command, mode }): UserConfigExport => {
  ...

  return {
    plugins: [
      ...
      viteMockServe({
        // default
        mockPath: 'mock',
        localEnabled: command === 'serve',
      }),
    ],
    ...
  }
}

mock 测试

PS:下面创建的 mock 测试目录需要和 public 目录处于同一级别

测试数据、接口方法

src 同级添加 mock 目录并创建 test.ts 文件,内容如下: image-20220124114224896.png

import { MockMethod } from 'vite-plugin-mock';
export default [
  {
    url: '/api/get',
    method: 'get',
    response: ({ query }) => {
      return {
        code: 0,
        data: {
          name: 'test get'
        }
      };
    }
  },
  {
    url: '/api/post',
    method: 'post',
    timeout: 2000,
    response: {
      code: 0,
      data: {
        name: 'test post'
      }
    }
  }
] as MockMethod[];

src 目录下添加 api 目录进行接口调用封装,并添加 api/test/index.ts 测试模块文件,内容如下: image-20220124114247188.png

// api/test/index.ts
import { defHttp } from '@/utils/http';

enum Api {
  // 测试接口
  Test = '/api/sug?code=utf-8&q=电脑',
  Get = '/api/get',
  Post = '/api/post'
}

/**
 * @description: 测试线上接口
 */
export const TestApi = () =>
  defHttp.request({
    url: Api.Test,
    method: 'GET'
  });

/**
 * @description: 测试 mock get 方法
 */
export const MockGet = () => defHttp.get({ url: Api.Get });

/**
 * @description: 测试 mock post 方法
 */
export const MockPost = () => defHttp.post({ url: Api.Post });

接口调用

src/views/index.vue 文件 script 调用接口内容如下,运行项目查看控制台信息或网络信息。

...
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';
import { TestApi, MockGet, MockPost } from '@/api/test';

(async () => {
  // 测试封装的 axios
  const res1 = await TestApi();
  console.log('TestApi:', res1);

  const res2 = await MockGet();
  console.log('MockGet:', res2);

  const res3 = await MockPost();
  console.log('MockPost:', res3);
})()

const appStore = useAppStore();
</script>

CSS 预编译器

安装项目需要的 css 预编译器 stylussass/scssless

pnpm i stylus -D
pnpm i sass -D
pnpm i less -D

详情示例代码可查看项目 vue3-vite-ts-pnpm 视图文件 views/index.vue 内容信息。

总结

文章知识回顾。我们可以了解到 vue 生态基本配置信息,主要包含以下内容知识:

  • vue-router 动态路由配置
  • piniavuex 状态管理配置
  • axios 请求另类封装
  • mock 模拟接口数据
  • css 预编译器集成

参考
从 0 搭建项目模板(vue3)
Vue-Vben-Admin 中后台框架