开箱即用的Vite-Vue3工程化模板

前言

由于临近毕业肝毕设和论文,停更有一段时间了,不过好在终于肝完了大部分内容,只剩下校对工作

毕设采用技术栈Vue3,Vite,TypeScript,Node,开发过程中产出了一些其它的东西,预计会出一系列的文章进行介绍

废话不多了步入正题...

体验模板

模板仓库地址


线上预览

两步到位

本地引入

# 方法一
npx degit atqq/vite-vue3-template#main my-project cd my-project
# 方法二
git clone https://github.com/ATQQ/vite-vue3-template.git cd vite-vue3-template

启动

# 安装依赖
yarn install
# 运行
yarn dev

模板介绍

已包含特性

内置了常见的工程化项目所用的内容,后文只对其中的一些特性做简单介绍

目录介绍

.
├── __tests__
├── dist # 构建结果
├── public # 公共静态资源
├── src # 源码目录
│ ├── apis
│ ├── assets
│ ├── components
│ ├── pages
│ ├── router
│ ├── store
│ ├── @types
│ ├── utils
│ ├── shims-vue.d.ts
│ ├── env.d.ts
│ ├── main.ts
│ └── App.vue
├── README.md
├── index.html # 应用入口
├── jest.config.ts
├── LICENSE
├── package.json
├── tsconfig.json
├── cloudbaserc.json # 腾讯云CloudBase相关配置文件
├── vite.config.ts # vite配置文件
└── yarn.lock

Vite

Vite有多牛牪犇,我就不赘述了

简单的vite.config.ts配置文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path' // https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
build: {
target: 'modules', // 默认值
// sourcemap: true,
},
server: {
port: 8080,
proxy: {
'/api/': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api/, ''),
},
'/api-prod/': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api-prod/, ''),
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
},
},
})

@vue/compiler-sfc

这个就是前段时间比较争议的一个提案,不过真香,进一步了解

Vuex

采用分业务模块的方案

目录结构

src/store/
├── index.ts
└── modules
└── module1.ts
module1.ts
import { Module } from 'vuex'

interface State {
count: number
} const store: Module<State, unknown> = {
namespaced: true,
state() {
return {
count: 0,
}
},
getters: {
isEven(state) {
return state.count % 2 === 0
},
},
// 只能同步
mutations: {
increase(state, num = 1) {
state.count += num
},
decrease(state) {
state.count -= 1
},
},
// 支持异步,可以考虑引入API
actions: {
increase(context, payload) {
context.commit('increase', payload)
setTimeout(() => {
context.commit('decrease')
}, 1000)
},
},
} export default store
index.ts
import { createStore } from 'vuex'
import module1 from './modules/module1' // Create a new store instance.
const store = createStore({
modules: {
m1: module1,
},
}) export default store

main.ts中引入

import store from './store'
app.use(store)

视图中调用

import { computed } from 'vue'
import { useStore } from 'vuex' const store = useStore() // state
const count = computed(() => store.state.m1.count)
// getters
const isEven = computed(() => store.getters['m1/isEven'])
// mutations
const add = () => store.commit('m1/increase')
// actions
const asyncAdd = () => store.dispatch('m1/increase')

Vue-Router

目录结构

src/router/
├── index.ts
├── Interceptor
│ └── index.ts
└── routes
└── index.ts

拦截器与页面路由相分离

Interceptor/index.ts
import { Router } from 'vue-router'

declare module 'vue-router' {
interface RouteMeta {
// 是可选的
isAdmin?: boolean
// 是否需要登录
requireLogin?: boolean
}
} function registerRouteGuard(router: Router) {
/**
* 全局前置守卫
*/
router.beforeEach((to, from) => {
if (to.meta.requireLogin) {
if (from.path === '/') {
return from
}
return false
}
return true
}) /**
* 全局解析守卫
*/
router.beforeResolve(async (to) => {
if (to.meta.isAdmin) {
try {
console.log(to)
} catch (error) {
// if (error instanceof NotAllowedError) {
// // ... 处理错误,然后取消导航
// return false
// } else {
// // 意料之外的错误,取消导航并把错误传给全局处理器
// throw error
// }
console.error(error)
}
}
}) /**
* 全局后置守卫
*/
router.afterEach((to, from, failure) => {
// 改标题,监控上报一些基础信息
// sendToAnalytics(to.fullPath)
if (failure) {
console.error(failure)
}
})
} export default registerRouteGuard
routes/index.ts
import { RouteRecordRaw } from 'vue-router'
import Home from '../../pages/home/index.vue'
import About from '../../pages/about/index.vue'
import Dynamic from '../../pages/dynamic/index.vue' const NotFind = () => import('../../pages/404/index.vue')
const Index = () => import('../../pages/index/index.vue')
const Axios = () => import('../../pages/axios/index.vue')
const Element = () => import('../../pages/element/index.vue')
const routes: RouteRecordRaw[] = [
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFind },
{
path: '/',
name: 'index',
component: Index,
children: [
{ path: 'home', component: Home, name: 'home' },
{ path: 'about', component: About, name: 'about' },
{ path: 'axios', component: Axios, name: 'axios' },
{ path: 'element', component: Element, name: 'element' },
{
path: 'dynamic/:id',
component: Dynamic,
meta: {
requireLogin: false,
isAdmin: true,
},
name: 'dynamic',
},
],
},
] export default routes
router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import registerRouteGuard from './Interceptor'
import routes from './routes' const router = createRouter({
history: createWebHistory(import.meta.env.VITE_ROUTER_BASE as string),
routes,
}) // 注册路由守卫
registerRouteGuard(router) export default router

main.ts中引入

import router from './router'
app.use(router)

Axios

对axios的简单包装

ajax.ts
import axios from 'axios'

const instance = axios.create({
baseURL: import.meta.env.VITE_APP_AXIOS_BASE_URL,
}) /**
* 请求拦截
*/
instance.interceptors.request.use((config) => {
const { method, params } = config
// 附带鉴权的token
const headers: any = {
token: localStorage.getItem('token'),
}
// 不缓存get请求
if (method === 'get') {
headers['Cache-Control'] = 'no-cache'
}
// delete请求参数放入body中
if (method === 'delete') {
headers['Content-type'] = 'application/json;'
Object.assign(config, {
data: params,
params: {},
})
} return ({
...config,
headers,
})
}) /**
* 响应拦截
*/
instance.interceptors.response.use((v) => {
if (v.data?.code === 401) {
localStorage.removeItem('token')
// alert('即将跳转登录页。。。', '登录过期')
// setTimeout(redirectHome, 1500)
return v.data
}
if (v.status === 200) {
return v.data
}
// alert(v.statusText, '网络错误')
return Promise.reject(v)
})
export default instance

api目录结构

src/apis/
├── ajax.ts
├── index.ts
└── modules
└── public.ts

分业务模块编写接口调用方法,通过apis/index.ts对外统一导出

export { default as publicApi } from './modules/public'

注入全局的Axios实例,Vue2中通常是往原型(prototype)上挂载相关方法,在Vue3中由于使用CreateApp创建实例,所以推荐使用provide/inject 来传递一些全局的实例或者方法

main.ts

import Axios from './apis/ajax'

const app = createApp(App)

app.provide('$http', Axios)

视图中使用

import { inject } from 'vue'

const $http = inject<AxiosInstance>('$http')

polyfill.io

部分浏览器可能对ES的新语法支持程度不一致,存在一定的兼容问题,此时就需要使用polyfill(垫片)

polyfill.io是一个垫片服务,直接通过cdn按需引入垫片,不影响包体积

工作原理是通过解析客户端的UA信息,然后根据查询参数,判断是否需要垫片,不需要则不下发

简单使用

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script
src="https://polyfill.alicdn.com/polyfill.min.js?features=es2019%2Ces2018%2Ces2017%2Ces5%2Ces6%2Ces7%2Cdefault"></script>
</head> <body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body> </html>

查询参数在线生成->url-builder

由于官方服务是部署在非大陆,所以延迟较高,由于polyfill-service是开源的,所以可以自己进行搭建

国内大厂也有一些镜像:

element UI Plus

Vue3版本的Element UI 组件库,虽然有些坑,但勉强能用 O(∩_∩)O哈哈~

按需引入在使用过程中发现Dev和Prod环境下的样式表现有差异,固采用全量引入的方式

utils/elementUI.ts

import { App } from '@vue/runtime-core'

// 全量引入
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/lib/locale/lang/zh-cn' export default function mountElementUI(app: App<Element>) {
app.use(ElementPlus, { locale })
}

main.ts

import mountElementUI from './utils/elementUI'

mountElementUI(app)

最新文章

  1. MAC usb启动盘制作
  2. 分享一例测试环境下nginx+tomcat的视频业务部署记录
  3. 【原】整理的react相关的一些学习地址,包括 react-router、redux、webpack、flux
  4. Oracle Redo Log
  5. 每天学一点JAVA
  6. Valid Parentheses
  7. 关于oc中出现的typedef的用法/定义函数指针
  8. Nginx服务器架构简析
  9. zoj 2290 Game 博弈论
  10. hadoop的核心思想
  11. 转摘 Eclipse智能提示及快捷键
  12. linux du 与 df 命令
  13. csu 1303 Decimal (数论题)
  14. SQL Server 堆表与栈表的对比(大表)
  15. JavaScript &amp; HTML5 Canvas 概览 更新时间2014-0411-1805
  16. geoserver发布地图服务WMS
  17. springdata 动态查询之排序
  18. 引擎设计跟踪: 为什么Blade可以用Clang编译
  19. 数据快速批量添加到Elasticsearch
  20. window安装ab压力测试并使用

热门文章

  1. Mysql之索引选择及优化
  2. Python3实现短信轰炸机
  3. Swagger接口如何生成Html离线文档
  4. IT培训有哪些坑(一)?
  5. PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642
  6. 计划任务统一集中管理系统cronsun(替代crontab)
  7. 浅谈Android中的事件分发机制
  8. Node.js/Vue.js使用jsSHA库进行SHA1/2/3加密
  9. mysql 遇到的问题
  10. 记一次go中map并发引起的事故