Vite2.x + Vue.js3.x + TS起步
创建项目
Requires Node.js ^14.17.0, 16.0.0 or later. 因为部分依赖在低版本node中不兼容
1 | yarn create vite vite-project --template vue-ts |
修改语法检查与格式化配置
- 安装依赖
Requires
vue-eslint-parser
9.0.0 or later.
1 | yarn add eslint eslint-plugin-vue vue-eslint-parser @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-airbnb-base eslint-plugin-import vite-plugin-eslint -D |
修改
.eslintignore
文件1
2
3
4/dist/
/*.js
/*.zip
/*.rar修改
.eslintrc.js
文件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
72module.exports = {
root: true,
globals: {
defineEmits: 'readonly',
defineProps: 'readonly',
},
extends: ['plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-recommended', 'airbnb-base'],
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
},
rules: {
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁用 debugger
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', // 禁用 console
'no-bitwise': 'off', // 禁用按位运算符
'no-tabs': 'off', // 禁用 tab
'array-element-newline': ['error', 'consistent'], // 强制数组元素间出现换行
indent: ['error', 2, { MemberExpression: 1, SwitchCase: 1, ignoredNodes: ['TemplateLiteral'] }], // 强制使用一致的缩进
quotes: ['error', 'single'], // 强制使用一致的反勾号、双引号或单引号
'comma-dangle': ['error', 'always-multiline'], // 要求或禁止末尾逗号
'object-curly-spacing': ['error', 'always'], // 强制在大括号中使用一致的空格
'arrow-body-style': 'off',
'max-len': ['error', 120], // 强制一行的最大长度
'no-new': 'off', // 禁止使用 new 以避免产生副作用
'linebreak-style': 'off', // 强制使用一致的换行风格
'import/extensions': 'off', // 确保在导入路径中统一使用文件扩展名
'eol-last': 'off', // 要求或禁止文件末尾存在空行
'no-shadow': 'off', // 禁止变量声明与外层作用域的变量同名
'no-unused-vars': 'warn', // 禁止出现未使用过的变量
'import/no-cycle': 'off', // 禁止一个模块导入一个有依赖路径的模块回到自己身上
'arrow-parens': 'off', // 要求箭头函数的参数使用圆括号
semi: ['error', 'never'], // 要求或禁止使用分号代替 ASI
eqeqeq: 2, // 要求使用 === 和 !==
'no-param-reassign': 'off', // 禁止对 function 的参数进行重新赋值
'import/prefer-default-export': 'off', // 如果模块只输入一个名字,则倾向于默认输出
'no-use-before-define': 2, // 禁止在变量定义之前使用它们,则倾向于默认输出
'no-continue': 'off', // 禁用 continue 语句
'prefer-destructuring': 'off', // 优先使用数组和对象解构
'no-plusplus': 'off', // 禁用一元操作符 ++ 和 --
'prefer-const': 'warn', // 要求使用 const 声明那些声明后不再被修改的变量
'global-require': 2, // 要求 require() 出现在顶层模块作用域中
'no-prototype-builtins': 'off', // 禁止直接调用 Object.prototypes 的内置属性
'consistent-return': 'off', // 要求 return 语句要么总是指定返回的值,要么不指定
'one-var-declaration-per-line': 'off', // 要求或禁止在变量声明周围换行
'one-var': 'off', // 强制函数中的变量要么一起声明要么分开声明
'import/named': 'off', // 确保命名导入与远程文件中的命名导出相对应
'object-curly-newline': 'off', // 强制大括号内换行符的一致性
'default-case': 'off', // 要求 switch 语句中有 default 分支
'no-trailing-spaces': 2, // 禁用行尾空格
'func-names': 'off', // 要求或禁止使用命名的 function 表达式
radix: 'off', // 强制在 parseInt() 使用基数参数
'no-unused-expressions': 'off', // 禁止出现未使用过的表达式
'no-underscore-dangle': 'off', // 禁止标识符中有悬空下划线
'no-nested-ternary': 'off', // 禁用嵌套的三元表达式
'no-restricted-syntax': 'off', // 禁用特定的语法
'no-await-in-loop': 'off', // 禁止在循环中出现 await
'import/no-extraneous-dependencies': 'off', // 禁止使用外部包
'import/no-unresolved': 'off', // 确保导入指向一个可以解析的文件/模块
'template-curly-spacing': ['error', 'never'], // 要求或禁止模板字符串中的嵌入表达式周围空格的使用
'@typescript-eslint/no-var-requires': 'off', // 除import语句外,禁止使用require语句
'@typescript-eslint/no-empty-function': 'off', // 不允许空函数
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'guard-for-in': 'off', // 要求 for-in 循环中有一个 if 语句
'class-methods-use-this': 'off', // 强制类方法使用 this
'vue/html-indent': ['error', 2], // 在<template>中强制一致缩进
'vue/html-self-closing': 'off', // 执行自闭合的风格
'vue/multi-word-component-names': 'off',
'vue/max-attributes-per-line': 'off',
'vue/singleline-html-element-content-newline': 'off', // 要求单行元素的内容前后有一个换行符
'import/order': 'off',
},
}修改
.prettierrc.js
文件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// prettier.config.js or .prettierrc.js
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: false,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 换行符使用 lf
endOfLine: 'lf'
}修改
tsconfig.json
文件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{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"skipLibCheck": true,
"types": [
"node"
],
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
],
"@api/*": [
"src/api/*"
]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules"],
"references": [{ "path": "./tsconfig.node.json" }]
}修改
tsconfig.node.json
文件1
2
3
4
5
6
7
8{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}修改
vite.config.ts
文件1
2
3
4
5
6
7
8
9
10
11
12import { defineConfig } from 'vite'
import eslintPlugin from 'vite-plugin-eslint'
export default defineConfig(() => {
return {
plugins: [
eslintPlugin({
include: ['src/**/*.ts', 'src/**/*.vue', 'src/*.ts', 'src/*.vue'],
}),
]
}
})
修改git钩子
先安装依赖
1
2yarn add -D lint-staged
npx husky-init && yarn修改
package.json
文件1
2
3
4
5
6
7
8
9
10
11{
"scripts": {
"prepare": "husky install",
"lint": "lint-staged"
},
"lint-staged": {
"src/**/*.{js,ts,vue}": [
"eslint --fix"
]
}
}修改
.husky/pre-commit
1
2
3
4
5
6!/usr/bin/env sh
在sourcetree报错,找不到npm,修改path可解决这个问题
export PATH="/usr/local/opt/node@14/bin/:$PATH"
. "$(dirname -- "$0")/_/husky.sh"
npm run lint
使用less
先安装依赖
1
yarn add less less-loader @types/node -D
创建全局变量文件
src/assets/styles/variables.less
1
@baseFontSize: 14px;
修改
vite.config.ts
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { defineConfig } from 'vite'
const path = require('path')
export default defineConfig(() => {
return {
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
charset: false,
additionalData: `@import (reference) "${path.resolve(__dirname, 'src/assets/styles/variables.less')}";`
}
}
}
}
})创建全局样式文件
src/assets/styles/global.less
1
2
3body {
margin: 0;
}在
main.ts
加载全局样式文件1
2
3
4
5
6import { createApp } from 'vue'
import App from '@/App.vue'
import './assets/styles/global.less'
const app = createApp(App)
app.mount('#app')在组件内使用less
1
2
3
4
5<style lang="less" scoped>
.my-button {
font-size: @baseFontSize;
}
</style>
修改环境变量配置
创建
.env.development
文件1
2
3VITE_ENV = DEV
VITE_APP_BASE_URL = 'http://localhost/api'
VITE_APP_TITLE = 'index'创建
.env.production
文件1
2
3VITE_ENV = PROD
VITE_APP_BASE_URL = 'https://www.pengwf.com'
VITE_APP_TITLE = '首页'修改
package.json
文件1
2
3
4
5
6
7{
"scripts": {
"dev": "vite --mode development",
"build": "rimraf ./dist && vue-tsc --noEmit && vite build --mode production",
"preview": "vite preview"
}
}修改
vite.config.ts
文件1
2
3
4
5
6
7
8
9
10
11
12
13import { defineConfig } from 'vite'
const path = require('path')
export default defineConfig(() => {
return {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@api': path.resolve(__dirname, 'src/api')
}
}
}
})
批量加载全局组件
1 | // src/components/index.ts |
1 | <!-- src/components/global/MyButton.vue --> |
1 | // src/main.ts |
使用 element-plus UI 库
先安装依赖
1
2yarn add element-plus
yarn add unplugin-vue-components unplugin-auto-import -D修改
vite.config.ts
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Component from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig(() => {
return {
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Component({
resolvers: [ElementPlusResolver()]
})
]
}
})在使用less而非scss的情况下需要全局加载样式文件,修改
main.ts
1
2
3
4
5
6
7import { createApp } from 'vue'
import App from '@/App.vue'
import './assets/styles/global.less'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.mount('#app')
使用pinia
安装依赖
1
yarn add pinia
创建
src/store/main.ts
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
44import { defineStore } from 'pinia'
export interface UserinfoType {
id: string
name: string
date: string
}
export const mainStore = defineStore('main', {
state: () => {
return {
token: '',
network: true,
userinfo: {} as UserinfoType,
}
},
getters: {
getToken(state) {
return state.token
},
getNetwork(state) {
return state.network
},
getUserinfo(state) {
return state.userinfo
},
},
actions: {
setToken(token: string) {
this.token = token
localStorage.setItem('token', token)
},
clearToken() {
this.token = ''
localStorage.removeItem('token')
},
setNetwork(flag: boolean) {
this.network = flag
},
setUserinfo(userinfo: UserinfoType) {
this.userinfo = userinfo
},
},
})修改
main.ts
1
2
3
4
5
6
7
8
9import { createApp } from 'vue'
import App from '@/App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')在组件中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<script setup lang="ts">
import { mainStore } from '@/store/main'
const store = mainStore()
const logout = () => {
store.clearToken()
}
</script>
<template>
<h1>首页</h1>
<p><img alt="Vue logo" src="@/assets/images/logo.png" /></p>
<p>
<button @click="logout">退出登录</button>
</p>
</template>
使用vue-router
安装依赖
1
yarn add vue-router@4
定义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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: 'dashboard',
},
]
const modules = import.meta.globEager('./*.ts')
for (const path in modules) {
routes.push(...modules[path].default)
}
const router = createRouter({
history: createWebHistory(),
routes,
})
router.beforeEach((to, from) => {
const { path: toPath } = to
const { path: fromPath } = from
if (toPath === fromPath) {
return false
}
})
export default router
// src/router/dashboard.ts
export default [
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/dashboard/index.vue')
}
]
// src/router/login.ts
export default [
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue')
}
]修改
main.ts
1
2
3
4
5
6
7import { createApp } from 'vue'
import App from '@/App.vue'
import router from '@/router'
const app = createApp(App)
app.use(router)
app.mount('#app')在组件中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<script setup lang="ts">
import { useRouter } from 'vue-router'
import { mainStore } from '@/store/main'
const router = useRouter()
const store = mainStore()
const logout = () => {
store.clearToken()
router.replace('/login')
}
</script>
<template>
<h1>首页</h1>
<p>
<button @click="logout">退出登录</button>
</p>
</template>
使用vue-i18n
安装依赖
1
yarn add vue-i18n
定义语言包
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// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
const language: { [key: string]: any } = {}
const langs = import.meta.globEager('./*.ts')
for (const [key, value] of Object.entries(langs)) {
const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.'))
language[name] = value.default
}
const i18n = createI18n({
locale: 'zh_CN',
messages: language,
})
export default i18n
// src/i18n/zh_CN.ts
export default {
hello: '你好'
}
// src/i18n/en_US.ts
export default {
hello: 'Hello'
}修改
main.ts
1
2
3
4
5
6
7import { createApp } from 'vue'
import App from '@/App.vue'
import i18n from '@/i18n'
const app = createApp(App)
app.use(i18n)
app.mount('#app')在组件中使用
1
2
3
4<template>
<h1>首页</h1>
<p>{{ $t('hello') }}</p>
</template>
使用vite-plugin-imagemin
压缩图片
- 修改
package.json
文件国内不搭梯子的话,基本上都会遇到安装失败的问题,需要切换到淘宝的源,并且在
package.json
文件增加以下配置
1 | { |
安装依赖
1
yarn add vite-plugin-imagemin -D
修改
vite.config.ts
文件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
38import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig(() => {
return {
plugins: [
vue(),
viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false,
},
optipng: {
optimizationLevel: 7,
},
mozjpeg: {
quality: 20,
},
pngquant: {
quality: [0.8, 0.9],
speed: 4,
},
svgo: {
plugins: [
{
name: 'removeViewBox',
},
{
name: 'removeEmptyAttrs',
active: false,
},
],
}
})
]
}
})
使用vite-plugin-html
自定义入口文件
- 安装依赖
新版本在我的mac上测试有bug,所以指定了一个比较低的版本
1 | yarn add vite-plugin-html@3.1.0 -D |
修改
vite.config.ts
文件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
26import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createHtmlPlugin } from 'vite-plugin-html'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd())
const isProd: boolean = env.VITE_ENV === 'PROD'
return {
plugins: [
vue(),
createHtmlPlugin({
minify: isProd,
template: 'pages/index.html',
inject: {
data: {
title: env.VITE_APP_TITLE,
injectScript: isProd
? ''
: `<script src="https://cdn.jsdelivr.net/npm/vconsole@2.5.2/dist/vconsole.min.js"></script>`
}
}
}),
]
}
})修改入口文件
pages/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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><%= title %></title>
<%- injectScript %>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
封装axios
安装依赖
1
2yarn add axios js-cookie nprogress qs ts-md5
yarn add @types/js-cookie @types/nprogress @types/qs -D创建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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159// src/utils/ajax.ts
import axios from 'axios'
import qs from 'qs'
import Cookies from 'js-cookie'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { Md5 } from 'ts-md5/dist/md5'
import { ElNotification } from 'element-plus'
import { mainStore } from '@/store/main'
import router from '@/router'
const ajax: any = axios.create({
timeout: 60000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
Accept: 'application/json',
},
})
function buildSign(params: any, timestamp: number, token: string): string {
if (!token) {
return ''
}
const keys: string[] = Object.keys(params).sort()
let paramsStr = ''
keys.forEach((key: string, i: number) => {
const value: string = typeof params[key] === 'object' ? JSON.stringify(params[key]) : <string>params[key]
paramsStr = `${paramsStr}${key}=${value}`
if (i < keys.length - 1) {
paramsStr += '&'
}
})
return Md5.hashStr(`${timestamp}${token}${paramsStr}`)
}
ajax.interceptors.request.use(
(config: any) => {
if (!config.isMock || import.meta.env.VITE_ENV === 'PROD') {
config.baseURL = import.meta.env.VITE_APP_BASE_URL
}
if (config.isFile) {
config.headers['Content-Type'] = 'multipart/form-data'
}
const store = mainStore()
if (store.getToken) {
config.headers.Authorization = store.getToken
}
if (!config.data) {
config.data = {}
}
if (config.params) {
config.data = {
...config.data,
...config.params,
}
}
if (!config.isHideLoading) {
NProgress.start()
}
config.data.locale = Cookies.get('language') || 'zh_CN'
config.data.clientId = 'pc'
const timestamp = Date.parse(new Date().toString()) / 1000
const sign = buildSign(config.data, timestamp, store.getToken)
config.headers['x-timestamp'] = timestamp
if (sign && !config.isHideSign) {
config.headers['x-sign'] = sign
}
if (config.data) {
if (config.method === 'get') {
config.url = `${config.url}?${qs.stringify(config.data)}`
}
if (config.headers['Content-Type'] !== 'multipart/form-data' && config.method !== 'get') {
config.data = qs.stringify(config.data, {
arrayFormat: 'indices',
allowDots: true,
})
}
}
return config
},
(error: any) => Promise.reject(error),
)
ajax.interceptors.response.use(
(response: any) => {
NProgress.done()
const store = mainStore()
store.setNetwork(true)
if (response.status === 200) {
if (response.data.errno === 0) {
return Promise.resolve(response.data)
}
if (!response.config.isHideError) {
ElNotification({
title: '提示',
message: response.data.message,
type: 'error',
})
}
return Promise.reject(response)
}
return Promise.reject(response)
},
(error: any) => {
NProgress.done()
const store = mainStore()
store.setNetwork(window.navigator.onLine)
const { response } = error
if (response) {
switch (response.status) {
case 401:
case 403:
ElNotification({
title: '提示',
message: '登录过期,请重新登录',
duration: 0,
type: 'error',
})
setTimeout(() => {
router.replace('/login')
}, 1000)
break
case 404:
ElNotification({
title: '提示',
message: '请求的资源不存在',
type: 'error',
})
break
case 500:
case 501:
case 502:
case 503:
case 504:
case 505:
ElNotification({
title: '提示',
message: '服务器端发生错误,请稍后再试',
type: 'error',
})
break
default:
console.error(response.data.message)
break
}
return Promise.reject(response)
}
if (!window.navigator.onLine) {
ElNotification({
title: '提示',
message: '网络连接断开,请检查网络',
type: 'error',
})
}
return Promise.reject(error)
},
)
export default ajax定义请求接口
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// src/api/dashboard.ts
import ajax from '@/utils/ajax'
export function dashboard(data?: any) {
return ajax({
url: '/dashboard',
method: 'post',
isMock: true,
data
})
}
// src/api/login.ts
import ajax from '@/utils/ajax'
export function login(data: any) {
return ajax({
url: '/login',
method: 'post',
isMock: true,
data
})
}
export function getUserInfo() {
return ajax({
url: '/userinfo',
method: 'get',
isMock: true
})
}在组件中使用
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<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { dashboard } from '@/api/dashboard'
import { useRouter } from 'vue-router'
import { mainStore } from '@/store/main'
const router = useRouter()
const store = mainStore()
const userinfo = store.getUserinfo
const username = ref(userinfo.name)
onMounted(() => {
dashboard({ a: 'a', b: 1 })
.then((result: any) => {
console.log(result)
})
.catch((err: any) => {
console.log(err)
})
})
const logout = () => {
store.clearToken()
router.replace('/login')
}
</script>
<template>
<h1>首页</h1>
<p><img alt="Vue logo" src="@/assets/images/logo.png" /></p>
<p>{{ $t('hello') }}, {{ username }}</p>
<p>
<my-button @click="logout">退出登录</my-button>
</p>
</template>
使用vite-plugin-mock
安装依赖
1
yarn add mockjs vite-plugin-mock -D
修改
vite.config.ts
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd())
const isProd: boolean = env.VITE_ENV === 'PROD'
return {
base: './',
plugins: [
vue(),
viteMockServe({
mockPath: './mock',
localEnabled: !isProd,
prodEnabled: false
})
]
}
})实例化Mock
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// mock/dashboard.ts
import { MockMethod } from 'vite-plugin-mock'
import Mock, { Random } from 'mockjs'
export default [
{
url: '/dashboard',
method: 'post',
// statusCode: 500,
// response: ({headers}: {headers:any}) => {
// // console.log(headers['x-sign'], headers['x-timestamp'], 'mock')
// return {
// errno: 0,
// message: 'success',
// data: Mock.mock({
// 'list|4': [
// {
// id: '@id',
// name: '@cname',
// date: Random.date('yyyy-MM-dd')
// }
// ]
// })
// }
// },
rawResponse: (req, res) => {
// console.log(req.headers, 'mock')
res.setHeader('Content-Type', 'application/json')
if (req.headers['x-sign']) {
res.statusCode = 200
res.end(
JSON.stringify({
errno: 0,
message: 'success',
data: Mock.mock({
'list|4': [
{
id: '@id',
name: '@cname',
date: Random.date('yyyy-MM-dd')
}
]
})
})
)
} else {
res.statusCode = 403
res.end('')
}
}
}
] as MockMethod[]
// mock/login.ts
import { MockMethod } from 'vite-plugin-mock'
import Mock, { Random } from 'mockjs'
import { splitQueryParams } from '@/utils/common'
export default [
{
url: '/login',
method: 'post',
rawResponse: async (req, res) => {
let reqbody = ''
await new Promise((resolve) => {
req.on('data', (chunk) => {
reqbody += chunk
})
req.on('end', () => resolve(undefined))
})
res.setHeader('Content-Type', 'application/json')
const body = splitQueryParams(reqbody)
if (!reqbody || !body || typeof body.username === 'undefined' || typeof body.password === 'undefined') {
res.statusCode = 403
res.end('')
return
}
res.statusCode = 200
if (body.username === 'admin' && body.password === 'password') {
res.end(
JSON.stringify({
errno: 0,
message: '登录成功',
data: {
token: Random.word(64)
}
})
)
} else {
res.end(
JSON.stringify({
errno: 1001,
message: '用户名或者密码错误'
})
)
}
}
},
{
url: '/userinfo',
method: 'get',
statusCode: 200,
response: () => {
return {
errno: 0,
message: 'success',
data: Mock.mock({
id: '@id',
name: '@cname',
date: Random.date('yyyy-MM-dd')
})
}
}
}
] as MockMethod[]
使用vite-plugin-svg-icons
加载svg图片
安装依赖
1
yarn add -D vite-plugin-svg-icons
修改
tsconfig.json
1
2
3
4
5
6{
"types": [
"node",
"vite-plugin-svg-icons/client"
]
}修改
vite.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
const path = require('path')
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd())
const isProd: boolean = env.VITE_ENV === 'PROD'
return {
base: './',
plugins: [
vue(),
createSvgIconsPlugin({
iconDirs: [path.resolve(__dirname, 'src/icons')],
symbolId: 'icon-[dir]-[name]',
}),
]
}
})在
main.ts
中注册1
2
3
4
5
6import { createApp } from 'vue'
import 'virtual:svg-icons-register'
import App from '@/App.vue'
const app = createApp(App)
app.mount('#app')定义组件
SvgIcon
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// src/components/global/SvgIcon.vue
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
prefix: {
type: String,
default: 'icon',
},
// name值为svg文件名
name: {
type: String,
required: true,
},
color: {
type: String,
default: '#333',
},
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const emit = defineEmits({
click: (evt: MouseEvent) => evt instanceof MouseEvent,
})
const handleClick = (evt: MouseEvent) => {
emit('click', evt)
}
</script>
<template>
<span @click="handleClick">
<svg class="svg-icon" aria-hidden="true">
<use :href="symbolId" :fill="color" />
</svg>
</span>
</template>
<style lang="less" scoped>
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
vertical-align: -0.15em;
}
</style>创建
src/icons
目录,并将svg文件存放到该目录下在组件中使用
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<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { dashboard } from '@/api/dashboard'
import { useRouter } from 'vue-router'
import { mainStore } from '@/store/main'
const router = useRouter()
const store = mainStore()
const userinfo = store.getUserinfo
const username = ref(userinfo.name)
onMounted(() => {
dashboard({ a: 'a', b: 1 })
.then((result: any) => {
console.log(result)
})
.catch((err: any) => {
console.log(err)
})
})
const logout = () => {
store.clearToken()
router.replace('/login')
}
</script>
<template>
<h1>首页</h1>
<p><img alt="Vue logo" src="@/assets/images/logo.png" /></p>
<p>{{ $t('hello') }}, {{ username }}</p>
<p>
<svg-icon class="logout" color="red" name="account-arrow-right-outline" />
<my-button @click="logout">退出登录</my-button>
</p>
</template>
<style type="text/less" scoped>
.logout {
font-size: 2em;
}
</style>