赞
踩
技术栈 | 描述 | 官网 |
---|---|---|
Vue3 | 渐进式 JavaScript 框架 | https://cn.vuejs.org/ |
Element Plus | 基于 Vue 3,面向设计师和开发者的组件库 | https://element-plus.gitee.io/zh-CN/ |
Vite | 前端开发与构建工具 | https://cn.vitejs.dev/guide/ |
TypeScript | 开发语言,是 JavaScript 的超集 | |
Vue Router | Vue.js 的官方路由 | https://router.vuejs.org/zh/ |
wangEditor | Typescript 开发的 Web 富文本编辑器 | www.wangeditor.com/ |
Echarts | 开源可视化图表库 | https://echarts.apache.org/zh/ |
vue-i18n | Vue 国际化多语言插件 | https://vue-i18n.intlify.dev/ |
VueUse | 基于Vue组合式API的实用工具集(类比HuTool工具) | http://www.vueusejs.com/guide/ |
环境 | 名称 |
---|---|
运行环境 | Node 16+ |
开发工具 | VSCode |
VSCode插件 | Vue Language Features (Volar) TypeScript Vue Plugin (Volar) ,禁用Vetur |
推荐安装nvm控制node版本
按照 vite 官网搭建第一个 Vite 项目,执行以下命令完成 vue 、typescirpt 模板项目的初始化
npm init vite@latest vue3-element-admin --template vue-ts
npm install
npm run dev
相对路径别名配置,使用 @ 代替 src
配置 vite.config.ts
npm install @types/node
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path"; //这个path用到了上面安装的@types/node
const pathSrc = path.resolve(__dirname, "src");
// https://vitejs.dev/config/
export default defineConfig({
// 路径别名
resolve: {
alias: {
"@": pathSrc, // @代替src
},
},
plugins: [vue()],
});
// src/App.vue
import HelloWorld from '../src/components/HelloWorld.vue'
import HelloWorld from '@/components/HelloWorld.vue'
修改tsconfig.json
"compilerOptions": {
...
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
"@/*": ["src/*"]
}
}
# 选择一个你喜欢的包管理器
# NPM
$ npm install element-plus --save
# Yarn
$ yarn add element-plus
# pnpm
$ pnpm install element-plus
如果你对打包后的文件大小不是很在乎,那么使用完整导入会更方便。
// main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
您需要使用额外的插件来导入要使用的组件。
首先你需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件
npm install -D unplugin-vue-components unplugin-auto-import
vite.config.ts - 自动导入配置
src
目录下,新建 types
目录,用于存放自动导入函数和组件的TS类型声明文件。
import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import AutoImport from "unplugin-auto-import/vite"; import Components from "unplugin-vue-components/vite"; import path from "path"; //这个path用到了上面安装的@types/node const pathSrc = path.resolve(__dirname, "src"); // https://vitejs.dev/config/ export default defineConfig({ // 路径别名 resolve: { alias: { "@": pathSrc, // @代替src }, }, plugins: [ vue(), AutoImport({ // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 imports: ["vue"], eslintrc: { enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件 }, dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径 }), Components({ dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径 }), ], });
.eslintrc.cjs - 自动导入函数 eslint 规则引入
"extends": [
"./.eslintrc-auto-import.json"
],
Element Plus 提供了基于 ES Module 的开箱即用的 Tree Shaking 功能。
但你需要安装 unplugin-element-plus 来导入样式。 配置文档参考 docs
App.vue
<template>
<el-button>我是 ElButton</el-button>
</template>
<script>
import { ElButton } from 'element-plus'
export default {
components: { ElButton },
}
</script>
// vite.config.ts
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'
export default defineConfig({
// ...
plugins: [ElementPlus()],
})
npm i -D unplugin-icons
// vite.config.ts import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; import Icons from "unplugin-icons/vite"; import IconsResolver from "unplugin-icons/resolver"; export default ({ mode }: ConfigEnv): UserConfig => { return { plugins: [ // ... AutoImport({ // ... resolvers: [ // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式) ElementPlusResolver(), // 自动导入图标组件 IconsResolver({}), ] vueTemplate: true, // 是否在 vue 模板中自动导入 dts: path.resolve(pathSrc, 'types', 'auto-imports.d.ts') // 自动导入组件类型声明文件位置,默认根目录 }), Components({ resolvers: [ // 自动导入 Element Plus 组件 ElementPlusResolver(), // 自动注册图标组件 IconsResolver({ enabledCollections: ["ep"] // element-plus图标库,其他图标库 https://icon-sets.iconify.design/ }), ], dts: path.resolve(pathSrc, "types", "components.d.ts"), // 自动导入组件类型声明文件位置,默认根目录 }), Icons({ // 自动安装图标库 autoInstall: true, }), ], }; };
参考配置:element-plus-best-practices - vite.config.ts
<template> <h1>{{ msg }}</h1> <div class="card"> <button type="button" @click="count++">count is {{ count }}</button> </div> <div class="card"> <el-button type="success"><i-ep-SuccessFilled />Success</el-button> <el-button type="info"><i-ep-InfoFilled />Info</el-button> <el-button type="warning"><i-ep-WarningFilled />Warning</el-button> <el-button type="danger"><i-ep-WarnTriangleFilled />Danger</el-button> </div> </template> <style scoped> .read-the-docs { color: #888; } </style>
通过 vite-plugin-svg-icons 插件,Iconfont
第三方图标库实现本地图标。
npm install -D fast-glob@3.2.11
npm install -D vite-plugin-svg-icons@2.0.1
// src/main.ts
import 'virtual:svg-icons-register';
// vite.config.ts import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; export default ({command, mode}: ConfigEnv): UserConfig => { return ( { plugins: [ createSvgIconsPlugin({ // 指定需要缓存的图标文件夹 iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], // 指定symbolId格式 symbolId: 'icon-[dir]-[name]', }) ] } ) }
<script setup lang="ts"> const props = defineProps({ prefix: { type: String, default: "icon", }, name: { type: String, required: false, }, color: { type: String, }, size: { type: String, default: "1em", }, }); const symbolId = computed(() => `#${props.prefix}-${props.name}`); </script> <template> <svg aria-hidden="true" class="svg-icon" :style="'width:' + size + ';height:' + size" > <use :xlink:href="symbolId" :fill="color" /> </svg> </template> <style scoped> .svg-icon { display: inline-block; outline: none; width: 1em; height: 1em; vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */ fill: currentColor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */ overflow: hidden; } </style>
<template>
<el-button type="info"><svg-icon name="bl"/>本地svg</el-button>
</template>
参考:vite-plugin-svg-icons 文档
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
pinia优点:
yarn add pinia
# 或者使用 npm
npm install pinia
创建一个 pinia 实例 (根 store) 并将其传递给应用:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
在 src
文件夹下创建 store
文件夹,并添加 counter.js
文件。
Store
(如 Pinia)是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,state
、getter
和 action
,我们可以假设这些概念相当于组件中的 data
、computed
和 methods
。store
是用 defineStore(name, function | options)
定义的,建议其函数返回的值命名为 use…Store
方便理解name
:必填值且唯一,简单点说就可以理解成是一个命名空间。function | options
:可以是对象或函数形式。state
、getters
和 actions
选项。下面案例以选项模式为例:
在 store
文件夹下创建 counter.js
文件,这个文件就是存有关 counter 的一些相关的数据。
import {defineStore} from 'pinia' /*defineStore 是需要传参数的,其中第一个参数是id,就是一个唯一的值, 简单点说就可以理解成是一个命名空间. 第二个参数就是一个对象,里面有三个模块需要处理,第一个是 state, 第二个是 getters,第三个是 actions。 */ const useCounter = defineStore("counter",{ state:() => ({ count:88, }), getters: { }, actions: { } }) //暴露useCounter这个模块 export default useCounter
在页面中使用:
<template> <div> <el-button>我是 ElButton</el-button> <div>store===>counter.js的count值:{{ counterStore.count }}</div> </div> </template> <script setup> // 引入创建的store import useCounter from "./store/counter"; // 调用store里的方法 const counterStore = useCounter(); console.log("counterStore", counterStore.count); </script> <style scoped></style>
**注意:**在使用时 ,取值时不用和 vuex 一样还要.state,直接.state里面的count值就行了,写法:counterStore.count。
案例需求,点击按钮加一:
我们分别用两种方法取count值,一个解构,一个不解构
<template> <div> <h2>Home Word</h2> <h2>展示pinia的counter的count值: {{ counterStore.count }}</h2> <h2>展示解构出来的pinia的counter的count值: {{ count }}</h2> <el-button @click="addCount">function count+1</el-button> </div> </template> <script setup> // 引入创建的store import useCounter from "./store/counter"; // 调用store里的方法 const counterStore = useCounter(); // 结构count值 const { count } = counterStore; function addCount() { //这里可以直接操作count,修改(写入)store,在vuex还要commit在mutaitions修改数据 counterStore.count++; } </script>
我们发现解构出来的值 失去响应式了。
解决方案:
为了从 store
中提取属性时保持其响应性,你需要使用 storeToRefs()
。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。
<template> <div> <h2>Home Word</h2> <!-- 使用useCounter的实例获取state中的值 --> <h2>展示pinia的counter的count值: {{ counterStore.count }}</h2> <h2>展示解构出来的pinia的counter的count值: {{ count }}</h2> <el-button @click="addCount">function count+1</el-button> </div> </template> <script setup> import { storeToRefs } from "pinia"; // 引入创建的store import useCounter from "./store/counter"; // 调用store里的方法 const counterStore = useCounter(); // 结构count值 const { count } = storeToRefs(counterStore); function addCount() { //这里可以直接操作count,修改(写入)store,在vuex还要commit在mutaitions修改数据 counterStore.count++; } </script>
import { defineStore } from 'pinia' const useUser = defineStore("user", { state: () => ({ name: "why", age: 18, level: 100 }), actions:{ setCurrent () { this.level++ } } }) export default useUser
<template> <div> <h2>Home Word</h2> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <h2>等级: {{ level }}</h2> <el-button @click="updateStore">修改user信息</el-button> <el-button @click="updateStoreLevel">修改level</el-button> </div> </template> <script setup> import useUser from "./store/user"; import { storeToRefs } from "pinia"; const userStore = useUser(); const { name, age, level } = storeToRefs(userStore); function updateStore() { // 方法一:一个个的修改状态 // userStore.name = "zimo"; // userStore.age = 20; // 方法二 :一次性修改多个状态 // userStore.$patch({ // name: "zimo", // age: 20, // }); // 方法三:替换state为新的对象 const oldState = userStore.$state; userStore.$state = { ...userStore.$state, name: "curry", level: 200, }; // 下面会返回true console.log(oldState === userStore.$state); } // 方法四:通过actions修改 const updateStoreLevel = () => { userStore.setCurrent(); }; </script>
新增一个重置按钮:
<el-button @click="resetStore">重置user信息</el-button>
新增一个重置方法:
function resetStore() {
userStore.$reset();
}
getters 类似于 vue 里面的计算属性,可以对已有的数据进行修饰。getters中可以定义接受一个state作为参数的函数,不管调用多少次,getters中的函数只会执行一次,且都会缓存。
// 定义关于counter的store import { defineStore } from 'pinia' const useCounter = defineStore("counter", { state: () => ({ count: 99 }), getters: { // 1.基本使用 doubleCount(state) { return state.count * 2 }, // 2.一个getter引入另外一个getter doubleCountAddOne() { // this是store实例,可以直接使用另一个getter return this.doubleCount + 1 }, // 3.getters也支持返回一个函数 getFriendById(state) { return function(id) { return id } }, // 4.getters中用到别的store中的数据 showMessage(state) { //获取user信息,拿到useUser模块 const userStore = useUser() //拼接信息 return `name:${userStore.name}-count:${state.count}` } }, }) export default useCounter
<template> <div> <!-- 在模板中使用 --> <h2>基本使用:doubleCount: {{ counterStore.doubleCount }}</h2> <h2> 一个getter引入另外一个getter:doubleCountAddOne: {{ counterStore.doubleCountAddOne }} </h2> <h2>函数id-99: {{ counterStore.getFriendById(99) }}</h2> <h2> getters中获取另一个store中的state/getters数据==>showMessage:{{ counterStore.showMessage }} </h2> </div> </template> <script setup> import useCounter from "./store/counter"; const counterStore = useCounter(); // 在js文件中使用 const doubleCount = counterStore.doubleCount; const doubleCountAddOne = counterStore.doubleCountAddOne; const frend = counterStore.getFriendById(99); </script>
**注意:**getters中用别的store中的数据 ,在counter模块中拿user模块的store数据,要引入user模块。
actions
相当于组件中的 methods
。
可以使用 defineStore()
中的 actions
属性定义,并且它们非常适合定义一些业务逻辑。
和 getters
一样,在 action
中可以通过 this
访问整个 store
实例的所有操作。
// 定义关于counter的store import { defineStore } from 'pinia' const useCounter = defineStore("counter", { state: () => ({ count: 99, }), // 定义actions actions: { increment() { this.count++ }, incrementNum(num) { this.count += num } } }) export default useCounter
<template> <div> <h2>Home Word</h2> <h2>doubleCount: {{ counterStore.count }}</h2> <el-button @click="changeState">修改state</el-button> </div> </template> <script setup> import useCounter from "./store/counter"; const counterStore = useCounter(); function changeState() { // 可以通过counterStore对象直接使用 counterStore.increment(); // counterStore.incrementNum(10); } </script>
结合async await 修饰
import { defineStore } from 'pinia' const Login = () => { return new Promise((resolve) => { setTimeout(() => { resolve({ name: 'Rlm', isLogin: true }) }, 3000) }) } const useUser = defineStore("user", { state: () => ({ user: {}, name: "123" }), actions:{ async getLoginInfo() { const result = await Login() this.user = result; } } }) export default useUser
<template>
<div>
<h2>Home Word</h2>
<h2>user: {{ userStore.user }}</h2>
<el-button @click="Login">获取user</el-button>
</div>
</template>
<script setup>
import useUser from "./store/user";
const userStore = useUser();
const Login = () => {
userStore.getLoginInfo();
};
</script>
import { defineStore } from 'pinia' const Login = () => { return new Promise((resolve) => { setTimeout(() => { resolve({ name: 'Rlm', isLogin: true }) }, 3000) }) } const useUser = defineStore("user", { state: () => ({ user: {}, name: "123" }), actions:{ async getLoginInfo() { const result = await Login() this.user = result; this.setName(result.name) }, setName (name) { this.name = name; } } }) export default useUser
$reset() 将会把state所有值 重置回 原始状态。前面2.4 重置state数据有写到。
类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)。
import { defineStore } from 'pinia' const useUser = defineStore("user", { state: () => ({ name: "why", age: 18, level: 100 }), actions:{ setCurrent () { this.level++ } } }) export default useUser
<template> <div> <h2>Home Word</h2> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <h2>等级: {{ level }}</h2> <el-button @click="updateStore">修改user信息</el-button> <el-button @click="updateStoreLevel">修改level</el-button> <el-button @click="resetStore">重置user信息</el-button> </div> </template> <script setup> import useUser from "./store/user"; import { storeToRefs } from "pinia"; const userStore = useUser(); const { name, age, level } = storeToRefs(userStore); userStore.$subscribe((mutation, state) => { // mutation.type // type的三种类型 'direct' | 'patch object' | 'patch function' // 和 cartStore.$id 一样 // mutation.storeId // 'user' // payload只有 mutation.type === 'patch object'的情况下才可用 // mutation.payload // 传递给 cartStore.$patch() 的补丁对象。 console.log(mutation, state); // 每当状态发生变化时,将整个 state 持久化到本地存储。 localStorage.setItem("user", JSON.stringify(state)); }); function updateStore() { // 方法一:一个个的修改状态 订阅 state type 为 'direct' // userStore.name = "zimo"; // userStore.age = 20; // 方法二 :一次性修改多个状态 订阅 state type 为 'patch object' userStore.$patch({ name: "zimo", age: 20, }); // 方法三:替换state为新的对象 订阅 state type 为 'patch function' // const oldState = userStore.$state; // userStore.$state = { // ...userStore.$state, // name: "curry", // level: 200, // }; // 下面会返回true // console.log(oldState === userStore.$state); } // 方法四:通过actions修改 订阅 state type 为 'direct' const updateStoreLevel = () => { userStore.setCurrent(); }; // 重置user信息方法 function resetStore() { userStore.$reset(); } </script>
默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true }
作为第二个参数,以将 state subscription 从当前组件中分离:
userStore.$subscribe( (mutation, state) => { // mutation.type // 'direct' | 'patch object' | 'patch function' // 和 cartStore.$id 一样 // mutation.storeId // 'user' // 只有 mutation.type === 'patch object'的情况下才可用 // mutation.payload // 传递给 cartStore.$patch() 的补丁对象。 console.log(mutation, state); // 每当状态发生变化时,将整个 state 持久化到本地存储。 localStorage.setItem("user", JSON.stringify(state)); }, { detached: true, } );
你可以通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError 允许你在 action 抛出错误或 reject 时执行一个回调函数。这些函数对于追踪运行时错误非常有用,类似于Vue docs 中的这个提示。
import { defineStore } from 'pinia' const Login = () => { return new Promise((resolve) => { setTimeout(() => { resolve({ name: 'Rlm', isLogin: true }) }, 3000) }) } const useUser = defineStore("user", { state: () => ({ user: {}, name: "123", count:1 }), actions:{ async getLoginInfo() { const result = await Login() this.user = result; this.setName(result.name) }, setName (name) { this.name = name; }, testOnAction(id) { this.count++ console.log(this.count) return Promise.resolve('这是testOnAction返回的值') }, testOnAction2(...args) { console.log(...args) return Promise.reject('这是testOnAction222错误返回的值') } } })
<template> <div> <h2>Home Word</h2> <h2>user: {{ userStore.user }}</h2> <h2>name: {{ userStore.name }}</h2> <el-button @click="Login">获取user</el-button> </div> </template> <script setup> import useUser from "./store/user"; const userStore = useUser(); const Login = () => { userStore.getLoginInfo(); }; // 订阅 action const unsubscribe = userStore.$onAction( ({ name, // action 名称 store, // store 实例,类似 `someStore` args, // 传递给 action 的参数数组 after, // 在 action 返回或解决后的钩子 onError, // action 抛出或拒绝的钩子 }) => { // 这将在执行 "store "的 action 之前触发。 console.log(`Start "${name}" with params [${args.join(", ")}].`); if (name === "testOnAction") { // 这将在 action 成功并完全运行后触发。 // 它等待着任何返回的 promise after((reject) => { //这里可以执行一些操作 console.log("订阅 action---after===>", reject); }); } // 如果 action 抛出或返回一个拒绝的 promise,这将触发 onError((error) => { console.log(error); }); } ); // 手动删除监听器 // unsubscribe(); userStore.testOnAction(1); userStore.testOnAction2(1, 2, 3, 45); </script>
npm i pinia-plugin-persist
// 引入pinia
import { createPinia } from 'pinia'
import piniaPluginPersist from 'pinia-plugin-persist'
const pinia = createPinia()
pinia.use(piniaPluginPersist)
app.use(pinia)
import { defineStore } from 'pinia'
export default defineStore('commonStore', {
//开启数据存储
persist: { enabled: true },
state: () => {
return {
info: {}
}
},
})
persist: {
enabled: true,
strategies: [
{
key: 'all', //存储的key值,默认为store名,
storage: localStorage, //存储的位置,默认为sessionStorage
paths: ['info'] //需要存储的state状态,默认为所有
}
]
},
npm i -D sass
添加变量 $bg-color 定义,注意规范变量以 $ 开头
// src/styles/variables.scss
$bg-color:#FFB03E;
导入 SCSS 全局变量文件
// vite.config.ts
css: {
// CSS 预处理器
preprocessorOptions: {
//define global scss variable
scss: {
javascriptEnabled: true,
additionalData: `@use "@/styles/variables.scss" as *;`
}
}
}
<script setup lang="ts"> import { ref } from "vue"; defineProps<{ msg: string }>(); const count = ref(0); </script> <template> <h1>{{ msg }}</h1> <h1 class="box">hello word</h1> <div class="card"> <button type="button" @click="count++">count is {{ count }}</button> </div> <div class="card"> <el-button type="success"><i-ep-SuccessFilled />Success</el-button> <el-button type="info"><i-ep-InfoFilled />Info</el-button> <el-button type="warning"><i-ep-WarningFilled />Warning</el-button> <el-button type="danger"><i-ep-WarnTriangleFilled />Danger</el-button> <el-button type="info"><svg-icon name="bl" />本地 svg</el-button> </div> </template> <style lang="scss" scoped> .read-the-docs { color: #888; } .box { background-color: $bg-color; } </style>
// 导出 variables.scss 文件的变量
:export{
bgColor:$bg-color
}
<script setup lang="ts"> import { ref } from "vue"; import variables from "@/styles/variables.module.scss"; console.log("scss变量颜色===>", variables.bgColor); defineProps<{ msg: string }>(); const count = ref(0); </script> <template> <h1 class="box">hello word</h1> <h1 style="height: 50px" :style="{ 'background-color': variables.bgColor }"> {{ msg }} </h1> <div class="card"> <button type="button" @click="count++">count is {{ count }}</button> </div> <div class="card"> <el-button type="success"><i-ep-SuccessFilled />Success</el-button> <el-button type="info"><i-ep-InfoFilled />Info</el-button> <el-button type="warning"><i-ep-WarningFilled />Warning</el-button> <el-button type="danger"><i-ep-WarnTriangleFilled />Danger</el-button> <el-button type="info"><svg-icon name="bl" />本地 svg</el-button> </div> </template> <style lang="scss" scoped> .read-the-docs { color: #888; } .box { background-color: $bg-color; } </style>
unocss是一个即时的原子CSS引擎,它可以让你用简短的类名来控制元素的样式,而不需要写复杂的CSS代码。
npm install -D unocss
// vite.config.ts
import UnoCSS from 'unocss/vite'
export default {
plugins: [
UnoCSS({ /* options */ }),
],
}
import 'uno.css'
安装启用后,页面上就能看出哪些 class 使用 unocss 提供 (带有虚线),并且能显示类名对应的样式内容。
ctrl + shift + p =>
输入 open Setting
=> 选择 首选项:打开用户设置
添加以下配置
"editor.quickSuggestions": {
"strings": true,
"other": true,
"comments": true,
},
配置完成后,在输入类名时会对 unocss 中存在的 class 进行智能提示。
Vite的环境变量主要是为了区分开发、测试、生产等环境的变量。
.env.development
、.env.production
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE = 'my-vue3-element-admin'
VITE_APP_PORT = 9000
VITE_APP_BASE_API = '/dev-api'
VITE_APP_TITLE = 'my-vue3-element-admin'
VITE_APP_PORT = 9000
VITE_APP_BASE_API = '/prod-api'
新建 src/types/env.d.ts
文件存放环境变量TS类型声明
interface ImportMetaEnv { /** * 应用标题 */ VITE_APP_TITLE: string; /** * 应用端口 */ VITE_APP_PORT: number; /** * API基础路径(反向代理) */ VITE_APP_BASE_API: string; } interface ImportMeta { readonly env: ImportMetaEnv; }
本地开发环境通过 Vite 配置中 server 反向代理解决浏览器跨域问题,生产环境则是可以通过 nginx 配置反向代理 。
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import AutoImport from "unplugin-auto-import/vite"; import Components from "unplugin-vue-components/vite"; import path from "path"; //这个path用到了上面安装的@types/node const pathSrc = path.resolve(__dirname, "src"); // 引入 icon import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; import Icons from "unplugin-icons/vite"; import IconsResolver from "unplugin-icons/resolver"; // svg import { createSvgIconsPlugin } from "vite-plugin-svg-icons"; // https://vitejs.dev/config/ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { const env = loadEnv(mode, process.cwd()); return { // 路径别名 resolve: { alias: { "@": pathSrc, // @代替src }, }, css: { // CSS 预处理器 preprocessorOptions: { //define global scss variable scss: { javascriptEnabled: true, additionalData: `@use "@/styles/variables.scss" as *;`, }, }, }, server: { host: "0.0.0.0", port: Number(env.VITE_APP_PORT), open: true, // 运行是否自动打开浏览器 proxy: { // 反向代理解决跨域 [env.VITE_APP_BASE_API]: { target: 'http://localhost:8989', // 本地接口地址 changeOrigin: true, rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址 }, }, }, plugins: [ vue(), AutoImport({ // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 imports: ["vue"], eslintrc: { enabled: false, // 是否自动生成 eslint 规则,建议生成之后设置 false filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件 }, resolvers: [ // 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式) ElementPlusResolver(), // 自动导入图标组件 IconsResolver({ enabledCollections: ["ep"] }), ], vueTemplate: true, // 是否在 vue 模板中自动导入 dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径 }), Components({ resolvers: [ // 自动导入 Element Plus 组件 ElementPlusResolver(), // 自动注册图标组件 IconsResolver({ enabledCollections: ["ep"], // element-plus图标库,其他图标库 https://icon-sets.iconify.design/ }), ], dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径 }), Icons({ // 自动安装图标库 autoInstall: true, }), // createSvgIconsPlugin({ // // 指定需要缓存的图标文件夹 // iconDirs: [path.resolve(process.cwd(), "src/assets/icons")], // // 指定symbolId格式 // symbolId: "icon-[dir]-[name]", // }), createSvgIconsPlugin({ // 指定需要缓存的图标文件夹 iconDirs: [path.resolve(process.cwd(), "src/assets/icons")], // 指定symbolId格式 symbolId: "icon-[dir]-[name]", /** * 自定义插入位置 'body-last' | 'body-first' * @default: body-last */ inject: "body-last", /** * custom dom id * @default: __svg__icons__dom__ */ customDomId: "__svg__icons__dom__", }), ], }; });
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。