在使用Vue3 + TypeScript的过程中,这种写法在适应之后,确实更加舒服,通过Vue3的setup语法糖,我可以将一个功能模块的所有东西,都写到一个片段中,而不是像之前一样,一个功能涉及到的变量、方法、watch、computed等东一块西一块,需要维护的时候,还要上下翻来翻去。
而网上有一些大佬,包括我身边的几位前端朋友,都在批判TypeScript,毕竟习惯了裸奔,突然给你加了一层又一层的衣服,哈哈,难免不适应。我个人刚开始使用TypeScript的时候,也相当不适应,经常编辑器一片红,但是,ts可以帮助我在写代码的时候更加严谨,也就避免的运行项目的时候,出现各种各样的错误。我个人宁可编辑器一篇红,也不愿意在浏览器里错误百出。毕竟,本人水平不高,只有这么点认知。
在这里,还是要说一下ts和js的关系,ts是js的超集,简单粗俗的去理解,ts就是给js加了类型系统,ts提供了js的所有功能,想要很好的使用ts的前提,还是要熟练掌握js的。
大家可以阅读vite中文文档的开始项,vite需要Node.js14.18以上版本,我使用nvm,安装了14.19.1版本。
https://cn.vitejs.dev/ vite中文文档
https://blog.csdn.net/m0_64697285/article/details/127318141 参考这位博主文章,安装nvm工具
编辑器终端中,使用npm构建vite项目
npm create vite@latest
输入项目名
? Project name: » vue3_demo
选择Vue框架
? Select a framework: » - Use arrow-keys. Return to submit.
Vanilla
> Vue
React
Preact
Lit
Svelte
Others
选择TypeScript
? Select a variant: » - Use arrow-keys. Return to submit.
JavaScript
> TypeScript
Customize with create-vue ↗
Nuxt ↗
进入创建的项目 可以直接控制台 cd vue3_demo 也可以编辑器打开文件夹vue3_demo
npm install
npm run dev
vscode安装插件
1.安装插件
npm install -D eslint eslint-plugin-vue
2.进行eslint初始化
npm init @eslint/config
下面是我自己配置时候的选项,根据自己的需要去选择
? How would you like to use ESLint? ... => To check syntax and find problems
? What type of modules does your project use? ... => JavaScript modules (import/export)
? Which framework does your project use? ... => Vue.js
Does your project use TypeScript? => Yes
? Where does your code run? ... => Browser, Node
? What format do you want your config file to be in? ... => JavaScript
? Would you like to install them now? => Yes
? Which package manager do you want to use? ... => npm
运行完后,根目录下会生成.eslintrc.cjs文件
下面是我自己的配置项,非常简单的配置
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended' // 这里是将prettier作为插件被eslint使用,避免两者有冲突,放在extends最下方
],
overrides: [],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
parser: '@typescript-eslint/parser'
},
plugins: ['vue', '@typescript-eslint'],
rules: {
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'max-len': ['error', 100], // 强制一行的最大长度
'no-shadow': 'off', // 禁止变量声明与外层作用域的变量同名
'no-unused-vars': 'warn', // 禁止出现未使用过的变量
eqeqeq: 'off', // 要求使用 === 和 !==
'prefer-const': 'warn', // 要求使用 const 声明那些声明后不再被修改的变量
'@typescript-eslint/no-empty-function': 'off', // 不允许空函数
'@typescript-eslint/no-explicit-any': 'off' // 禁止使用 any 类型
}
}
由于是vite构建的项目,需要添加插件,让vite识别eslint相关配置
npm install vite-plugin-eslint --save-dev
修改vite.config.ts
import eslint from 'vite-plugin-eslint' // 新增
plugins: [vue(), eslint()], // 新增 eslint()
配置编辑器保存后eslint自动修复代码
vscode => setting.json
{
"workbench.colorTheme": "Bluloco Light",
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"editor.fontSize": 16,
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
},
"workbench.editor.enablePreview": false,
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
"typescript.format.insertSpaceBeforeFunctionParenthesis": true,
"eslint.validate": [
"vue",
"html",
"javascript",
"graphql",
"javascriptreact",
"json",
"typescript",
"typescriptreact",
"js",
"ts",
"md"
],
"prettier.singleQuote": true,
"prettier.semi": false,
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"workbench.iconTheme": "vscode-icons",
"editor.fontWeight": "normal",
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"cursorcode.accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkN1VWI0NkVHT3BHaXI4TDh3VkRQRCJ9.eyJpc3MiOiJodHRwczovL2N1cnNvci51cy5hdXRoMC5jb20vIiwic3ViIjoiZ2l0aHVifDEyMjAxMzc0OCIsImF1ZCI6WyJodHRwczovL2N1cnNvci51cy5hdXRoMC5jb20vYXBpL3YyLyIsImh0dHBzOi8vY3Vyc29yLnVzLmF1dGgwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE2ODI0MTUyNTksImV4cCI6MTY4MjUwMTY1OSwiYXpwIjoiS2JaVVI0MWNZN1c2elJTZHBTVUo3STdtTFlCS09DbUIiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIn0.vfr0VLYkPSI4lXmGPt2I7oDbqsqLmjpMCiBAxpeauiVb6B_7Xb7s3OOoa6l2mSAMl1gnd7uqdNhtqk78pOxoo9Kt3mTfqTO-KlhJ3ppUG5m9NN6LIDcoxsYa8KeplvYPmgRD9vcz1P22xQjHc--g4bgrG85pjBB92irqpzCBKQkW9JRqi514vRF0oVWRoh7aX6NafnMKqjtqeLch8T4tuEPHmH88hAM5TOKLaTGO3fr4Iu7hqTRHOntxiWMJdr38YTIUEIkjIfrFxX5mHIL2mPUur6y9gyIsBpAoB3BJqWK6-xxJyMYtbJmkReinOdsj4Irt31Vu1ddm18P5Vb3FIQ",
"volar.format.initialIndent": {
"html": true
},
"prettier.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"stylelint.enable": true,
"scss.lint.unknownAtRules": "ignore",
"stylelint.validate": ["css", "scss", "vue"],
"editor.tabSize": 2,
"prettier.useTabs": true,
"editor.formatOnSave": true, // 开启保存文件自动格式化代码
"editor.defaultFormatter": "esbenp.prettier-vscode", // 默认的代码格式化工具
"prettier.requireConfig": true,
"settingsSync.ignoredExtensions": [] // 需要Prettier的配置文件
}
修改tsconfig.json
"compilerOptions": {
"types": ["vite/client"], // 新增,解决eslint对import.meta.env的报错
}
最后,配置prettier自动修复另外一些代码
npm install -D prettier
npm install -D eslint-config-prettier // eslint兼容的插件
npm install -D eslint-plugin-prettier // eslint的prettier
根目录下新建.prettierrc.cjs
module.exports = {
// 一行最多一百字符
printWidth: 100,
// 使用2个空格缩进
tabWidth: 2,
// 不适用缩进符而使用空格
useTabs: false,
// 不尾随分号
semi: false,
// 使用单引号
singleQuote: true,
// 多行逗号分隔的语法中,最后一行不加逗号
trailingComma: 'none',
// 单个参数的箭头函数不加括号
arrowParens: 'avoid',
// 对象大括号内两边是否加空格
bracketSpacing: true
}
与src同级,创建.env.production与.env.development文件
字段须VITE开头,否则不识别,也可以自行配置后,使用其他字段
想用其他字段 参考 https://blog.csdn.net/weixin_45547638/article/details/127277857
// .env.development
# 开发环境
VITE_BASE_URL = http://192.168.50.207:8080/api
VITE_APP_TITLE = 开发环境
// .env.production
# 生产环境
VITE_BASE_URL = http://192.168.50.161:5173/api
VITE_APP_TITLE = 生产环境
// 在compilerOptions中新增
{
"compilerOptions": {
...
"baseUrl": ".", // 未设置baseUrl,不允许使用非相对路径
"paths": {
"@": ["src"],
"@/*": ["src/*"]
}
},
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default (({ mode }) => {
在启动项目时,控制台打印环境。只是给自己看当前属于什么环境和BASE_URL
console.log(mode)
const BASE_URL = loadEnv(mode, process.cwd()).VITE_BASE_URL
console.log(BASE_URL)
return defineConfig({
// base: './',
plugins: [vue()],
// 配置别名
resolve: {
alias: {
"@": resolve(__dirname, './src')
}
}
})
})
// 会报错: 找不到名称'__dirname'
path模块是node.js的内置模块,node.js默认不支持ts文件
安装@type/node依赖包 npm install @types/node --save-dev
// package.json 修改scripts中内容,修改后为
"scripts": {
"start": "vite --host --open", // npm start启动项目,启动后自动打开浏览器,并在控制台输出BASE_URL
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
// 第一步 npm安装
npm install element-plus --save
// 第二步 参考Element Plus官网,可完整引入或按需导入 我选择按需导入
// 按需导入需要安装以下两个插件
npm install -D unplugin-vue-components unplugin-auto-import
// 修改vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default (({ mode }) => {
console.log(mode)
const BASE_URL = loadEnv(mode, process.cwd()).VITE_BASE_URL
console.log(BASE_URL)
return defineConfig({
// base: './',
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
}),
],
// 配置别名
resolve: {
alias: {
"@": resolve(__dirname, './src')
}
}
})
})
// 安装Element Plus Icon
npm install @element-plus/icons-vue
// 修改main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount('#app')
// 安装Less
npm i less
// npm安装
npm install pinia
// 先在main.ts中引入pinia
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia).mount('#app')
// src下新建store文件夹 根据不同业务需要创建不同的 use_textName_Store.ts 文件
// 安装pinia-plugin-persist 实现数据持久化储存
npm i pinia-plugin-persist --save
// 新建useUserStore.ts
import { defineStore } from "pinia";
export const useUserStore = defineStore('user', {
state: () => {
return {
userName: sessionStorage.getItem("userName"),
phone: sessionStorage.getItem("phone"),
token: sessionStorage.getItem("satoken"),
}
},
getters: {},
actions: {
setUser (user: any) {
this.userName = user.userName
this.phone = user.phone
sessionStorage.setItem("userName", user.userName)
sessionStorage.setItem("phone", user.phone)
},
setToken (token: any) {
this.token = token
sessionStorage.setItem(token.tokenName, token.tokenValue)
}
},
// 开启数据缓存 默认sessionStorage state属性全部储存
persist: {
enabled: true,
}
// 也可以指定sessionStorage或者localStorage 指定持久化字段
persist: {
enabled: true,
strategies: [
{
storage: localStorage,
paths: ['id'],//指定要持久化的字段
}
]
}
})
// Pinia和VueX相比,在单个文件中最大的差别,就是去掉了饱受诟病的mutations
// 在.vue文件中使用pinia=>useUserStore
import { useUserStore } from '@/store/userStore';
const userStore = useUserStore()
// npm安装Vue-Router
npm install vue-router@4
// 网上有很多关于vue3动态路由的文档博客,配合ts使用,不过我试用了很多,效果都不尽人意,最后,自己总结了可行的动态路由方式。没有配合后端来完成动态路由,后期研究明白,会在文章中补充的。
// src下新建router文件夹 => 新建index.ts 存放不需要权限的路由及beforeEach
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
const staticRoutes: Array<RouteRecordRaw> = [
{
path: '/login',
name: 'Login',
meta: {
hidden: true,
title: '登录页'
},
component: () => import('@/views/login/index.vue')
},
{
path: '/',
name: 'layout',
component: () => import('@/layout/index.vue'),
redirect: '/home',
meta: {
hidden: false
},
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
icon: 'House'
}
}
]
},
{
path: '/404',
meta: {
hidden: true,
title: '404'
},
component: () => import('@/views/error_page/404.vue')
},
]
const router = createRouter({
history: createWebHistory(),
routes: staticRoutes
})
export default router
// router文件夹下 新建trendsRouter.ts 存放动态路由内容
import { RouteRecordRaw } from "vue-router";
const trendsRouter: Array<RouteRecordRaw> = [
{
path: '/echarts',
name: 'echarts',
component: () => import('@/views/echarts/index.vue'),
meta: {
title: '可视化',
icon: 'House'
}
}
]
export default trendsRouter
// 在index.ts中,引入trendsRouter.ts,并补充router.beforeEach
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import trendsRouter from "./trendsRouter";
const staticRoutes: Array<RouteRecordRaw> = [
{
path: '/login',
name: 'Login',
meta: {
hidden: true,
title: '登录页'
},
component: () => import('@/views/login/index.vue')
},
{
path: '/',
name: 'layout',
component: () => import('@/layout/index.vue'),
redirect: '/home',
meta: {
hidden: false
},
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页',
icon: 'House'
}
}
]
},
{
path: '/404',
meta: {
hidden: true,
title: '404'
},
component: () => import('@/views/error_page/404.vue')
},
]
const router = createRouter({
history: createWebHistory(),
routes: staticRoutes
})
// 创建一个变量,用来判断是否已经动态添加了路由 防止陷入动态路由死循环
let needLoad = true
// 配置白名单
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
const token = sessionStorage.getItem("satoken")
if (to.meta.title) {
document.title = to.meta.title as string
}
if (!token) {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next('/login')
}
} else {
if (to.path === '/login') {
next('/home')
} else {
if (needLoad) {
// 这里用router.options.routes 是因为在实际应用中,我发现直接router.addRoute动态添加路由后,页面总是为空,后来发现新增的路由并没有添加成功,通过此方法,可以有效保证动态添加路由成功并成功跳转
const current: any = router.options.routes
trendsRouter.forEach(v => {
current[1].children.push(v)
router.addRoute('layout',v)
})
router.addRoute({
path: '/:catchAll(.*)',
redirect: '/404'
},)
needLoad = false
// 防止空白页面或者404 动态添加完后,路由重新跳转
next({ ...to, replace: true })
} else {
next()
}
}
}
})
export default router
// 在main.ts中引入router
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const pinia = createPinia()
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(pinia).use(router).mount('#app')
// 在页面中使用router
import { useRouter, useRoute } from 'vue-router';
const router = useRouter()
const route = useRoute()
当然,目前仅是动态添加没有问题,页面跳转没有问题,如果sidebar选项在动态添加的路由上,我刷新页面的话,浏览器会有警告,但并不影响页面显示及路由跳转
[Vue Router warn]: No match found for location with path "/echarts"
关于路由配置及动态路由,各位有什么好的意见或者方法,可以评论给我,多谢多谢!!!
// npm安装axios
npm install axios
// 个人习惯将request及接口管理文件夹都放在一起
// src下新建http文件夹 http => 新建request.ts
import axios from 'axios'
// 页面顶部的进度条,使用起来简单
// npm i nprogress npm i --save-dev @types/nprogress
import Nprogress from 'nprogress'
import 'nprogress/nprogress.css'
import { ElMessage } from 'element-plus'
const request = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
timeout: 5000
})
const NERWORK_ERROR = '服务器错误,请稍后重试!'
request.interceptors.request.use(config => {
Nprogress.start()
const token = sessionStorage.getItem("satoken")
config.headers.satoken = token
return config
}, error => {
Nprogress.done()
return Promise.reject(error)
})
request.interceptors.response.use(res => {
Nprogress.done()
if (res.status === 200) {
return res.data
} else {
ElMessage.error(NERWORK_ERROR)
return Promise.reject(NERWORK_ERROR)
}
})
class Http {
get = function (url: string, params: any) {
return request({
url: url,
method: 'get',
headers: { 'Content-Type': 'multipart/form-data' },
params
})
}
post = function (url: string, data: any) {
return request({
url: url,
method: 'post',
data
})
}
}
const http = new Http()
export { http }
// http文件夹 => 新建interface文件夹 用于管理各个业务场景下的接口
// interface文件夹 => 新建user.ts 管理用户的注册登录登出,用户信息等接口
class Apis {
register = `user/register`
login = `user/login`
refresh = `user/hello`
}
const user = new Apis()
export { user }
// http文件夹 => 新建http.ts 整合所有接口文件与request
import { http } from "./request";
import { user } from './interface/user'
const register = (data: any) => {
return http.post(user.register, data)
}
const login = (data: any) => {
return http.post(user.login, data)
}
const refresh = (data: any) => {
return http.post(user.refresh, data)
}
export default {
register,
login,
refresh
}
// 在main.ts中引入http 全局挂载 个人喜欢用$http
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import http from './http/http'
const pinia = createPinia()
const app = createApp(App)
// vue3中 不能再使用vue.prototype.$http
app.config.globalProperties.$http = http;
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(pinia).use(router).mount('#app')
// 在页面中使用$http,也不能再是简单的通过this.$http来使用,为了避免每个页面都返货的获取上下文,我简单封了一个获取全局的方法
// src => 新建utils文件夹 => 新建getCurrentInstance.ts
import { getCurrentInstance, ComponentInternalInstance } from 'vue';
export default function useCurrentInstance() {
const currentInstance = getCurrentInstance() as ComponentInternalInstance;
const proxy = currentInstance.appContext.config.globalProperties;
// 所有通过app.config.globalProperties挂载的,都在proxy中
return proxy
}
// 页面中
import useCurrentInstance from '@/utils/getCurrentInstance';
const { $http } = useCurrentInstance();
// 通过$http.login(data) 来登录。。。
// 配置axios的过程中,可能会有Property does not exist on type 'AxiosResponse'报错
// 如果有报错, src => 新建shims-vue.d.ts
import { AxiosRequestConfig } from "axios";
declare module 'axios' {
interface AxiosInstance {
(config: AxiosRequestConfig): Promise<any>
}
}
// npm安装mock.js
npm install mockjs
// src => 新建mock文件夹 => 新建type.ts
// typeScript写参数接口,这个东西看着J里J气的,哈哈,越来越像Java了
export interface MockParams {
url: string,
type: string,
data?: any,
params?: any,
response(option?: any): Record<string, unknown>
}
// mock文件夹 => 新建api文件夹 => 新建user.ts 模拟用户注册登录等
function paramObj (url: any) {
const search = url.split('?')[1]
if (!search) {
return {}
}
return JSON.parse(
'{"' +
decodeURIComponent(search)
.replace(/"/g, '\\"')
.replace(/&/g, '","')
.replace(/=/g, '":"') +
'"}')
}
const userLogin = {
url: `user/login`,
type: "post",
response: (config: any) => {
const { userName, userPassword } = JSON.parse(config.body)
if (userName === 'wq19970106' && userPassword === 'Wq19970106..') {
return {
code: 20000,
message: '登录成功',
data: {
tokenName: 'satoken',
tokenValue: 'dsadfhasoidqwehdasd',
userName: '王琪',
phone: '15582868787'
}
}
} else {
return {
code: 40010,
message: '账号或密码错误'
}
}
}
}
const userRegister = {
url: `/user/register`,
type: 'post',
response: (config: any) => {
const { userName, userPassword } = JSON.parse(config.body)
if (userName !== 'wq19970106' && userPassword) {
return {
code: 20000,
message: '注册成功'
}
} else {
return {
code: 40010,
message: '账号已存在'
}
}
}
}
const user = [userLogin, userRegister]
export default user
// mock文件夹 => 新建index.ts
import Mock from 'mockjs'
import { MockParams } from './type'
import user from './api/user'
const mocks = [...user]
export function mockRequest() {
let i: MockParams
for(i of mocks){
Mock.mock(new RegExp(i.url), i.type || 'get', i.response)
}
}
// 在main.ts中引入使用
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import { mockRequest } from './mock'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import http from './http/http'
const pinia = createPinia()
const app = createApp(App)
app.config.globalProperties.$http = http;
if(process.env.NODE_ENV === 'development'){
mockRequest()
}
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(pinia).use(router).mount('#app')
// 在配置mock.js过程中 控制台会有typeScript关于mock的报错
npm install @types/mockjs
// shims-vue.d.ts 添加
declare module 'mockjs'
// npm安装
npm install moment
npm i --save lodash
// moment是关于时间的工具类,内容全面,功能强大,同样的还有day.js 本人更习惯moment
// main.ts中引入moment,全局挂载
import moment from 'moment'
app.config.globalProperties.$moment = moment;
// 在页面中同样使用封好的getCurrentInstance
import useCurrentInstance from '@/utils/getCurrentInstance';
const { $http, $moment } = useCurrentInstance();
const now = ref($moment().format("LTS"));
const timer = setInterval(function () {
now.value = $moment().format("LTS")
}, 1000)
// lodash是一款很实用的js工具类,包含数组、集合、字符串等操作函数,我用的更多的是防抖之类的方法
// npm安装
npm install echarts
// utils文件夹 => 新建echarts.ts 用于按需导入echarts
import * as echarts from 'echarts/core';
import {
BarChart,
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption,
LineChart,
LineSeriesOption
} from 'echarts/charts';
import {
TitleComponent,
// 组件类型的定义后缀都为 ComponentOption
TitleComponentOption,
TooltipComponent,
TooltipComponentOption,
ToolboxComponent,
GridComponent,
GridComponentOption,
// 数据集组件
DatasetComponent,
DatasetComponentOption,
// 内置数据转换器组件 (filter, sort)
TransformComponent,
LegendComponent
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = echarts.ComposeOption<
| BarSeriesOption
| LineSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
>;
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
ToolboxComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LineChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
LegendComponent
]);
export default echarts
// 修改main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createPinia } from 'pinia'
import { mockRequest } from './mock'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import moment from 'moment'
import http from './http/http'
import echarts from './utils/echarts'
// echarts.resize()会有浏览器警告,npm i default-passive-events -S
import 'default-passive-events'
const pinia = createPinia()
const app = createApp(App)
app.config.globalProperties.$moment = moment;
app.config.globalProperties.$http = http;
app.config.globalProperties.$echarts = echarts
if(process.env.NODE_ENV === 'development'){
mockRequest()
}
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(pinia).use(router).mount('#app')
// 在需要的页面中使用echarts
<template>
<div class="echarts">
<div id="main" ref="mainDom"></div>
</div>
</template>
<script lang='ts' setup>
import { ref, nextTick, watch } from 'vue';
import useCurrentInstance from '@/utils/getCurrentInstance'
import { useUserStore } from '@/store/userStore';
const userStore = useUserStore()
const { $echarts } = useCurrentInstance()
const mainDom = ref(null)
let myEcharts: any
const initEchart = () => {
myEcharts = $echarts.init(mainDom.value)
let options = {
title: {
text: '数据报表',
textStyle: {
color: '#567788',
fontStyle: 'oblique',
fontWeight: 'lighter'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
//类型分类
legend: {
data: ['用户1', '用户2', '用户3', '用户4', '用户5']
},
toolbox: {
feature: {
saveAsImage: {}
}
},
//坐标
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true//如果坐标图上xy轴显示的有标示,一定要为true,否则展示不全,内容会被隔挡
},
//X轴的命名
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
//bar--柱状图
//line -- 线图
{
name: '用户1',
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '用户2',
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [240, 182, 291, 234, 290, 330, 310]
},
{
name: '用户3',
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [120, 232, 2101, 154, 190, 330, 410]
},
{
name: '用户4',
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [360, 332, 301, 334, 390, 330, 320]
},
{
name: '用户5',
type: 'line',
stack: 'Total',
label: {
show: true,
position: 'top'
},
areaStyle: {},
emphasis: {
focus: 'series'
},
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
myEcharts.setOption(options)
window.addEventListener('resize', () => {
myEcharts.resize()
})
}
nextTick(()=> {
initEchart()
})
watch(userStore, () => {
myEcharts.dispose()
setTimeout(() => {
initEchart()
},300);
})
</script>
<style lang='less' scoped>
.echarts {
width: 100%;
height: 100%;
position: relative;
#main {
width: 45%;
height: 45%;
position: absolute;
top: 10px;
left: 10px;
border: 1px solid black;
}
}
</style>
至此,vite构建的最基本的vue3项目就搭建完了。有任何不妥的地方,各位大佬不吝指点一下。多谢*500!