软件开发定制定制vue3+ts+vite+element plus+axios+pinia框架搭建

目录

一、技术栈

  • vue3:软件开发定制定制组件封装和拆分比Vue2软件开发定制定制更加细化和合理。
  • typescript:比js软件开发定制定制更加严格的类型检查,软件开发定制定制能够在编译期就能发现错误。
  • vite:软件开发定制定制下一代前端开发和构建工具。
  • element plus:ui组件库,软件开发定制定制比较热门的vue软件开发定制定制组件库之一。
  • axios:基于promise软件开发定制定制的网络请求库。
  • vue-router:路由控制。
  • pinia:软件开发定制定制状态管理类库,比vuex更小,对ts的支持更友好。
  • volar插件:代码补全和检测工具,可以尝试替换vetur,如果不替换的话,用ts的语法糖的时候会出现找不到默认的default的错误。
  • pnpm:比npm和yarn更强大的包管理工具,包安装速度极快,磁盘空间利用效率高。

二、搭建过程

1、创建项目

# npm 6.xnpm init vite@latest my-vue-app --template vue-ts# npm 7+, 需要额外的双横线npm init vite@latest my-vue-app -- --template vue-ts# yarnyarn create vite my-vue-app --template vue-ts# pnpmpnpm create vite my-vue-app -- --template vue-ts
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
# 全局安装pnpmnpm i pnpm -g
  • 1
  • 2

2、引入element-plus

# -D安装到开发环境 -S安装到生产环境pnpm i element-plus -D
  • 1
  • 2

全局引入:main.ts

import { createApp } from 'vue'import App from './App.vue'// 引入element-plusimport element from 'element-plus'import 'element-plus/dist/index.css'  // 不引入会导致ui样式不正常createApp(App).use(element).mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3、引入vue-router

pnpm i vue-router@latest -D
  • 1

配置别名:.config.ts

# 使用require需要安装@types/nodenpm i @types/node -D
  • 1
  • 2
import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import * as path from 'path'import { settings } from './src/config/index'export default defineConfig({  plugins: [vue()],  base: settings.base,               // 生产环境路径  resolve: {    alias: {						 // 配置别名      '@': path.resolve(__dirname, 'src'),      'assets': path.resolve(__dirname, 'src/assets'),      'components': path.resolve(__dirname, 'src/components'),      'config': path.resolve(__dirname, 'src/config'),      'router': path.resolve(__dirname, 'src/router'),      'tools': path.resolve(__dirname, 'src/tools'),      'views': path.resolve(__dirname, 'src/views'),      'plugins': path.resolve(__dirname, 'src/plugins'),      'store': path.resolve(__dirname, 'src/store'),    }  },  build: {    target: 'modules',    outDir: 'dist',           // 指定输出路径    assetsDir: 'static',      // 指定生成静态资源的存放路径    minify: 'terser',         // 混淆器,terser构建后文件体积更小    sourcemap: false,         // 输出.map文件    terserOptions: {      compress: {        drop_console: true,   // 生产环境移除console        drop_debugger: true   // 生产环境移除debugger      }    },  },  server: {    // 是否主动唤醒浏览器    open: true,    // 占用端口           port: settings.port,    // 是否使用https请求        https: settings.https,    // 扩展访问端口    // host: settings.host,          proxy: settings.proxyFlag ? {      '/api': {        target: 'http://127.0.0.1:8080',  // 后台接口        changeOrigin: true,               // 是否允许跨域        // secure: false,                    // 如果是https接口,需要配置这个参数        rewrite: (path: any) => path.replace(/^\/api/, ''),      },    } : {}  }})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

添加主路由文件:/src/router/index.ts

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'import { Home } from '../config/constant';const routes: Array<RouteRecordRaw> = [  {    path: '',    name: 'index',    redirect: '/home',  },  {    path: '/home',    name: 'home',    component: Home,    meta: {      title: '首页'    }  },]const router = createRouter({  history: createWebHistory(),  routes})export default router;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

全局文件:/src/config/constant.ts

// 没有的vue文件自行创建引入即可export const Home = () => import('@/layout/index.vue')export const Login = () => import('@/views/login/Login.vue')
  • 1
  • 2
  • 3

全局引入:main.ts

import { createApp } from 'vue'import App from './App.vue'import element from 'element-plus'import 'element-plus/dist/index.css'// 添加routerimport router from './router/index'// 全局引用createApp(App).use(element).use(router).mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在App.vue添加路由渲染

<script setup lang="ts"></script><template>  <!-- router组件渲染的地方 -->  <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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4、引入axios

pnpm i axios -D
  • 1

请求函数封装:/src/plugins/request.ts

import axios from 'axios'import cookieService from 'tools/cookie'import { ElMessage } from 'element-plus'import { settings } from 'config/index'axios.defaults.withCredentials = true// 请求超时时间60saxios.defaults.timeout = 1 * 60 * 1000// get请求头axios.defaults.headers.get['Content-Type'] = 'application/json'// post请求头axios.defaults.headers.post['Content-Type'] = 'application/json'// 根请求路径axios.defaults.baseURL = settings.baseUrl// 请求拦截器axios.interceptors.request.use(  config => {    // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了    // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断    // 增加接口时间戳    config.params = { _t: 1000, ...config.params }    config.headers = { 'x-csrf-token': "xxx" }    return config  },  error => {    return Promise.reject(error)  })// 响应拦截器let timer: any = falseaxios.interceptors.response.use(  response => {    cookieService.set('xxx', response.headers['csrftoken'])    if (response.status === 200) {      return Promise.resolve(response)    } else {      return Promise.reject(response)    }  },  error => {    if (error.response && error.response.status) {      const path = window.location.href      switch (error.response.status) {        case 302:          window.location.href =            '' + path          break        case 401:          window.location.href =            '' + path          break        case 403:          // 清除token          if (!timer) {            timer = setTimeout(() => {              ElMessage({                message: '登录信息已过期,请重新登录!',                type: 'error',              })              setTimeout(() => {                window.location.href = 'xxx' + path                cookieService.set('loginCookie', false, 1)              }, 2000)            }, 0)          }          break        // 404请求不存在        case 404:          ElMessage({            message: '请求不存在',            type: 'error',          })          break        case 500:          ElMessage({            message: error.response.statusText,            type: 'error',          })          break        default:          ElMessage({            message: error.response.data.message,            type: 'error',          })      }      return Promise.reject(error.response)    }  })/** * get方法,对应get请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */export function get(url: string, params: any) {  return new Promise((resolve, reject) => {    axios      .get(url, { params: params })      .then(res => {        resolve(res.data)      })      .catch(err => {        reject(err.data)      })  })}/** * post方法,对应post请求 * @param {String} url [请求的url地址] * @param {Object} params [请求时携带的参数] */export function post(url: string, params: any) {  return new Promise((resolve, reject) => {    axios      .post(url, params)      .then(res => {        resolve(res.data)      })      .catch(err => {        reject(err.data)      })  })}export default axios
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

添加全局配置文件:/src/config/index.ts

const BASE_URL = process.env.NODE_ENV === 'development' ? '/api' : 'http://localhost:8080'const settings = {  // 请求根路径  baseUrl: BASE_URL,  // 是否开启代理,本地需要开,线上环境关闭            proxyFlag: true,  // 端口              port: 8081,          // 是否开启https           https: false,            // 扩展端口       // host: 'localhost',           // 公共路径  base: './'                  }export { settings }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

添加api请求文件:/src/config/api.ts

import { get, post } from 'plugins/request'// 用户请求const user = () => {  const getUser = (url: string, params: any) => {    return get(url, params)  }  return {    getUser  }}// 权限请求const permission =  () => {  const login = (url: string, params: any) => {    return get(url, params)  }  return {    login  }}const userService = user()const permissionService = permission()export { userService, permissionService }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

添加url路径文件(根据后台接口定):/src/config/url.ts

// 用户urlconst userBaseUrl = '/user'export const userUrl = {  add: userBaseUrl + '/add',  get: userBaseUrl + '',  edit: userBaseUrl + '/edit',  delete: userBaseUrl + '/delete' }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用案例:/src/views/Home.vue

<template>  <div>    {{ state.userName }}  </div></template><script lang='ts' setup>import { reactive } from 'vue';import { userService } from 'config/api';import { userUrl } from 'config/url';const state = reactive({  userName: ''})getUser()function getUser() {  userService.getUser(userUrl.get, '').then((resp: any) => {    console.log(resp)    state.userName = resp.data;  })}</script><style scoped></style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

5、引入

pnpm i pinia -D
  • 1

全局引入:main.ts

import { createApp } from 'vue'import App from './App.vue'import element from 'element-plus'import 'element-plus/dist/index.css'import router from '@/router'import { createPinia } from 'pinia'const pinia = createPinia()createApp(App).use(element).use(router).use(pinia).mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

状态管理案例:/src/store/index.ts

import { defineStore } from 'pinia'/*  * 传入2个参数,定义仓库并导出 * 第一个参数唯一不可重复,字符串类型,作为仓库ID以区分仓库 * 第二个参数,以对象形式配置仓库的state、getters、actions * 配置 state getters actions */export const mainStore = defineStore('main', {  /*   * 类似于组件的data数据,用来存储全局状态的   * 1、必须是箭头函数   */  state: () => {    return {      msg: 'hello world!',      counter: 0    }  },  /*   * 类似于组件的计算属性computed的get方法,有缓存的功能   * 不同的是,这里的getters是一个函数,不是一个对象   */  getters: {    count10(state) {      console.log('count10被调用了')      return state.counter + 10    }  },  /*   * 类似于组件的methods的方法,用来操作state的   * 封装处理数据的函数(业务逻辑):初始化数据、修改数据   */  actions: {    updateCounter(value: number) {      console.log('updateCounter被调用了')      this.counter = value * 1000    }   }})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

使用案例:/src/views/Home.vue

<template>  <div>    {{ state.userName }}  </div>  <el-button @click="handleClick">增加</el-button>  <div>    {{ counter }}  </div></template><script lang='ts' setup>import { reactive } from 'vue';import { userService } from 'config/api';import { userUrl } from 'config/url';// 定义一个状态对象import { mainStore } from 'store/index';import { storeToRefs } from 'pinia';// 创建一个该组件的状态对象const state = reactive({  userName: ''})// 实例化一个状态对象const store = mainStore();// 解构并使数据具有响应式const { counter } = storeToRefs(store);getUser()function getUser() {  userService.getUser(userUrl.get, '').then((resp: any) => {    console.log(resp)    state.userName = resp.data;  })}function handleClick() {  counter.value++;  store.updateCounter(counter.value)}</script><style scoped></style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

引入持久化插件:pinia-plugin-persist

pnpm i pinia-plugin-persist -D
  • 1

在main.ts全局引入

import { createApp } from 'vue'import App from './App.vue'import element from 'element-plus'import 'element-plus/dist/index.css'import router from '@/router'import { createPinia } from 'pinia'import piniaPluginPersist from 'pinia-plugin-persist'const pinia = createPinia()pinia.use(piniaPluginPersist)createApp(App).use(element).use(router).use(pinia).mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

编写persist配置文件piniaPersist.ts

export const piniaPluginPersist = (key: any) => {   return {    enabled: true, // 开启持久化存储    strategies: [        {          // 修改存储中使用的键名称,默认为当前 Store的id          key: key,          // 修改为 sessionStorage,默认为 localStorage          storage: localStorage,          // []意味着没有状态被持久化(默认为undefined,持久化整个状态)          // paths: [],        }    ]  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用案例

import { defineStore } from 'pinia'import { piniaPluginPersist } from 'plugins/piniaPersist'/*  * 传入2个参数,定义仓库并导出 * 第一个参数唯一不可重复,字符串类型,作为仓库ID以区分仓库 * 第二个参数,以对象形式配置仓库的state、getters、actions * 配置 state getters actions */export const mainStore = defineStore('mainStore', {  /*   * 类似于组件的data,用来存储全局状态的   * 1、必须是箭头函数   */  state: () => {    return {      msg: 'hello world!',      counter: 0    }  },  /*   * 类似于组件的计算属性computed,有缓存的功能   * 不同的是,这里的getters是一个函数,不是一个对象   */  getters: {    count10(state) {      console.log('count10被调用了')      return state.counter + 10    }  },  /*   * 类似于组件的methods,用来操作state的   * 封装处理数据的函数(业务逻辑):同步异步请求,更新数据   */  actions: {    updateCounter(value: number) {      console.log('updateCounter被调用了')      this.counter = value * 1000    }   },  /*   * 持久化,可选用localStorage或者sessionStorage   *   */  persist: piniaPluginPersist('mainStore')})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

三、运行与打包

运行命令

pnpm run dev
  • 1

打包命令(环境自选)

pnpm run build:dev
  • 1

配置不同的打包环境:package.json

{  "name": "vite-study",  "private": true,  "version": "0.0.0",  "scripts": {    "dev": "vite",    "build": "vue-tsc --noEmit && vite build",    "build:dev": "vue-tsc --noEmit && vite build",    // 开发环境    "build:prod": "vue-tsc --noEmit && vite build",   // 生产环境    "preview": "vite preview"  },  "dependencies": {    "vue": "^3.2.37"  },  "devDependencies": {    "@types/node": "^18.0.0",    "@vitejs/plugin-vue": "^2.3.3",    "axios": "^0.27.2",    "element-plus": "^2.2.6",    "pinia": "^2.0.14",    "typescript": "^4.7.4",    "vite": "^2.9.12",    "vue-router": "^4.0.16",    "vue-tsc": "^0.34.17"  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

由于使用到了vite作为打包工具,在实际使用过程中遇到了问题。webpack打包可以直接指定打包成zip或者其他格式的压缩包,但是在vite中是没有这个配置的,那么遇到流水线部署的时候我们应该怎么办呢?

方法:利用node插件compressing

引入compressing

pnpm i compressing -D
  • 1

根目录创建:zip.js

const path = require("path");const { resolve } = require("path");const fs = require("fs");const compressing = require("compressing");const zipPath = resolve("zip");const zipName = (() => `zip/dist.zip`)();// 判断是否存在当前zip路径,没有就新增if (!fs.existsSync(zipPath)) {  fs.mkdirSync(zipPath);}// 清空zip目录const zipDirs = fs.readdirSync("./zip");if (zipDirs && zipDirs.length > 0) {  for (let index = 0; index < zipDirs.length; index++) {    const dir = zipDirs[index];    const dirPath = resolve(__dirname, "zip/" + dir)    console.log("del ===", dirPath);    fs.unlinkSync(dirPath)  }}// 文件压缩compressing.zip  .compressDir(resolve("dist/"), resolve(zipName))  .then(() => {    console.log(`Tip: 文件压缩成功,已压缩至【${resolve(zipName)}`);  })  .catch(err => {    console.log("Tip: 压缩报错");    console.error(err);  });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

package.json中配置script命令

"build:dev": "vue-tsc --noEmit && vite build && node ./zip.js","build:prod": "vue-tsc --noEmit && vite build && node ./zip.js",
  • 1
  • 2

输入命令打包

pnpm run build:dev
  • 1

命令执行完后在zip文件夹会生成dist.zip的压缩包

四、参考

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发