# vue3-admin-test
**Repository Path**: jseven68/vue3-admin-test
## Basic Information
- **Project Name**: vue3-admin-test
- **Description**: 新的vue3练习项目
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2024-05-17
- **Last Updated**: 2024-10-25
## Categories & Tags
**Categories**: Uncategorized
**Tags**: vue3, vite, Element-UI
## README
# Vue 3 + TypeScript + Vite
## 搭建
1. 初始化项目
按照 🍃Vite 官方文档 - 搭建第一个 Vite 项目 说明,执行以下命令完成 vue 、typescirpt 模板项目的初始化
> pnpm create vite my-vue-app --template vue-ts
> my-vue-app 项目名称
> vue-ts: vue + typescript 模板的标识,查看 create-vite 以获取每个模板的更多细节:vue,vue-ts,react,react-ts
2. 安装包
```js
pnpm install
pnpm run dev
```
## 配置别名
1. 配置
> 需要修改 vite.config.ts 文件,添加别名配置
```js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@views': path.resolve(__dirname, './src/views'),
'@utils': path.resolve(__dirname, './src/utils'),
'@store': path.resolve(__dirname, './src/store'),
'@router': path.resolve(__dirname, './src/router'),
'@assets': path.resolve(__dirname, './src/assets'),
'@api': path.resolve(__dirname, './src/api'),
},
},
})
```
2. 安装@types/node
>import path from 'path'编译器报错:TS2307: Cannot find module 'path' or its corresponding type declarations.
本地安装 Node 的 TypeScript 类型描述文件即可解决编译器报错
```js
pnpm install @types/node --save-dev
```
`
3. TypeScript 编译配置
同样还是import path from 'path' 编译报错: TS1259: Module '"path"' can only be default-imported using the 'allowSyntheticDefaultImports' flag
因为 typescript 特殊的 import 方式 , 需要配置允许默认导入的方式,还有路径别名的配置
```json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
},
"allowSyntheticDefaultImports": true // 允许默认导入
}
}
```
## 自动导入
unplugin 自动导入
Element Plus 官方文档中推荐 按需自动导入 的方式,而此需要使用额外的插件 unplugin-auto-import 和 unplugin-vue-components 来导入要使用的组件。所以在整合 Element Plus 之前先了解下自动导入的概念和作用
概念
为了避免在多个页面重复引入 API 或 组件,由此而产生的自动导入插件来节省重复代码和提高开发效率。
插件概念自动导入对象unplugin-auto-import按需自动导入APIref,reactive,watch,computed 等APIunplugin-vue-components按需自动导入组件Element Plus 等三方库和指定目录下的自定义组件.
### 安装
```js
pnpm install unplugin-auto-import unplugin-vue-components -D
```
### 配置
配置完保存,会发现src/types/目录下自动生成了导入文件了, 然后组件使用vue的api和导入src/componets下组件都会自动导入,不需要手动导入了
```ts
// vite.config.ts
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import path from "path";
const pathSrc = path.resolve(__dirname, "src");
plugins: [
AutoImport({
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
imports: ["vue"],
eslintrc: {
enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false ,配置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 规则引入
```ts
"extends": [
"./.eslintrc-auto-import.json"
],
```
### tsconfig.json
自动导入TS类型声明文件引入
```json
{
"include": ["src/**/*.d.ts"]
}
```
### ElementPlus引入
需要完成上面一节的 自动导入 的安装和配置
#### 安装
```js
npm install element-plus
pnpm i -D unplugin-icons // 安装自动导入 Icon 依赖
```
> vite.cconfig.ts 配置,参考官网
```ts
// vite.config.ts
import vue from "@vitejs/plugin-vue";
import { UserConfig, ConfigEnv, loadEnv, defineConfig } from "vite";
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,
}),
],
};
};
```
#### 使用
```vue
Success
Info
```
#### 整合svg
> 通过 vite-plugin-svg-icons 插件整合 Iconfont 第三方图标库实现本地图标
1. 安装
```js
pnpm install -D fast-glob@3.2.11
pnpm install -D vite-plugin-svg-icons@2.0.1
```
2. 创建目录
创建 src/assets/icons 目录 , 放入从 Iconfont 复制的 svg 图标
3. main.ts 引入注册脚本
```js
// src/main.ts
import 'virtual:svg-icons-register';
```
4. vite.config.ts 配置插件
```ts
// 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]',
})
]
}
)
}
```
5. 创建组件
参考项目 src/components/SvgIcon.vue
6. 使用
icon-class="xxx"
```vue
SVG 本地图标
```
## 整合scss
> 安装
```js
pnpm install -D sass
```
> vite.config.ts 配置 全局引入
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [
vue(),
],
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "@/styles/variables.scss";`,
},
},
}
```
> 配置ts文件使用的scss变量
创建 variables.module.scss 文件,vue3中xxx.module.css就是一个css模块文件
```scss
// 导出 variables.scss 文件的变量
:export{
bgColor: $--primary-color // variables.scss 文件中的变量
}
```
使用
```ts
```
## 整合 UnoCSS
> 安装
```js
pnpm install -D unocss
```
> vite.config.ts 配置
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Unocss from 'unocss/vite';
export default defineConfig({
plugins: [
vue(),
UnoCSS(),
],
})
```
> main.ts 配置
```ts
// src/main.ts
// src/main.ts
import 'uno.css'
```
VSCode 安装 UnoCSS 插件
## 整合Pinia
> 安装
```js
pnpm install pinia
```
> main.ts 配置
```ts
// src/main.ts
import { 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');
```
> src/store/index.ts 配置
```ts
// src/store/index.ts
import { defineStore } from 'pinia';
export const useMainStore = defineStore('main', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
});
// 上面是选项式写法,也支持组合式
import { defineStore } from "pinia";
// 组合式
export const useCounterStore = defineStore('counter',()=>{
const count = ref(0)
// 定义其他状态和方法
const increment = () => {
count.value++
}
const double = computed(() => count.value * 2)
return {
count,
increment,
double
}
})
```
> src/views/Home.vue 使用
```vue
{{ mainStore.count }}
增加
```
## 整合环境变量
> Vite 环境变量主要是为了区分开发、测试、生产等环境的变量,详细查看vite文档
### env配置文件
项目根目录新建 .env.development 、.env.production
开发环境变量配置:.env.development
```ts
properties复制代码# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'
```
生产环境变量配置:.env.production
```ts
properties复制代码VITE_APP_TITLE = 'vue3-element-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod-api'
```
### 环境变量智能提示
新建 src/types/env.d.ts文件存放环境变量TS类型声明
```ts
// src/types/env.d.ts
interface ImportMetaEnv {
/**
* 应用标题
*/
VITE_APP_TITLE: string;
/**
* 应用端口
*/
VITE_APP_PORT: number;
/**
* API基础路径(反向代理)
*/
VITE_APP_BASE_API: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
```
### 打包配置
```json
{
"name": "vue3-admin-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite serve --mode development",
"build:prod": "vite build --mode production && vue-tsc --noEmit",
"preview": "vite preview"
},
}
```
## 配置本地代理
```ts
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
server: {
// 允许IP访问
host: "0.0.0.0",
// 应用端口 (默认:3000)
port: Number(env.VITE_APP_PORT),
// 运行是否自动打开浏览器
open: true,
proxy: {
/** 代理前缀为 /dev-api 的请求 */
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
// 接口地址
target: env.VITE_APP_API_URL,
rewrite: (path) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
},
},
},
})
```
## 整合axios
> 安装
```js
pnpm install axios
```
> src/utils/request.ts 配置
```ts
// src/utils/request.ts
/** axios封装 */
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { useUserStore } from '@/store/modules/user';
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json;charset=utf-8' }
});
// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const userStore = useUserStore();
if (userStore.token) {
config.headers.Authorization = userStore.token;
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, msg } = response.data;
// 登录成功
if (code === '00000') {
return response.data;
}
ElMessage.error(msg || '系统出错');
return Promise.reject(new Error(msg || 'Error'));
},
(error: any) => {
if (error.response.data) {
const { code, msg } = error.response.data;
// token 过期,跳转登录页
if (code === 'A0230') {
ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', {
confirmButtonText: '确定',
type: 'warning'
}).then(() => {
localStorage.clear(); // @vueuse/core 自动导入
window.location.href = '/';
});
}else{
ElMessage.error(msg || '系统出错');
}
}
return Promise.reject(error.message);
}
);
// 导出 axios 实例
export default service;
```
## Api 定义
登录接口举例:
### 类型定义
将类型定义复制到 src/api/auth/types.ts 文件中
```ts
/**
* 登录请求参数
*/
export interface LoginData {
/**
* 用户名
*/
username: string;
/**
* 密码
*/
password: string;
}
/**
* 登录响应
*/
export interface LoginResult {
/**
* 访问token
*/
accessToken?: string;
/**
* 过期时间(单位:毫秒)
*/
expires?: number;
/**
* 刷新token
*/
refreshToken?: string;
/**
* token 类型
*/
tokenType?: string;
}
```
### 接口定义
```ts
// src/api/auth/index.ts
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { LoginData, LoginResult } from './types';
/**
* 登录API
*
* @param data {LoginData}
* @returns
*/
export function loginApi(data: LoginData): AxiosPromise {
return request({
url: '/api/v1/auth/login',
method: 'post',
params: data
});
}
```
### 接口调用
```ts
// src/store/modules/user.ts
import { loginApi } from '@/api/auth';
import { LoginData } from '@/api/auth/types';
/**
* 登录调用
*
* @param {LoginData}
* @returns
*/
function login(loginData: LoginData) {
return new Promise((resolve, reject) => {
loginApi(loginData)
.then(response => {
const { tokenType, accessToken } = response.data;
token.value = tokenType + ' ' + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx
resolve();
})
.catch(error => {
reject(error);
});
});
}
```
## 整合路由
1. 安装
> npm install vue-router@next
### 路由守卫
```ts
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { routes } from './routes';
import { useUserStore } from '@/store/modules/user';
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
if (to.path === '/login') {
// 如果是登录页面,直接放行
next();
}
// 获取token
const token = userStore.token;
// 如果token存在,直接放行
if (token) {
next();
} else {
// 如果token不存在,跳转到登录页面
next('/login');
}
});
export default router;
```
### 全局注册
```ts
// main.ts
import router from "@/router";
app.use(router).mount('#app')
```
## 按钮权限
通过自定义指令方式
```ts
// src/directive/permission/index.ts
import { useUserStoreHook } from '@/store/modules/user';
import { Directive, DirectiveBinding } from 'vue';
/**
* 按钮权限
*/
export const hasPerm: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
// 「超级管理员」拥有所有的按钮权限
const { roles, perms } = useUserStoreHook().user;
if (roles.includes('ROOT')) {
return true;
}
// 「其他角色」按钮权限校验
const { value } = binding;
if (value) {
const requiredPerms = value; // DOM绑定需要的按钮权限标识
const hasPerm = perms?.some(perm => {
return requiredPerms.includes(perm);
});
if (!hasPerm) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
);
}
}
};
```
### 全局注册
```ts
// src/directive/index.ts
import type { App } from 'vue';
import { hasPerm } from './permission';
// 全局注册 directive 方法
export function setupDirective(app: App) {
// 使 v-hasPerm 在所有组件中都可用
app.directive('hasPerm', hasPerm);
}
```
```ts
// src/main.ts
import { setupDirective } from '@/directive';
const app = createApp(App);
// 全局注册 自定义指令(directive)
setupDirective(app);
```
### 使用
```html
新增
修改
删除
```
`