L
O
A
D
I
N
G

vite 搭建vue3项目(一)

一、创建项目

1.直接创建项目

1
2
3
4
5
6
7
使用 NPM:
$ npm create vite@latest
使用 Yarn:
$ yarn create vite
使用 PNPM:
$ pnpm create vite
然后按照提示操作即可!(选择vue,vue-ts)

2.使用模板创建项目

通过附加的命令行选项直接指定项目名称和你想要使用的模板例如,要构建一个 Vite + Vue 项目,运行:

1
2
3
4
5
6
7
8
使用 npm 6.x:
npm create vite@latest my-vue-app --template vue
使用 npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue
使用 yarn:
yarn create vite my-vue-app --template vue
使用 pnpm:
pnpm create vite my-vue-app --template vue

然后npm i 或者 yarn install 运行yarn dev 看看浏览器运行成了没,第一步就大功告成了

二、vite配置别名和环境变量的配置

1.配置别名

使用编辑器VScode打开刚刚搭建好的项目 进入配置文件 vite.config.ts

配置别名后的vite.config.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
const resolve = (dir: string) => path.join(__dirname, dir)

export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve('src'),
}
}
})

此时 TS 可能有这个错误提示:找不到模块“path”或其相应的类型声明

解决方法:

1
npm install @types/node --save-dev或者yarn add @types/node --save-dev

还需要在tsconfig.json的paths配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
],
"comps/*": [
"src/components/*"
],
"views/*": [
"src/views/*"
],
"store/*": [
"src/store/*"
]
},

2.环境变量的配置

vite 提供了两种模式:具有开发服务器的开发模式(development)和生产模式(production)

项目根目录新建:.env.development :

1
2
3
NODE_ENV=development

VITE_APP_WEB_URL= 'YOUR WEB URL'

项目根目录新建:.env.production :

1
2
3
NODE_ENV=production

VITE_APP_WEB_URL= 'YOUR WEB URL'

组件中使用:

1
console.log(import.meta.env.VITE_APP_WEB_URL)

配置 package.json:

打包区分开发环境和生产环境

1
2
"build:dev": "vite build --mode development",
"build:pro": "vite build --mode production",

三、配置跨域代理

在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
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve('src'),
comps: resolve('src/components'),
apis: resolve('src/apis'),
views: resolve('src/views'),
utils: resolve('src/utils'),
routes: resolve('src/routes'),
styles: resolve('src/styles')
}
},
server: {
// 配置前端服务地址和端口
//服务器主机名
host: '',
//端口号
port: 3088,
//设为 true 时若端口已被占用则会直接退出,而不是尝试下一个可用端口
strictPort: false,
//服务器启动时自动在浏览器中打开应用程序,当此值为字符串时,会被用作 URL 的路径名
open: false,
//自定义代理规则
proxy: {
// 选项写法
'/api': {
target: '',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
})

使用跨域代理:

​ 用代理, 首先你得有一个标识, 告诉他你这个连接要用代理. 不然的话, 可能你的 html, css, js这些静态资源都跑去代理. 所以我们一般只有接口用代理, 静态文件用本地.‘/api’: {}, 就是告诉node, 我接口只有是’/api’开头的才用代理.所以你的接口就要这么写 /api/xx/xx. 最后代理的路径就是 http://xxx.xx.com/api/xx/xx.可是不对啊, 我正确的接口路径里面没有/api啊. 所以就需要 pathRewrite,把’/api’去掉, 这样既能有正确标识, 又能在请求接口的时候去掉api.

四、添加 css 预处理器 sass

安装 :

1
2
npm install -D sass sass-loader
或者yarn add sass sass-loader

在 src/assets 下新增 style 文件夹,用于存放全局样式文件

五、约束代码风格

TypeScirpt 官方决定全面采用 ESLint 作为代码检查的工具,并创建了一个新项目 typescript-eslint,提供了 TypeScript 文件的解析器 @typescript-eslint/parser 和相关的配置选项 @typescript-eslint/eslint-plugin 等

1.Eslint支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# eslint 安装
yarn add eslint --dev
# eslint 插件安装
yarn add eslint-plugin-vue --dev

yarn add @typescript-eslint/eslint-plugin --dev

yarn add eslint-plugin-prettier --dev

# typescript parser
yarn add @typescript-eslint/parser --dev


直接:npm i typescript eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin -D

注意: 如果 eslint 安装报错:

可以尝试运行以下命令:

1
yarn config set ignore-engines true

项目下新建 .eslintrc.js配置 eslint 校验规则:

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
// 需要安装依赖:  npm i eslint-define-config
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
root: true,
/* 指定如何解析语法。*/
parser: 'vue-eslint-parser',
/* 优先级低于parse的语法解析配置 */
parserOptions: {
parser: '@typescript-eslint/parser',
},
// https://eslint.bootcss.com/docs/user-guide/configuring#specifying-globals
globals: {
Nullable: true,
},
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
// 'plugin:vue/vue3-recommended',
// 'plugin:vue/recommended' // Use this if you are using Vue.js 2.x.

'plugin:vue/vue3-recommended',
// 此条内容开启会导致 全局定义的 ts 类型报 no-undef 错误,因为
// https://cn.eslint.org/docs/rules/
'eslint:recommended',
'plugin:@typescript-eslint/recommended', // typescript-eslint推荐规则,
'prettier',
'plugin:prettier/recommended',
],
rules: {
// 'no-undef': 'off',
// 禁止使用 var
'no-var': 'error',
semi: 'off',
// 优先使用 interface 而不是 type
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'vue/html-indent': [
'error',
4,
{
attribute: 1,
baseIndent: 1,
closeBracket: 0,
alignAttributesVertically: true,
ignores: [],
},
],
// 关闭此规则 使用 prettier 的格式化规则, 感觉prettier 更加合理,
// 而且一起使用会有冲突
'vue/max-attributes-per-line': ['off'],
// 强制使用驼峰命名
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: false,
ignores: [],
},
],
},
})

项目下新建 .eslintignore

1
2
3
# eslint 忽略检查 (根据项目需要自行添加)
node_modules
dist

2.prettier支持

1
2
# 安装 prettier
yarn add prettier --dev

解决 eslint 和 prettier 冲突

解决 ESLint 中的样式规范和 prettier 中样式规范的冲突,以 prettier 的样式规范为准,使 ESLint 中的样式规范自动失效

1
2
# 安装插件 eslint-config-prettier
yarn add eslint-config-prettier --dev

项目下新建 .prettier.js

配置 prettier 格式化规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
tabWidth: 2,
jsxSingleQuote: true,
jsxBracketSameLine: true,
printWidth: 100,
singleQuote: true,
semi: false,
overrides: [
{
files: '*.json',
options: {
printWidth: 200,
},
},
],
arrowParens: 'always',
}

项目下新建 .prettierignore

1
2
3
# 忽略格式化文件 (根据项目需要自行添加)
node_modules
dist

package.json 配置:

1
2
3
4
5
6
{
"script": {
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
"prettier": "prettier --write ."
}
}

上面配置完成后,可以运行以下命令测试下代码检查个格式化效果:

1
2
3
4
# eslint 检查
yarn lint
# prettier 自动格式化
yarn prettier

六、安装路由

1
2
# 安装路由
yarn add vue-router@4

在 src 文件下新增 router 文件夹 => index.ts 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Login',
component: () => import('@/pages/login/Login.vue'), // 注意这里要带上 文件后缀.vue
},
]

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

export default router

修改入口文件 mian.ts :

1
2
3
4
5
6
7
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'

const app = createApp(App)
app.use(router)
app.mount('#app')

七、axios统一请求封装

1
2
3
4
5
6
7
# 安装 axios
yarn add axios
# 安装 nprogress 用于请求 loading
# 也可以根据项目需求自定义其它 loading
yarn add nprogress
# 类型声明,或者添加一个包含 `declare module 'nprogress'
yarn add @types/nprogress --dev

新增 service 文件夹,service 下新增 http.ts 文件以及 moudles 文件夹(存放各模块接口)和interface(公共ts)文件夹:

http.ts : 用于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
//http.ts
import axios, { AxiosRequestConfig } from 'axios'
import NProgress from 'nprogress'

// 设置请求头和请求路径
axios.defaults.baseURL = '/api'
axios.defaults.timeout = 10000
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.interceptors.request.use(
(config): AxiosRequestConfig<any> => {
const token = window.sessionStorage.getItem('token')
if (token) {
//@ts-ignore
config.headers.token = token
}
return config
},
(error) => {
return error
}
)
// 响应拦截
axios.interceptors.response.use((res) => {
if (res.data.code === 111) {
sessionStorage.setItem('token', '')
// token过期操作
}
return res
})

interface ResType<T> {
code: number
data?: T
msg: string
err?: string
}
interface Http {
get<T>(url: string, params?: unknown): Promise<ResType<T>>
post<T>(url: string, params?: unknown): Promise<ResType<T>>
upload<T>(url: string, params: unknown): Promise<ResType<T>>
download(url: string): void
}

const http: Http = {
get(url, params) {
return new Promise((resolve, reject) => {
NProgress.start()
axios
.get(url, { params })
.then((res) => {
NProgress.done()
resolve(res.data)
})
.catch((err) => {
NProgress.done()
reject(err.data)
})
})
},
post(url, params) {
return new Promise((resolve, reject) => {
NProgress.start()
axios
.post(url, JSON.stringify(params))
.then((res) => {
NProgress.done()
resolve(res.data)
})
.catch((err) => {
NProgress.done()
reject(err.data)
})
})
},
upload(url, file) {
return new Promise((resolve, reject) => {
NProgress.start()
axios
.post(url, file, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then((res) => {
NProgress.done()
resolve(res.data)
})
.catch((err) => {
NProgress.done()
reject(err.data)
})
})
},
download(url) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = url
iframe.onload = function () {
document.body.removeChild(iframe)
}
document.body.appendChild(iframe)
},
}
export default http

例如:moudles 下新增login文件夹,用于存放登录模块的请求接口,login 文件夹下分别新增 login.ts types.ts :

1
2
3
4
5
6
7
8
9
10
import http from '@/service/http'
import * as T from './types'

const loginApi: T.ILoginApi = {
login(params){
return http.post('/login', params)
}

}
export default loginApi

types.ts:

1
2
3
4
5
6
7
export interface ILoginParams {
userName: string
passWord: string | number
}
export interface ILoginApi {
login: (params: ILoginParams)=> Promise<any>
}

八、状态管理 pinia

1
2
# 安装
yarn add pinia@next

src 文件夹下新增 store 文件夹,接在在 store 中新增 main.ts

main.ts 中增加

1
2
3
4
# 引入
import { createPinia } from "pinia"
# 创建根存储库并将其传递给应用程序
app.use(createPinia())

九、添加element-plus

1
2
#安装 element-plus 
yarn add element-plus

1.element-plus按需引入

需要用到两个插件unplugin-vue-components、unplugin-auto-import这两个插件。

1
npm i unplugin-vue-components unplugin-auto-import -D

另外这里要注意的是,由于使用了 unplugin-vue-components unplugin-auto-import 这两个插件,按需加载其实是不需要 import 组件,但如果使用Api创建组件,例如elmesage,elnotification这些,可以看到不 import 的话会提示错误,如果 import 又会导致样式的丢失,需要下载一个插件

1
2
3
yarn add unplugin-element-plus -D
#或者
npm i unplugin-element-plus -D

配置vite.config.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
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import ElementPlus from 'unplugin-element-plus/vite'

const resolve = (dir: string) => path.join(__dirname, dir);

export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
ElementPlus()
],
//配置别名
resolve: {
alias: {
"@": resolve("src"),
comps: resolve("src/components"),
service: resolve("src/service"),
views: resolve("src/views"),
route: resolve("src/route"),
},
},
// css:{
//preprocessorOptions:{
//scss:{
// additionalData:'@import "@/assets/style/main.scss";'
// }
//}
// },
//配置跨域代理
server: {
// 配置前端服务地址和端口
//服务器主机名
host: "127.0.0.1",
//端口号
port: 3088,
//设为 true 时若端口已被占用则会直接退出,而不是尝试下一个可用端口
strictPort: false,
//服务器启动时自动在浏览器中打开应用程序,当此值为字符串时,会被用作 URL 的路径名
open: true,
//自定义代理规则
proxy: {
"/api": {
target: "http://localhost:3000", //要代理的本地api地址,也可以换成线上测试地址
changeOrigin: true, //跨域
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});

2.添加element-plus图标

1
2
3
4
# NPM
$ npm install @element-plus/icons-vue
# Yarn
$ yarn add @element-plus/icons-vue

然后在main.ts中全局注册并使用

1
2
3
4
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
Object.keys(ElementPlusIconsVue).forEach(key => {
app.component(key, ElementPlusIconsVue[key as keyof typeof ElementPlusIconsVue]);
});

十、svg图标插件使用

1.安装svg图标插件

1
2
#安装插件vue-svg-icon
npm install vue-svg-icon --save-dev

2. 注册全局组件svgIcon

在main.ts中全局注册并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import { createPinia } from "pinia"
import SvgIcon from './components/SvgIcon/index.vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import "element-plus/dist/index.css"
//样式
import './styles/index.scss'

const app = createApp(App)
// 注册element Icons组件
Object.keys(ElementPlusIconsVue).forEach(key => {
app.component(key, ElementPlusIconsVue[key as keyof typeof ElementPlusIconsVue]);
});
app.use(router).use(createPinia()).component('svg-icon', SvgIcon).mount('#app')

3.下载存放svg图标

在src下新建assets=>icons=>svg文件夹,用来存放svg图标,所有下载的SVG图标放入其中

image-20221008180639420

4. 使用svg图标

以bug.svg图标为例,修改scale的值调整图标的大小。

1
<svgIcon name="del" :scale="1" />

十一、untils文件夹

在根目录下新建一个untils文件夹,这个文件夹下的内容主要是导出常用的一些公共方法等等:

image-20221008171843967

untils=>util.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
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
160
161
162
163
164
165
166
167
168
169
170
//常用的工具方法
import { isArray } from "@/utils/is";

/**
* @description 获取localStorage
* @param {String} key Storage名称
* @return string
*/
export function localGet(key: string) {
const value = window.localStorage.getItem(key);
try {
return JSON.parse(window.localStorage.getItem(key) as string);
} catch (error) {
return value;
}
}

/**
* @description 存储localStorage
* @param {String} key Storage名称
* @param {Any} value Storage值
* @return void
*/
export function localSet(key: string, value: any) {
window.localStorage.setItem(key, JSON.stringify(value));
}

/**
* @description 清除localStorage
* @param {String} key Storage名称
* @return void
*/
export function localRemove(key: string) {
window.localStorage.removeItem(key);
}

/**
* @description 清除所有localStorage
* @return void
*/
export function localClear() {
window.localStorage.clear();
}

/**
* @description 对象数组深克隆
* @param {Object} obj 源对象
* @return object
*/
export function deepCopy<T>(obj: any): T {
let newObj: any;
try {
newObj = obj.push ? [] : {};
} catch (error) {
newObj = {};
}
for (let attr in obj) {
if (typeof obj[attr] === "object") {
newObj[attr] = deepCopy(obj[attr]);
} else {
newObj[attr] = obj[attr];
}
}
return newObj;
}

/**
* @description 判断数据类型
* @param {Any} val 需要判断类型的数据
* @return string
*/
export function isType(val: any) {
if (val === null) return "null";
if (typeof val !== "object") return typeof val;
else return Object.prototype.toString.call(val).slice(8, -1).toLocaleLowerCase();
}

/**
* @description 生成随机数
* @param {Number} min 最小值
* @param {Number} max 最大值
* @return number
*/
export function randomNum(min: number, max: number): number {
let num = Math.floor(Math.random() * (min - max) + max);
return num;
}
/**
* @description 递归查询当前路由所对应的路由
* @param {Array} menuList 菜单列表
* @param {String} path 当前地址
* @return array
*/
export function getTabPane<T, U>(menuList: any[], path: U): T {
let result: any;
for (let item of menuList || []) {
if (item.path === path) result = item;
const res = getTabPane(item.children, path);
if (res) result = res;
}
return result;
}

/**
* @description 使用递归处理路由菜单,生成一维数组
* @param {Array} menuList 所有菜单列表
* @param {Array} newArr 菜单的一维数组
* @return array
*/
export function handleRouter(routerList: Menu.MenuOptions[], newArr: string[] = []) {
routerList.forEach((item: Menu.MenuOptions) => {
typeof item === "object" && item.path && newArr.push(item.path);
item.children && item.children.length && handleRouter(item.children, newArr);
});
return newArr;
}

/**
* @description 扁平化数组对象
* @param {Array} arr 数组对象
* @return array
*/
export function getFlatArr(arr: any) {
return arr.reduce((pre: any, current: any) => {
let flatArr = [...pre, current];
if (current.children) flatArr = [...flatArr, ...getFlatArr(current.children)];
return flatArr;
}, []);
}

/**
* @description 格式化表格单元格默认值
* @param {Number} row 行
* @param {Number} col 列
* @param {String} callValue 当前单元格值
* @return string
* */
export function defaultFormat(row: number, col: number, callValue: any) {
// 如果当前值为数组,使用 / 拼接(根据需求自定义)
if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : "--";
return callValue ?? "--";
}

/**
* @description 处理无数据情况
* @param {String} callValue 需要处理的值
* @return string
* */
export function formatValue(callValue: any) {
// 如果当前值为数组,使用 / 拼接(根据需求自定义)
if (isArray(callValue)) return callValue.length ? callValue.join(" / ") : "--";
return callValue ?? "--";
}

/**
* @description 根据枚举列表查询当需要的数据(如果指定了 label 和 value 的 key值,会自动识别格式化)
* @param {String} callValue 当前单元格值
* @param {Array} enumData 枚举列表
* @param {String} type 过滤类型(目前只有 tag)
* @return string
* */
export function filterEnum(callValue: any, enumData: any, searchProps?: { [key: string]: any }, type?: string): string {
const value = searchProps?.value ?? "value";
const label = searchProps?.label ?? "label";
let filterData: any = {};
if (Array.isArray(enumData)) filterData = enumData.find((item: any) => item[value] === callValue);
if (type == "tag") return filterData?.tagType ? filterData.tagType : "";
return filterData ? filterData[label] : "--";
}

is文件夹下的index.ts 常用的判断方式,is=>index.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
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
const toString = Object.prototype.toString;

/**
* @description: 判断值是否未某个类型
*/
export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`;
}

/**
* @description: 是否为函数
*/
export function isFunction<T = Function>(val: unknown): val is T {
return is(val, "Function");
}

/**
* @description: 是否已定义
*/
export const isDef = <T = unknown>(val?: T): val is T => {
return typeof val !== "undefined";
};

export const isUnDef = <T = unknown>(val?: T): val is T => {
return !isDef(val);
};
/**
* @description: 是否为对象
*/
export const isObject = (val: any): val is Record<any, any> => {
return val !== null && is(val, "Object");
};

/**
* @description: 是否为时间
*/
export function isDate(val: unknown): val is Date {
return is(val, "Date");
}

/**
* @description: 是否为数值
*/
export function isNumber(val: unknown): val is number {
return is(val, "Number");
}

/**
* @description: 是否为AsyncFunction
*/
export function isAsyncFunction<T = any>(val: unknown): val is Promise<T> {
return is(val, "AsyncFunction");
}

/**
* @description: 是否为promise
*/
export function isPromise<T = any>(val: unknown): val is Promise<T> {
return is(val, "Promise") && isObject(val) && isFunction(val.then) && isFunction(val.catch);
}

/**
* @description: 是否为字符串
*/
export function isString(val: unknown): val is string {
return is(val, "String");
}

/**
* @description: 是否为boolean类型
*/
export function isBoolean(val: unknown): val is boolean {
return is(val, "Boolean");
}

/**
* @description: 是否为数组
*/
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}

/**
* @description: 是否客户端
*/
export const isClient = () => {
return typeof window !== "undefined";
};

/**
* @description: 是否为浏览器
*/
export const isWindow = (val: any): val is Window => {
return typeof window !== "undefined" && is(val, "Window");
};

export const isElement = (val: unknown): val is Element => {
return isObject(val) && !!val.tagName;
};

export const isServer = typeof window === "undefined";

// 是否为图片节点
export function isImageDom(o: Element) {
return o && ["IMAGE", "IMG"].includes(o.tagName);
}

export function isNull(val: unknown): val is null {
return val === null;
}

export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val);
}

export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val);
}

十二、typings->global.d.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
// * Menu
declare namespace Menu {
interface MenuOptions {
path: string;
title: string;
icon?: string;
isLink?: string;
close?: boolean;
children?: MenuOptions[];
}
}

declare type TabsOptions = Menu.MenuOptions & {};

// * Vite
declare type Recordable<T = any> = Record<string, T>;

declare interface ViteEnv {
VITE_API_URL: string;
VITE_PORT: number;
VITE_OPEN: boolean;
VITE_GLOB_APP_TITLE: string;
VITE_DROP_CONSOLE: boolean;
VITE_PROXY_URL: string;
VITE_BUILD_GZIP: boolean;
VITE_REPORT: boolean;
}

十三、vue3.0使用tsx语法

1.下载

1
yarn add @vitejs/plugin-vue-jsx -D

2.引入

在vite.config.ts中

1
2
3
4
5
6
7
8
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),vueJsx()]
})

3.tsconfig.json 配置文件

1
2
3
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",

参考文章:

Vite2 + Vue3 + TypeScript + Pinia 搭建一套企业级的开发脚手架

Vue3 + Ts + ElementPlus + Vite2 从零搭建后台管理系统

禾几元老哥的github源码

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

我是穷比,在线乞讨!

支付宝
微信