Vue3+ Vite + Element-Plus + TypeScript 从0到1搭建企业级后台管理系统(前后端开源)

306,928 阅读12分钟
专栏: 
vue3-element-admin

我正在参加「掘金·启航计划」

vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 + Element Plus 版本的后台管理前端解决方案,技术栈为 Vue 3 + Vite 6 + TypeScript + Element Plus + Pinia + Vue Router 等当前主流框架。

相较于其他管理前端框架,vue3-element-admin 的优势在于一有一无 (配套后端、复杂封装):

  • 配套完整 Java 后端 权限管理接口,开箱即用,提供 OpenAPI 文档 搭配 Apifox 生成 Node、Python、Go等其他服务端代码;

  • 完全基于 vue-element-admin 升级的 Vue3 版本,未在框架(Element Plus)的组件再过渡封装,上手成本低和扩展性高。

前言

本篇是 vue3-element-admin v2.x 版本从 0 到 1,相较于 v1.x 版本 主要增加了对原子CSS(UnoCSS)、按需自动导入、暗黑模式的支持。

项目源码

前后端源码GiteeGithub
前端vue3-element-adminvue3-element-admin
后端youlai-bootyoulai-boot

项目预览

在线预览

vue.youlai.tech/

首页控制台

明亮模式

暗黑模式

接口文档

接口文档

权限管理系统

用户管理角色管理
菜单管理字典管理

扩展生态

youlai-mall 有来开源商城:Spring Cloud微服务+ vue3-element-admin+uni-app

youlai-mall 商品管理mall-app 移动端

项目指南

  • 功能清单

  • 技术栈
技术栈描述官网
Vue3渐进式 JavaScript 框架cn.vuejs.org/
Element Plus基于 Vue 3,面向设计师和开发者的组件库element-plus.gitee.io/zh-CN/
Vite前端开发与构建工具cn.vitejs.dev/
TypeScript微软新推出的一种语言,是 JavaScript 的超集www.tslang.cn/
Pinia新一代状态管理工具pinia.vuejs.org/
Vue RouterVue.js 的官方路由router.vuejs.org/zh/
wangEditorTypescript 开发的 Web 富文本编辑器www.wangeditor.com/
Echarts一个基于 JavaScript 的开源可视化图表库echarts.apache.org/zh/
vue-i18nVue 国际化多语言插件vue-i18n.intlify.dev/
VueUse基于Vue组合式API的实用工具集(类比HuTool工具)www.vueusejs.com/

环境准备

名称备注
开发工具VSCode 下载-
运行环境Node 16+ 下载image-20230224222640120
VSCode插件(必装)Vue - Official image.png

vite 项目初始化

按照 🍃Vite 官方文档 - 搭建第一个 Vite 项目 说明,执行以下命令完成 vuetypescirpt 模板项目的初始化

bash
代码解读
复制代码
npm init vite@latest vue3-element-admin --template vue-ts
  • vue3-element-admin: 自定义的项目名称

  • vue-tsvue + typescript 模板的标识,查看 create-vite 以获取每个模板的更多细节:vue,vue-ts,react,react-ts

初始化完成项目位于 D:\project\demo\vue3-element-admin , 使用 VSCode 导入,执行以下命令启动:

bash
代码解读
复制代码
npm install npm run dev

浏览器访问 localhost:5173 预览

src 路径别名配置

相对路径别名配置,使用 @ 代替 src

Vite 配置

配置 vite.config.ts ,截图展示关键代码,完整代码移步:vite.config.ts

安装@types/node

import path from 'path'编译器报错:TS2307: Cannot find module 'path' or its corresponding type declarations.

本地安装 Node 的 TypeScript 类型描述文件即可解决编译器报错

bash
代码解读
复制代码
npm install @types/node --save-dev

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 // 允许默认导入 } }

路径别名使用

typescript
代码解读
复制代码
// src/App.vue import HelloWorld from '/src/components/HelloWorld.vue' import HelloWorld from '@/components/HelloWorld.vue'

unplugin 自动导入

Element Plus 官方文档中推荐 按需自动导入 的方式,而此需要使用额外的插件 unplugin-auto-importunplugin-vue-components 来导入要使用的组件。所以在整合 Element Plus 之前先了解下自动导入的概念和作用

概念

为了避免在多个页面重复引入 API组件,由此而产生的自动导入插件来节省重复代码和提高开发效率。

插件概念自动导入对象
unplugin-auto-import按需自动导入APIref,reactive,watch,computed 等API
unplugin-vue-components按需自动导入组件Element Plus 等三方库和指定目录下的自定义组件

看下自动导入插件未使用和使用的区别:

插件名未使用自动导入使用自动导入
unplugin-auto-import
unplugin-vue-components

安装插件依赖

bash
代码解读
复制代码
npm install -D unplugin-auto-import unplugin-vue-components

vite.config.ts - 自动导入配置

先创建好 /src/types 目录用于存放自动导入函数和组件的TS类型声明文件,再进行自动导入配置

下面只贴关键配置代码,完整代码移步:vite.config.ts

typescript
代码解读
复制代码
// 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 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 规则引入

javascript
代码解读
复制代码
"extends": [ "./.eslintrc-auto-import.json" ],

tsconfig.json - 自动导入TS类型声明文件引入

json
代码解读
复制代码
{ "include": ["src/**/*.d.ts"] }

自动导入效果

运行项目 npm run dev 自动

整合 Element Plus

参考: element plus 按需自动导入

需要完成上面一节的 自动导入 的安装和配置

安装 Element Plus

bash
代码解读
复制代码
npm install element-plus

安装自动导入 Icon 依赖

bash
代码解读
复制代码
npm i -D unplugin-icons

vite.config.ts 配置

参考: element-plus-best-practices - vite.config.ts

typescript
代码解读
复制代码
// 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, }), ], }; };

示例代码

html
代码解读
复制代码
<!-- src/components/HelloWorld.vue --> <div> <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>

效果预览

整合 SVG 图标

通过 vite-plugin-svg-icons 插件整合 Iconfont 第三方图标库实现本地图标

参考: vite-plugin-svg-icons 安装文档

安装依赖

bash
代码解读
复制代码
npm install -D fast-glob@3.2.11 npm install -D vite-plugin-svg-icons@2.0.1

创建 src/assets/icons 目录 , 放入从 Iconfont 复制的 svg 图标

main.ts 引入注册脚本

typescript
代码解读
复制代码
// src/main.ts import 'virtual:svg-icons-register';

vite.config.ts 配置插件

typescript
代码解读
复制代码
// 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]', }) ] } ) }

SVG 组件封装

html
代码解读
复制代码
<!-- src/components/SvgIcon/index.vue --> <script setup lang="ts"> const props = defineProps({ prefix: { type: String, default: "icon", }, iconClass: { type: String, required: false, }, color: { type: String, }, size: { type: String, default: "1em", }, }); const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`); </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>

组件使用

html
代码解读
复制代码
<!-- src/components/HelloWorld.vue --> <template> <el-button type="info"><svg-icon icon-class="block"/>SVG 本地图标</el-button> </template>

整合 SCSS

一款CSS预处理语言,SCSS 是 Sass 3 引入新的语法,其语法完全兼容 CSS3,并且继承了 Sass 的强大功能。

安装依赖

bash
代码解读
复制代码
npm i -D sass

创建 variables.scss 变量文件,添加变量 $bg-color 定义,注意规范变量以 $ 开头

scss
代码解读
复制代码
// src/styles/variables.scss $bg-color:#242424;

Vite 配置导入 SCSS 全局变量文件

typescript
代码解读
复制代码
// vite.config.ts css: { // CSS 预处理器 preprocessorOptions: { //define global scss variable scss: { javascriptEnabled: true, additionalData: `@use "@/styles/variables.scss" as *;` } } }

style 标签使用SCSS全局变量

html
代码解读
复制代码
<!-- src/components/HelloWorld.vue --> <template> <div class="box" /> </template> <style lang="scss" scoped> .box { width: 100px; height: 100px; background-color: $bg-color; } </style>

上面导入的 SCSS 全局变量在 TypeScript 不生效的,需要创建一个以 .module.scss 结尾的文件

scss
代码解读
复制代码
// src/styles/variables.module.scss // 导出 variables.scss 文件的变量 :export{ bgColor:$bg-color }

TypeScript 使用 SCSS 全局变量

html
代码解读
复制代码
<!-- src/components/HelloWorld.vue --> <script setup lang="ts"> import variables from "@/styles/variables.module.scss"; console.log(variables.bgColor) </script> <template> <div style="width:100px;height:100px" :style="{ 'background-color': variables.bgColor }" /> </template>

整合 UnoCSS

UnoCSS 是一个具有高性能且极具灵活性的即时原子化 CSS 引擎 。

参考:Vite 安装 UnoCSS 官方文档

安装依赖

bash
代码解读
复制代码
npm install -D unocss

vite.config.ts 配置

typescript
代码解读
复制代码
// vite.config.ts import UnoCSS from 'unocss/vite' export default { plugins: [ UnoCSS({ /* options */ }), ], }

main.ts 引入 uno.css

typescript
代码解读
复制代码
// src/main.ts import 'uno.css'

VSCode 安装 UnoCSS 插件

再看下具体使用方式和实际效果:

代码效果
image-20230222220856251

如果UnoCSS 插件智能提示不生效,请参考:VSCode插件UnoCSS智能提示不生效解决

整合 Pinia

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。

参考:Pinia 官方文档

安装依赖

bash
代码解读
复制代码
npm install pinia

main.ts 引入 pinia

typescript
代码解读
复制代码
// src/main.ts import { createPinia } from "pinia"; import App from "./App.vue"; createApp(App).use(createPinia()).mount("#app");

定义 Store

根据 Pinia 官方文档-核心概念 描述 ,Store 定义分为选项式组合式 , 先比较下两种写法的区别:

选项式 Option Store组合式 Setup Store

至于如何选择,官方给出的建议 :选择你觉得最舒服的那一个就好

这里选择组合式,新建文件 src/store/counter.ts

typescript
代码解读
复制代码
// src/store/counter.ts import { defineStore } from "pinia"; export const useCounterStore = defineStore("counter", () => { // ref变量 → state 属性 const count = ref(0); // computed计算属性 → getters const double = computed(() => { return count.value * 2; }); // function函数 → actions function increment() { count.value++; } return { count, double, increment }; });

父组件

html
代码解读
复制代码
<!-- src/App.vue --> <script setup lang="ts"> import HelloWorld from "@/components/HelloWorld.vue"; import { useCounterStore } from "@/store/counter"; const counterStore = useCounterStore(); </script> <template> <h1 class="text-3xl">vue3-element-admin-父组件</h1> <el-button type="primary" @click="counterStore.increment">count++</el-button> <HelloWorld /> </template>

子组件

html
代码解读
复制代码
<!-- src/components/HelloWorld.vue --> <script setup lang="ts"> import { useCounterStore } from "@/store/counter"; const counterStore = useCounterStore(); </script> <template> <el-card class="text-left text-white border-white border-1 border-solid mt-10 bg-[#242424]" > <template #header> 子组件 HelloWorld.vue</template> <el-form> <el-form-item label="数字:"> {{ counterStore.count }}</el-form-item> <el-form-item label="加倍:"> {{ counterStore.double }}</el-form-item> </el-form> </el-card> </template>

效果预览

环境变量

Vite 环境变量主要是为了区分开发、测试、生产等环境的变量

参考: Vite 环境变量配置官方文档

env配置文件

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

  • 开发环境变量配置:.env.development

    properties
    代码解读
    复制代码
    # 变量必须以 VITE_ 为前缀才能暴露给外部读取 VITE_APP_TITLE = 'vue3-element-admin' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/dev-api'
  • 生产环境变量配置:.env.production

    properties
    代码解读
    复制代码
    VITE_APP_TITLE = 'vue3-element-admin' VITE_APP_PORT = 3000 VITE_APP_BASE_API = '/prod-api'

环境变量智能提示

新建 src/types/env.d.ts文件存放环境变量TS类型声明

typescript
代码解读
复制代码
// 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; }

使用自定义环境变量就会有智能提示,环境变量的读取和使用请看下一节的跨域处理中的 vite.config.ts的配置。

反向代理解决跨域

跨域原理

浏览器同源策略: 协议、域名和端口都相同是同源,浏览器会限制非同源请求读取响应结果。

本地开发环境通过 Vite 配置反向代理解决浏览器跨域问题,生产环境则是通过 nginx 配置反向代理 。

vite.config.ts 配置代理

表面肉眼看到的请求地址: http://localhost:3000/dev-api/api/v1/users/me

真实访问的代理目标地址: http://vapi.youlai.tech/api/v1/users/me

整合 Axios

Axios 基于promise可以用于浏览器和node.js的网络请求库

参考: Axios 官方文档

安装依赖

bash
代码解读
复制代码
npm install axios

Axios 工具类封装

typescript
代码解读
复制代码
// src/utils/request.ts import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { useUserStoreHook } 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 = useUserStoreHook(); 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;

登录接口实战

访问 vue3-element-admin 在线接口文档, 查看登录接口请求参数和响应数据类型

点击 生成代码 获取登录响应数据 TypeScript 类型定义

将类型定义复制到 src/api/auth/types.ts 文件中

typescript
代码解读
复制代码
/** * 登录请求参数 */ export interface LoginData { /** * 用户名 */ username: string; /** * 密码 */ password: string; } /** * 登录响应 */ export interface LoginResult { /** * 访问token */ accessToken?: string; /** * 过期时间(单位:毫秒) */ expires?: number; /** * 刷新token */ refreshToken?: string; /** * token 类型 */ tokenType?: string; }

登录 API 定义

typescript
代码解读
复制代码
// 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<LoginResult> { return request({ url: '/api/v1/auth/login', method: 'post', params: data }); }

登录 API 调用

typescript
代码解读
复制代码
// 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<void>((resolve, reject) => { loginApi(loginData) .then(response => { const { tokenType, accessToken } = response.data; token.value = tokenType + ' ' + accessToken; // Bearer eyJhbGciOiJIUzI1NiJ9.xxx.xxx resolve(); }) .catch(error => { reject(error); }); }); }

vue-router 动态路由

安装 vue-router

bash
代码解读
复制代码
npm install vue-router@next

路由实例

创建路由实例,顺带初始化静态路由,而动态路由需要用户登录,根据用户拥有的角色进行权限校验后进行初始化

typescript
代码解读
复制代码
// src/router/index.ts import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; export const Layout = () => import('@/layout/index.vue'); // 静态路由 export const constantRoutes: RouteRecordRaw[] = [ { path: '/redirect', component: Layout, meta: { hidden: true }, children: [ { path: '/redirect/:path(.*)', component: () => import('@/views/redirect/index.vue') } ] }, { path: '/login', component: () => import('@/views/login/index.vue'), meta: { hidden: true } }, { path: '/', component: Layout, redirect: '/dashboard', children: [ { path: 'dashboard', component: () => import('@/views/dashboard/index.vue'), name: 'Dashboard', meta: { title: 'dashboard', icon: 'homepage', affix: true } } ] } ]; /** * 创建路由 */ const router = createRouter({ history: createWebHashHistory(), routes: constantRoutes as RouteRecordRaw[], // 刷新时,滚动条位置还原 scrollBehavior: () => ({ left: 0, top: 0 }) }); /** * 重置路由 */ export function resetRouter() { router.replace({ path: '/login' }); location.reload(); } export default router;

全局注册路由实例

typescript
代码解读
复制代码
// main.ts import router from "@/router"; app.use(router).mount('#app')

动态权限路由

路由守卫 src/permission.ts ,获取当前登录用户的角色信息进行动态路由的初始化

最终调用 permissionStore.generateRoutes(roles) 方法生成动态路由

typescript
代码解读
复制代码
// src/store/modules/permission.ts import { listRoutes } from '@/api/menu'; export const usePermissionStore = defineStore('permission', () => { const routes = ref<RouteRecordRaw[]>([]); function setRoutes(newRoutes: RouteRecordRaw[]) { routes.value = constantRoutes.concat(newRoutes); } /** * 生成动态路由 * * @param roles 用户角色集合 * @returns */ function generateRoutes(roles: string[]) { return new Promise<RouteRecordRaw[]>((resolve, reject) => { // 接口获取所有路由 listRoutes() .then(({ data: asyncRoutes }) => { // 根据角色获取有访问权限的路由 const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles); setRoutes(accessedRoutes); resolve(accessedRoutes); }) .catch(error => { reject(error); }); }); } // 导出 store 的动态路由数据 routes return { routes, setRoutes, generateRoutes }; });

接口获取得到的路由数据

根据路由数据 (routes)生成菜单的关键代码

src/layout/componets/Sidebar/index.vuesrc/layout/componets/Sidebar/SidebarItem.vue
image-20230326145836872

按钮权限

除了 Vue 内置的一系列指令 (比如 v-modelv-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives),以下就通过自定义指令的方式实现按钮权限控制。

参考:Vue 官方文档-自定义指令

自定义指令

typescript
代码解读
复制代码
// 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(); 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']\"" ); } } };

全局注册自定义指令

typescript
代码解读
复制代码
// src/directive/index.ts import type { App } from 'vue'; import { hasPerm } from './permission'; // 全局注册 directive 方法 export function setupDirective(app: App<Element>) { // 使 v-hasPerm 在所有组件中都可用 app.directive('hasPerm', hasPerm); }
typescript
代码解读
复制代码
// src/main.ts import { setupDirective } from '@/directive'; const app = createApp(App); // 全局注册 自定义指令(directive) setupDirective(app);

组件使用自定义指令

html
代码解读
复制代码
// src/views/system/user/index.vue <el-button v-hasPerm="['sys:user:add']">新增</el-button> <el-button v-hasPerm="['sys:user:delete']">删除</el-button>

国际化

国际化分为两个部分,Element Plus 框架国际化(官方提供了国际化方式)和自定义国际化(通过 vue-i18n 国际化插件)

Element Plus 国际化

简单的使用方式请参考 Element Plus 官方文档-国际化示例,以下介绍 vue3-element-admin 整合 pinia 实现国际化语言切换。

Element Plus 提供了一个 Vue 组件 ConfigProvider 用于全局配置国际化的设置。

html
代码解读
复制代码
<!-- src/App.vue --> <script setup lang="ts"> import { ElConfigProvider } from 'element-plus'; import { useAppStore } from '@/store/modules/app'; const appStore = useAppStore(); </script> <template> <el-config-provider :locale="appStore.locale" > <router-view /> </el-config-provider> </template>

定义 store

typescript
代码解读
复制代码
// src/store/modules/app.ts import { defineStore } from 'pinia'; import { useStorage } from '@vueuse/core'; import defaultSettings from '@/settings'; // 导入 Element Plus 中英文语言包 import zhCn from 'element-plus/es/locale/lang/zh-cn'; import en from 'element-plus/es/locale/lang/en'; // setup export const useAppStore = defineStore('app', () => { const language = useStorage('language', defaultSettings.language); /** * 根据语言标识读取对应的语言包 */ const locale = computed(() => { if (language?.value == 'en') { return en; } else { return zhCn; } }); /** * 切换语言 */ function changeLanguage(val: string) { language.value = val; } return { language, locale, changeLanguage }; });

切换语言组件调用

html
代码解读
复制代码
<!-- src/components/LangSelect/index.vue --> <script setup lang="ts"> import { useI18n } from 'vue-i18n'; import SvgIcon from '@/components/SvgIcon/index.vue'; import { useAppStore } from '@/store/modules/app'; const appStore = useAppStore(); const { locale } = useI18n(); function handleLanguageChange(lang: string) { locale.value = lang; appStore.changeLanguage(lang); if (lang == 'en') { ElMessage.success('Switch Language Successful!'); } else { ElMessage.success('切换语言成功!'); } } </script> <template> <el-dropdown trigger="click" @command="handleLanguageChange"> <div> <svg-icon icon-class="language" /> </div> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :disabled="appStore.language === 'zh-cn'" command="zh-cn" > 中文 </el-dropdown-item> <el-dropdown-item :disabled="appStore.language === 'en'" command="en"> English </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </template>

Element Plus 分页组件看下国际化的效果

vue-i18n 自定义国际化

i18n 英文全拼 internationalization ,国际化的意思,英文 i 和 n 中间18个英文字母

参考:vue-i18n 官方文档 - installation

安装 vue-i18n

bash
代码解读
复制代码
npm install vue-i18n@9

自定义语言包

创建 src/lang/package 语言包目录,存放自定义的语言文件

中文语言包 zh-cn.ts英文语言包 en.ts

创建 i18n 实例

typescript
代码解读
复制代码
// src/lang/index.ts import { createI18n } from 'vue-i18n'; import { useAppStore } from '@/store/modules/app'; const appStore = useAppStore(); // 本地语言包 import enLocale from './package/en'; import zhCnLocale from './package/zh-cn'; const messages = { 'zh-cn': { ...zhCnLocale }, en: { ...enLocale } }; // 创建 i18n 实例 const i18n = createI18n({ legacy: false, locale: appStore.language, messages: messages }); // 导出 i18n 实例 export default i18n;

i18n 全局注册

typescript
代码解读
复制代码
// main.ts // 国际化 import i18n from '@/lang/index'; app.use(i18n).mount('#app');

登录页面国际化使用

$t 是 i18n 提供的根据 key 从语言包翻译对应的 value 方法

html
代码解读
复制代码
<span>{{ $t("login.title") }}</span>

在登录页面 src/view/login/index.vue 查看如何使用

效果预览

暗黑模式

Element Plus 2.2.0 版本开始支持暗黑模式,启用方式参考 Element Plus 官方文档 - 暗黑模式, 官方也提供了示例 element-plus-vite-starter 模版

这里根据官方文档和示例讲述 vue3-element-admin 是如何使用 VueUse 的 useDark 方法实现暗黑模式的动态切换。

导入 Element Plus 暗黑模式变量

typescript
代码解读
复制代码
// src/main.ts import 'element-plus/theme-chalk/dark/css-vars.css'

切换暗黑模式设置

html
代码解读
复制代码
<!-- src/layout/components/Settings/index.vue --> <script setup lang="ts"> import IconEpSunny from '~icons/ep/sunny'; import IconEpMoon from '~icons/ep/moon'; /** * 暗黑模式 */ const settingsStore = useSettingsStore(); const isDark = useDark(); const toggleDark = () => useToggle(isDark); </script> <template> <div class="settings-container"> <h3 class="text-base font-bold">项目配置</h3> <el-divider>主题</el-divider> <div class="flex justify-center" @click.stop> <el-switch v-model="isDark" @change="toggleDark" inline-prompt :active-icon="IconEpMoon" :inactive-icon="IconEpSunny" active-color="var(--el-fill-color-dark)" inactive-color="var(--el-color-primary)" /> </div> </div> </template>

自定义变量

除了 Element Plus 组件样式之外,应用中还有很多自定义的组件和样式,像这样的:

应对自定义组件样式实现暗黑模式步骤如下:

新建 src/styles/dark.scss

scss
代码解读
复制代码
html.dark { /* 修改自定义元素的样式 */ .navbar { background-color: #141414; } }

在 Element Plus 的样式之后导入它

typescript
代码解读
复制代码
// main.ts import 'element-plus/theme-chalk/dark/css-vars.css' import '@/styles/dark.scss';

效果预览

组件封装

wangEditor 富文本

参考: wangEditor 官方文档

安装 wangEditor

bash
代码解读
复制代码
npm install @wangeditor/editor @wangeditor/editor-for-vue@next

wangEditor 组件封装

html
代码解读
复制代码
<!-- src/components/WangEditor/index.vue --> <template> <div style="border: 1px solid #ccc"> <!-- 工具栏 --> <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" style="border-bottom: 1px solid #ccc" :mode="mode" /> <!-- 编辑器 --> <Editor :defaultConfig="editorConfig" v-model="defaultHtml" @onChange="handleChange" style="height: 500px; overflow-y: hidden" :mode="mode" @onCreated="handleCreated" /> </div> </template> <script setup lang="ts"> import { Editor, Toolbar } from "@wangeditor/editor-for-vue"; // API 引用 import { uploadFileApi } from "@/api/file"; const props = defineProps({ modelValue: { type: [String], default: "", }, }); const emit = defineEmits(["update:modelValue"]); const defaultHtml = useVModel(props, "modelValue", emit); const editorRef = shallowRef(); // 编辑器实例,必须用 shallowRef const mode = ref("default"); // 编辑器模式 const toolbarConfig = ref({}); // 工具条配置 // 编辑器配置 const editorConfig = ref({ placeholder: "请输入内容...", MENU_CONF: { uploadImage: { // 自定义图片上传 async customUpload(file: any, insertFn: any) { uploadFileApi(file).then((response) => { const url = response.data.url; insertFn(url); }); }, }, }, }); const handleCreated = (editor: any) => { editorRef.value = editor; // 记录 editor 实例,重要! }; function handleChange(editor: any) { emit("update:modelValue", editor.getHtml()); } // 组件销毁时,也及时销毁编辑器 onBeforeUnmount(() => { const editor = editorRef.value; if (editor == null) return; editor.destroy(); }); </script> <style src="@wangeditor/editor/dist/css/style.css"></style>

使用案例

html
代码解读
复制代码
<!-- wangEditor富文本编辑器示例 --> <script setup lang="ts"> import Editor from '@/components/WangEditor/index.vue'; const value = ref('初始内容'); </script> <template> <div class="app-container"> <editor v-model="value" style="height: 600px" /> </div> </template>

效果预览

Echarts 图表

参考:📊 Echarts 官方示例

安装 Echarts

bash
代码解读
复制代码
npm install echarts

组件封装

html
代码解读
复制代码
<!-- src/views/dashboard/components/Chart/BarChart.vue --> <template> <el-card> <template #header> 线 + 柱混合图 </template> <div :id="id" :class="className" :style="{ height, width }" /> </el-card> </template> <script setup lang="ts"> import * as echarts from 'echarts'; const props = defineProps({ id: { type: String, default: 'barChart' }, className: { type: String, default: '' }, width: { type: String, default: '200px', required: true }, height: { type: String, default: '200px', required: true } }); const options = { grid: { left: '2%', right: '2%', bottom: '10%', containLabel: true }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross', crossStyle: { color: '#999' } } }, legend: { x: 'center', y: 'bottom', data: ['收入', '毛利润', '收入增长率', '利润增长率'], textStyle: { color: '#999' } }, xAxis: [ { type: 'category', data: ['浙江', '北京', '上海', '广东', '深圳'], axisPointer: { type: 'shadow' } } ], yAxis: [ { type: 'value', min: 0, max: 10000, interval: 2000, axisLabel: { formatter: '{value} ' } }, { type: 'value', min: 0, max: 100, interval: 20, axisLabel: { formatter: '{value}%' } } ], series: [ { name: '收入', type: 'bar', data: [7000, 7100, 7200, 7300, 7400], barWidth: 20, itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#83bff6' }, { offset: 0.5, color: '#188df0' }, { offset: 1, color: '#188df0' } ]) } }, { name: '毛利润', type: 'bar', data: [8000, 8200, 8400, 8600, 8800], barWidth: 20, itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#25d73c' }, { offset: 0.5, color: '#1bc23d' }, { offset: 1, color: '#179e61' } ]) } }, { name: '收入增长率', type: 'line', yAxisIndex: 1, data: [60, 65, 70, 75, 80], itemStyle: { color: '#67C23A' } }, { name: '利润增长率', type: 'line', yAxisIndex: 1, data: [70, 75, 80, 85, 90], itemStyle: { color: '#409EFF' } } ] }; onMounted(() => { // 图表初始化 const chart = echarts.init( document.getElementById(props.id) as HTMLDivElement ); chart.setOption(options); // 大小自适应 window.addEventListener('resize', () => { chart.resize(); }); }); </script>

组件使用

html
代码解读
复制代码
<script setup lang="ts"> import BarChart from './components/BarChart.vue'; </script> <template> <BarChart id="barChart" height="400px"width="300px" /> </template>

效果预览

图标选择器

组件封装

html
代码解读
复制代码
<!-- src/components/IconSelect/index.vue --> <script setup lang="ts"> const props = defineProps({ modelValue: { type: String, require: false } }); const emit = defineEmits(['update:modelValue']); const inputValue = toRef(props, 'modelValue'); const visible = ref(false); // 弹窗显示状态 const iconNames: string[] = []; // 所有的图标名称集合 const filterValue = ref(''); // 筛选的值 const filterIconNames = ref<string[]>([]); // 过滤后的图标名称集合 const iconSelectorRef = ref(null); /** * 加载 ICON */ function loadIcons() { const icons = import.meta.glob('../../assets/icons/*.svg'); for (const icon in icons) { const iconName = icon.split('assets/icons/')[1].split('.svg')[0]; iconNames.push(iconName); } filterIconNames.value = iconNames; } /** * 筛选图标 */ function handleFilter() { if (filterValue.value) { filterIconNames.value = iconNames.filter(iconName => iconName.includes(filterValue.value) ); } else { filterIconNames.value = iconNames; } } /** * 选择图标 */ function handleSelect(iconName: string) { emit('update:modelValue', iconName); visible.value = false; } /** * 点击容器外的区域关闭弹窗 VueUse onClickOutside */ onClickOutside(iconSelectorRef, () => (visible.value = false)); onMounted(() => { loadIcons(); }); </script> <template> <div class="iconselect-container" ref="iconSelectorRef"> <el-input v-model="inputValue" readonly @click="visible = !visible" placeholder="点击选择图标" > <template #prepend> <svg-icon :icon-class="inputValue" /> </template> </el-input> <el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" width="400" > <template #reference> <div @click="visible = !visible" class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]" > <i-ep-caret-top v-show="visible"></i-ep-caret-top> <i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom> </div> </template> <!-- 下拉选择弹窗 --> <el-input class="p-2" v-model="filterValue" placeholder="搜索图标" clearable @input="handleFilter" /> <el-divider border-style="dashed" /> <el-scrollbar height="300px"> <ul class="icon-list"> <li class="icon-item" v-for="(iconName, index) in filterIconNames" :key="index" @click="handleSelect(iconName)" > <el-tooltip :content="iconName" placement="bottom" effect="light"> <svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" /> </el-tooltip> </li> </ul> </el-scrollbar> </el-popover> </div> </template>

组件使用

html
代码解读
复制代码
<!-- src/views/demo/IconSelect.vue --> <script setup lang="ts"> const iconName = ref('edit'); </script> <template> <div class="app-container"> <icon-select v-model="iconName" /> </div> </template>

效果预览

规范配置

代码统一规范

【vue3-element-admin】ESLint+Prettier+Stylelint+EditorConfig 约束和统一前端代码规范

  • Eslint: JavaScript 语法规则和代码风格检查;
  • Stylelint: CSS 统一规范和代码检测;
  • Prettier:全局代码格式化。

Git 提交规范

【vue3-element-admin】Husky + Lint-staged + Commitlint + Commitizen + cz-git 配置 Git 提交规范

  • Husky + Lint-staged 整合实现 Git 提交前代码规范检测/格式化;
  • Husky + Commitlint + Commitizen + cz-git 整合实现生成规范化且高度自定义的 Git commit message。

启动部署

项目启动

bash
代码解读
复制代码
# 安装 pnpm npm install pnpm -g # 安装依赖 pnpm install # 项目运行 pnpm run dev

项目部署

bash
代码解读
复制代码
# 项目打包 pnpm run build:prod

生成的静态文件在工程根目录 dist 文件夹

FAQ

1: defineProps is not defined

  • 问题描述

    'defineProps' is not defined.eslint no-undef

  • 解决方案

    根据 Eslint 官方解决方案描述,解析器使用 vue-eslint-parser v9.0.0 + 版本

    安装 vue-eslint-parser 解析器

    bash
    代码解读
    复制代码
    npm install -D vue-eslint-parser

    .eslintrc.js 关键配置( v9.0.0 及以上版本无需配置编译宏 vue/setup-compiler-macros)如下 :

    bash
    代码解读
    复制代码
    parser: 'vue-eslint-parser', extends: [ 'eslint:recommended', // ... ],

    重启 VSCode 已无报错提示

2: Vite 首屏加载慢(白屏久)

  • 问题描述

    Vite 项目启动很快,但首次打开界面加载慢?

    参考文章:为什么有人说 vite 快,有人却说 vite 慢

    vite 启动时,并不像 webpack 那样做一个全量的打包构建,所以启动速度非常快。启动以后,浏览器发起请求时, Dev Server 要把请求需要的资源发送给浏览器,中间需要经历预构建、对请求文件做路径解析、加载源文件、对源文件做转换,然后才能把内容返回给浏览器,这个时间耗时蛮久的,导致白屏时间较长。

    解决方案升级 vite 4.3 版本 github.com/vitejs/vite…

结语

本篇从项目介绍、环境准备、VSCode 的代码规范配置 、整合各种框架 、再到最后的启动部署,完整讲述如何基于 Vue3 + Vite4 + TypeScript + Element Plus 等主流技术栈从 0 到 1构建一个企业应用级管理前端框架。

项目有问题建议 issue 或者可以通过项目 关于我们 加入交流群反馈。

本文收录于以下专栏
cover
vue3-element-admin
专栏目录
🔥基于 Vue3.3 + Vite4.3 + TypeScript + Element-Plus + Pinia 等技术栈构建的后台管理前端模板(配套后端源码)。vue-element-admin 的 vue3 版本 。
·
4 篇文章
评论 552
avatar
0 / 1000
最热
最新
博主,我跟着文档创建和配置了项目,但到最后有一些问题。开始用的都是npm 创建的项目和依赖,最后删除了node_modules后,重新用pnpm install 和pnpm run dev有些配置文件会报错。像uno.config.ts中的一些引用会爆红,是不是npm和pnpm安装包时会有不同,这个要怎么处理?是从头到尾都用pnpm或npm吗?
点赞
评论
前端开发 @不联网搬砖公司
火狐浏览器不兼容吗
点赞
1
兼容啊,具体还得看下浏览器内核版本是否支持ES6新特性。
点赞
回复
有关字典的接口都失效了
点赞
1
可以了,接口文档已更新
1
回复
OpenAPI 文档vapi.youlai.tech,这个打不打开了,能提供下最新的地址么?想用这个生成一下express框架的代码
点赞
2
在线apifox文档导出,apifox.com
点赞
回复
感谢,按钮在最底下,没看到[哭笑]
点赞
回复
非常强
点赞
1
感谢认可。[抱拳]
点赞
回复
@前端开发
大佬,有考虑做react版本吗
点赞
1
这个暂时还没计划,如果有很多受众我们会考虑[嘿哈]
1
回复
为什么这个项目typescript的类型错误,运行时不报错呢?比如我在代码中明显写一个错误代码:let age: number = "字符串";这句代码运行并不会报错
点赞
1
有警告的,如果你需要启动就ts检查,在对应的脚本设置即可 "dev": "vue-tsc --noEmit & vite",
点赞
回复
感谢,刚好最近在找管理系统模板[呲牙]
点赞
1
感谢认可。
点赞
回复
路由权限的逻辑是不是用花裤衩大佬的逻辑呀[呲牙]
1
1
对的,不过有些合理化的调整,像动态的路由权限交由服务端判断等。
点赞
回复
现在用npm init vite 这个命令创建出来的目录结构和up的不一样了,怎么办啊,开始就卡死了,各种报红线,烦死了
点赞
1
参考配置下ESLint V9 版本:youlai.blog.csdn.net
点赞
回复
考虑出一个go的后端吗,目前用go写了几个接口了,懒得写。
点赞
1
有考虑,但目前没有时间和精力[流汗]
点赞
回复
svg封装这部分,怎么都没办法把svg图片封装到按钮中去,正常的svg显示都ok了。
点赞
评论
前端 @广东工业大学
pnpm install 的时候就卡死了,整个电脑都卡死了,试了好几次都这样
点赞
1
建议换台设备可以先排查下是否环境问题
点赞
回复
前端
sourcetree提交时报 stylelint --fix: Error: All input files were ignored because of the ignore pattern. Either change your input, ignore pattern or use "--allow-empty-input" to allow no inputs at standalone (file:///Users/xx/demo/vue3-element-admin/node_modules/.pnpm/stylelint@16.15.0_typescript@5.8.2/node_modules/stylelint/lib/standalone.mjs:291:43),请问这个怎么解决?
点赞
评论
前端开发工程师
为啥这么容易卡死在主页。预览的那个
点赞
7
预览环境我这边试了主页没有卡死的情况呀,请问怎么操作的?
1
回复
就是点进去就卡死[发呆]可能是我这边电脑问题,mac环境
点赞
回复
查看全部 7 条回复
火箭制造工 @青青草原羊村
能请教一下你的这个是怎么设置的吗?我的会有波浪线报错
点赞
评论
如果我前端配置角色动态路由,是不是需要将图中代码改为文档中“动态权限路由”的代码呀
点赞
评论
改不动改不动,ts类型定义的太多了,互相嵌套不知道怎么动。什么时候可以搞个js 的
点赞
1
vue3-element-admin 的JS版本已发布:gitee.com
1
回复
后端接口文档的链接打不开了
点赞
2
www.apifox.cn ,线上的接口文档可能关闭了,用这个共享的吧
1
回复
点赞
回复
插件包里面这个sortablejs是用在哪里了呢,我搜了一下全局,没发现引用[握手]
点赞
2
gitee.com 后面加的功能【代码生成】codegen里用到
点赞
回复
谢谢,之前已经把这个页面删除了,所以全局没搜到[赞]
点赞
回复
我感觉我刷到宝藏了
1
1
[奸笑]谢谢评价
点赞
回复
还在更新,牛,拿这个做架子,改造一下,上了四个项目了
1
1
谢谢反馈[嘿哈]
点赞
回复
大佬有没有js版本的
点赞
2
目前没有哦
点赞
回复
vue3-element-admin的JS版本已发布:gitee.com
点赞
回复
[微笑]
点赞
评论
[赞]
点赞
评论
大佬,能给出个后台本地部署的教程吗? 纯前端不会啊[委屈][委屈][委屈]
点赞
1
视频教程:www.bilibili.com

后端的文档月底会出来
1
回复
点赞点赞
点赞
1
感谢![摸头]
1
回复
初级前端开发
简洁版的,怎么有这个啊,怎么去掉,大佬
点赞
1
vite.config.ts 配置里去掉VueDevTools({
openInEditorHost: `http://localhost:${env.VITE_APP_PORT}`,
}),
点赞
回复
膜拜大佬,给我很大帮助
1
1
感谢评价!
点赞
回复
没接触过vue的,一步步跟着大佬弄,现在心里有谱啦,好人一生平安~[奸笑]
1
1
感谢认可评价!
点赞
回复
大佬,设置了三级菜单之后,导致layout组件加载两遍,是没有配置好吗?还是一个bug
点赞
1
配置不对,三级菜单的路由出口页面不能在使用Layout,需要自定义路由出口页面,可以参考演示站点的多级菜单配置。
1
回复
使用vue3-cron-plus-picker时,导致组件里的样式无法加载。请问大佬该如何解决
点赞
评论
需要登录的大屏项目,如何在配置完菜单后完全隐藏layout?
点赞
1
这个需要调整代码,最简单的方式修改src/layout/index.vue里为你大屏的内容
点赞
回复
前端
请问写了路由后,点击后不显示在顶部是什么原因?(如图)
点赞
1
失误失误...子路由写了个重定向[捂脸]
点赞
回复
跟着作者搭框架,已经过去快两个月了,现在公司只有我一个前端,我的项目都用的这个跟着搭的框架,特别感谢作者大大[流泪]
3
1
也非常谢谢你的反馈评价,后面自己多加油[奋斗]。开源项目也会因为你门的支持一直走下去。
2
回复
CV工程师
作者大大,这块是./src哇
1
4
src没错的,在你那边出现什么问题了吗?__dirname 是 node 提供的全局变量标识当前文件所在的目录路径,resolve 函数使用路径分隔符拼接项目目录和src得到src文件的绝对路径。
点赞
回复
我的目录结构如下tsconfig.json和tsconfig.app.json、tsconfig.node.json
点赞
回复
查看全部 4 条回复
前端工程师 @白鸽在线
你这项目是我目前学过vue3项目里写的最规范的[赞],学到好多知识
1
4
非常感谢你的认可评价[谢谢]
1
回复
我node版本v18.20.1, npm npm版本是10.8.3, pnpm 版本9.6.0,克隆项目 gitee.com 的源码, 启动报错sass错
More info: sass-lang.com


52 │ #{red($color),
│ ^^^^^^^^^^^
vite报错
7:04 [vite] Internal server error: Cannot find package 'vue-template-compiler' imported from /Users/chaozhao/Desktop/vue3-element-admin/node_modules/.pnpm/local-pkg@0.5.0/node_modules/local-pkg/dist/index.mjs
at new NodeError (node:internal/errors:405:5)
展开
点赞
回复
查看全部 4 条回复
请问菜单的图标是从哪弄的,我从阿里图标库下载的svg,不会根据菜单选择变色
点赞
2
删除 svg文件中的fill定义试试
1
回复
iconfont有的图标颜色是固定的,下载预览的时候切换颜色预览下
点赞
回复
前端开发
vite-plugin-svg-icons 在官网文档Issues中提了更多问题没有解决,影响还挺大,并且该库也不维护了,请问大佬在企业开发中还用这个库嘛
点赞
1
使用中,目前在项目不影响的
点赞
回复
真好
2
评论
大佬,请问封装的动态表格里,如何实现多级表头啊
1
评论
想部署到本地 但是项目图片加载不出来 后端功能也没通想问问是哪里的问题
点赞
4
本地服务该改的都改了
点赞
回复
F12 看下图片的地址
点赞
回复
查看全部 4 条回复
博主大大 为啥用线上地址 请求 一直报过期啊
点赞
4
截图看你的请求地址,应该是代理配置的问题
点赞
回复
这个 直接用的线上地址
1
回复
查看全部 4 条回复
项目启动不了 报错 Cannot read properties of undefined (reading '_c')
点赞
3
验证本地启动和打包部署到线上没问题的,多是和本机环境有关系
1
回复
建议提供下node和npm版本,以及项目在电脑的位置,还有报错的完整截图。
点赞
回复
查看全部 3 条回复
前段时间用的好好的,可能是pnpm 版本升级了,现在一直报这个错误
点赞
3
不管切哪个node版本都是报同样的错
点赞
回复
求大佬指点
点赞
回复
查看全部 3 条回复
本地运行验证码请求失败
点赞
2
请求的接口地址默认线上,如果调整为本地需要启动后端服务
点赞
回复
谢谢了
点赞
回复
请问有没有基础版
点赞
1
精简版:gitee.com
点赞
回复
打包能使用mock么?我的需求是只要把界面做好并部署即可,不需要添加接口请求
点赞
评论
这时什么原因啊,项目启动报错
点赞
4
你试着换个除了C盘的其他盘看下,有结果希望给个反馈,先感谢了。
1
回复
确实是放的位置的问题,换了个盘就好了
点赞
回复
查看全部 4 条回复
大佬,点击多级菜单后,然后切换导航显示模式,会出现这个
2
4
完善下操作步骤哈,我这测试未复现
点赞
回复
场景一
点赞
回复
查看全部 4 条回复
前端工程师 @非互联网
大佬,不太会ts,可以使用js的吗这个
1
2
vue3 搭配 ts,就像汉堡+可乐,绝配。所以,你应该花时间去学懂 ts
1
回复
点赞
回复
大佬,打包时候报/views/redirect/index.vue不存在怎么办
点赞
1
刚重新打包发布到线上:vue3.youlai.tech
测试没问题,群也没人反馈过,你可以尝试换台机器试下
点赞
回复
你好,这个项目挺棒,请问一下,这个生产环境下console.log出不来是在哪里做了配置?
点赞
1
github.com
不过vite集成了Terser插件,详细的Terser插件参数可以看看这里terser.org
,搜索一下drop_console就可以找到
点赞
回复
知乎和gitee 已经六连[呲牙]
1
1
感谢!
1
回复
问一下css的问题
如果我想要跟elementPlus vite版本一样修改successwarningdangererrorinfo 等这些的颜色应该怎么做
$--colors: (
"primary": (
"base": rgb(158, 19, 250),
),
"success": (
"base": #f0f326,
),
"warning": (
"base": #f2711c,
),
"danger": (
"base": #db2828,
),
"error": (
"base": #db2828,
),
"info": (
"base": #42b8dd,
),
);

// we can add this to custom namespace, default is 'el'
@forward "element-plus/theme-chalk/src/mixins/config.scss" with (
$namespace: "ep"
);

// You should use them in scss, because we calculate it by sass.
// comment next lines to use default color
@forward "element-plus/theme-chalk/src/common/var.scss" with (
// do not use same name, it will override.
$colors: $--colors,
$button-padding-horizontal: ("default": 50px)
);

// if you want to import all
// @use "element-plus/theme-chalk/src/index.scss" as *;

// You can comment it to hide debug info.
// @debug $--colors;

// custom dark variables
@use "./dark.scss";


上面是官方给的版本,但是我在框架离修改了,没有生效[呲牙][可怜]
展开
点赞
1
可以参考项目这里:gitee.com
点赞
回复
好像没有做屏幕适配呢,有空加一下,看看大佬的写法
点赞
评论
web前端开发 @百得思维
大佬,能不能出一个关于这个的补充的搭建说明,类似pinia模块化思路的构建
1
评论
请问这个userStore里的信息是登录后写入的吗?我找了半天没发现这个是怎么处理的,就是成功登录后当前用户的username、role等信息都是怎么放进去的
点赞
2
登录后会拿token获取用户信息后赋值的
点赞
回复
一般是在导航守卫里请求用户信息
点赞
回复
web前端开发 @百得思维
大佬,这个有详细的文档说明吗,看路由里面的meta配置看不大懂
点赞
2
router的meta有类型声明,也可以参考 vue-element-admin 的官方文档。
点赞
回复
好的大佬
点赞
回复
github.com

请问这里的
active-icon="Moon"
inactive-icon="Sunny"
为什广有效
找不到图标从哪边引入的
展开
点赞
1
项目中全量导入element plus 的icon,无需在页面再次导入了。
1
回复
好像有样式冲突的问题,把App.vue里面的HelloWorld组件删除,新建一个页面,然后把HelloWorld组件放进去,会发现bg-[#242424]不生效了
点赞
评论
大佬我能问一下那个菜单权限和按钮权限吗,角色权限分配提交后数据:{name: "首页", id: 0} 类似于这样的提交数据,是根据什么显隐的角色权限路由
点赞
1
是根据什么显隐的角色权限路由和菜单下的按钮?
点赞
回复
感谢作者,公司要使用vue3+ts做管理系统,我从众多的开源项目里找到了这个非常棒的框架,即使一直以来用的都是vue2+js也能很快上手,再次感谢作者的无私奉献[飞吻]
2
1
xiexie
点赞
回复
请问一下大佬按步骤配置到这一步无法解析是怎么造成的?
点赞
8
点赞
回复
点赞
回复
查看全部 8 条回复
我从码云上克隆下来的工程,有没有大佬看下为什么代码跑起来是这个样子?
点赞
3
你这gitee仓库地址多少?最新的代码样式可不是截图的样子。
点赞
回复
点赞
回复
查看全部 3 条回复
前端开发工程师
zhangyao1990.github.io这个还不错
点赞
评论
前端开发工程师
github.com这个vue3管理端模版用起来还不错,清新简洁.
点赞
评论
大佬有的vue文件有这个 是自动导入的bug吗?还是啥
点赞
2
不是,估计是重命名文件引发的,已删除,谢谢告知
点赞
回复
大佬客气了
点赞
回复
非常全面优雅的管理后台架构,虽然很多功能用不到,删起来花了点时间,但是还是很棒[赞]
点赞
4
有精简版,gitee和github搜索:vue3-element-admin-thin
1
回复
精简版github:github.com
精简版gitee:gitee.com
1
回复
查看全部 4 条回复
请问可以自己跟着搭一份吗
点赞
1
真有礼貌
点赞
回复
感谢作者,有一处想问下整合Element Plus那里,为什么在AutoImport和components两处配置都用 resolvers: [
ElementPlusResolver(),
IconsResolver({})
],我试了只在components那里加就可以无需引入直接element的组件和icon了, 是还有什么区别吗?还是只在components那里配置就可以了?
1
1
AutoImport 和 Components 分别对应于 unplugin-auto-import 和 unplugin-vue-components 这两个不同的Vite插件,它们的主要区别AutoImport用于导入API 函数,Components 用于导入组件
点赞
回复
富文本全屏就看不见写的东西了
点赞
评论
想把左侧导航划过显示悬浮的二级导航,修改哪个参数吗
点赞
1
水平导航菜单hover会悬浮 。
垂直导航菜单只有在折叠的时候hover会悬浮二级菜单,正常的情况官方没有提供对应的参数。具体可查看官方文档:element-plus.org
点赞
回复
作者能教一下后端的部署发布吗?本人纯前端,下载后端项目本地可以跑起来,但打成jar包放服务器上一直起不起来[捂脸]万分感谢。
点赞
2
juejin.cn 这篇文章讲了通过IDEA编译、打包、制作镜像和部署服务器,你可以看下。
点赞
回复
感谢[赞][赞][赞]
点赞
回复
资深CV工程师
优雅,实在是太优雅了。舒服了。 另外这里咋没有后端项目代码呢 ?期待能加个链接[呲牙]
1
2
谢谢认可,文中有的,后端仓库地址:gitee.com
点赞
回复
感谢开源,下午看了看前端代码和封装 以及整体架构。 只有真正的前端老司机才能搞出这么优雅的架构。不像某些网培老师,理论牛的一比,实际项目就像小白[奸笑]
点赞
回复
前端
现在接口还能用吗
点赞
1
线上接口已恢复
点赞
回复
前端工程师 @外包
怎么接口用不了了???我才做到首页就没了[流泪]好哥哥,不要这样啊[流泪][流泪][流泪]
1
1
线上接口已恢复
点赞
回复
大佬您好,我前段时间也在做前端基建相关的内容。遇到一个问题是,我们现在用的是antdv 3.x的版本,然后antdv 4.x的版本,对一些组件的props和emit做了破坏性的变更。有没有什么方案相对平滑的方案,对所有已经在使用的项目进行升级呢?
点赞
评论
前端工程师 @外包
文档中的整合Axios里面的import { useUserStoreHook } from '@/store/modules/user';这个源码在哪里?
点赞
3
文章不可能贴出完整的代码的,我已经把git仓库源码地址放在文章内多处声明了,你可以对着找下:gitee.com
2
回复
好的,谢谢你,项目写的很好噢,已经学到技术了,点赞
点赞
回复
查看全部 3 条回复
大佬有js版本的吗
点赞
2
vue3版本没有js,vue2版本的是js,可以配置项目跳过ts类型检查的。
点赞
回复
谢谢 希望你抓紧出个视频教程哇哈哈哈哈
1
回复
博主,你这些css是放在哪的呢
点赞
1
引入的element-plus组件的样式
点赞
回复
太牛了
1
1
感谢认可
点赞
回复
膜拜大佬[强],大佬能弄一个配套完整Python后端权限管理接口吗,非常感谢[拥抱]
1
1
dvadmin这个权限做的很好
点赞
回复
膜拜大佬[强]
2
评论
太强了
2
评论
这个动态路由不需要在router里面写一份吗 本地开发怎么办,还有那个dashboard怎么才能去掉 删掉路由就报错[流泪]
1
1
本地开发在 .env.development 开发环境配置文件中开启mock
1
回复
大前端
大佬你好,demo写的非常好,学习了很多东西。评论基本都回复了,太棒了。在码云那想捐赠一份早餐发现你没有开启。 然后我想请教两个问题, 一个是我demo拉下来进行二次开发,后续你那边更新了代码我该如何得知并且修改本地的,第二个问题是vue3貌似没有好用的PDF预览库。
1
3
有心,谢谢。gitee 可以关注下我,动态你可以看到,vue3 的PDF你可以试试这个:github.com
点赞
回复
发现一个bug,或者是我没搞明白的设计吧。文字描述: 在平台文档内嵌路由下,点击二级面包屑的平台文档四个字。
点赞
回复
查看全部 3 条回复
真是好东西,感谢大佬的无私奉献
1
1
谢谢评价[奋斗]
1
回复
牛逼 直接收藏
1
1
感谢认可和支持
1
回复
大佬b站有没有视频呀
1
1
暂时没有,目前还在着手开源项目的更新,后面有时间可以出视频
1
回复
感谢大佬的开源!
1
1
同样感谢支持.
1
回复
作为给公司选择框架来说,最关注的就是更新频率,就怕哪天有BUG也不再更新了,作者一定要坚持啊,会一直支持
1
1
感谢这么用心的评价和支持,只要有人需要,项目会坚持开源下去的。
1
回复
前端
大佬,问下有单独一套ui规范,怎么覆盖elementplus的样式呢?怎么写样式呢
点赞
1
官方有说明
1
回复
前端cv
进入首页后 再进入其他页面 开浏览器控制台或者缩放页面 回到首页 图表样式乱了
我没拉代码看 在线预览
点赞
1
已修复,谢谢反馈
1
回复
厉害了[赞],先收藏一波
点赞
1
谢谢
1
回复
前端开发 @斑马小镇
为什么在template第一行加个注释,一次进去没问题,切换到其他页面后全白了?[衰]
点赞
3
第一次进去没问题,但是有点明显的卡顿
点赞
回复
好吧,,,找到原因了
点赞
回复
查看全部 3 条回复
前端工程师
大佬好,我想问一下这个框架支持MIT开源协议吗
点赞
1
支持
点赞
回复
软件工程师
这个好鲜润太牛逼了,前后端都是他写的,还写的非常清晰优雅,值得学习[奸笑]
1
4
[嘿哈]何方神圣
1
回复
就是你名字[捂脸]呀 haoxr
点赞
回复
查看全部 4 条回复
吃饭大王 @干饭控股
[偷笑]
2
1
点赞
回复
web前端 @不知名的小公司
厉害了[赞],先收藏一波
2
评论
很棒!!![赞][赞][赞]
2
评论
作者
Developer | 有来开源
帮忙投票的童鞋留个回复,方便回关和说声谢谢[抱拳]activity.juejin.cn
点赞
评论
作者
Developer | 有来开源
大家帮忙投个票,谢谢了!activity.juejin.cn
点赞
2
点赞
回复
谢谢,已回关你了
点赞
回复
前端炒米粉首席师 @米线集团
[赞]
点赞
1
点赞
回复
web前端 @上海猫头鹰生物科技科技有限公司
element样式权重比unocss样式高怎么解决
点赞
2
为什么会出现这种情况
点赞
回复
遇到了同样的问题,请问有解决方法吗?
点赞
回复
web前端 @上海猫头鹰生物科技科技有限公司
master分支代码是当前最稳定的吗,可以直接拿来开发吗大佬
点赞
1
对的,master 是稳定的可以拿来开发。其他分支为开发或实验分支。
1
回复
前端工程师 @不知名公司
好好好
1
1
点赞
回复
element-plus-admin.cn 极其相似啊,你是参考做了个简版?
点赞
1
不是,文中的第一句已经做了说明 “vue3-element-admin 是基于 vue-element-admin 升级的”
点赞
回复
为什么我调用接口都是不对呀
点赞
2
什么意思?项目线上演示地址:vue3.youlai.tech 仓库地址:gitee.com
点赞
回复
在线接口文档:www.apifox.cn
点赞
回复
独立干饭队长
访问地址:http://39.107.143.109/login
Gitee:gitee.com
YU-ADMIN 企业级中后台模版,四种布局,多种主题颜色,支持PC、IPad和移动端。
欢迎Star!!!
5
2
动效,布局做的真不错。就是UI不适合做业务开发,然后系统有些臃肿
点赞
回复
老乡牛的
点赞
回复
前端开发 @云南见手青
我撸了一个只适合前端的,没有后台接口,不使用TS
admin.dumogu.top
4
3
点赞
回复
看着很不错,做得很精细,不过UI不太通用,用于公司项目
点赞
回复
查看全部 3 条回复
我虽然看不懂,但我大受震撼
1
评论
楼主,所有的接口都定义输入参数类型和返回参数类型吗,我看登陆那里有,所以好奇是不是所有的都需要这样定义
点赞
6
可以用,也可以不用,项目中基本都定义了。用接口工具自动生成的。
点赞
回复
接口工具?楼主的node版本是什么呀,我配置eslint 的时候关闭末尾的双引号了,但是 .vue 文件 setup lang: ts 里面import会自动加上 ;导致一直有错误提示,这个要怎么处理呀
点赞
回复
查看全部 6 条回复
楼主我想拿你的后台服务器端部分加入我的模版可以吗?chenhuajie.gitee.io
点赞
评论
楼主,我觉得你可以试试这个直接内网部署公网演示.juejin.cn
点赞
评论
大佬 想要一个不带TS的版本 快疯掉了
点赞
2
啥功能 疯掉了
点赞
回复
VUE3跟TS是最搭的,必须掌握的
点赞
回复
vue-router 封装文件 报错这个什么意思啊xdm
点赞
1
tsconfig.json的配置问题,重新配置即可
点赞
回复
前端工程师
厉害了
1
评论
请问怎么移除ts呀,或者把代码里的ts代码转成js[吃瓜群众][吃瓜群众][吃瓜群众]
点赞
评论
前端开发
大佬,这个项目咋部署到本地呀
点赞
评论
你好,你们这个Vue3.3 + Vite4.3 + Element-Plus + TypeScript 从0到1搭建企业级后台管理系统(前后端开源)是可以商用二开出售的是吗?
点赞
1
像可以支持二开做公司项目或私活都行。售卖源码是不行的。
3
回复
请问下,如果我要把ts从此项目移除是不是不太...容易?
点赞
6
不太...可能
点赞
回复
确实不太可能,哈哈。
因为我们已经确定后续所有新项目全用vue3了,所以这几天在自己搞一个不加ts的vue3-element-admin
1
回复
查看全部 6 条回复
切图仔 @切图公司
有个错误,在user.ts中定义的user是非响应式,在SwitchRoles.vue中通过storeToRefs解构,无法把非响应式的user解构出来,后面还.value
1
评论
切图仔 @切图公司
完全基于 vue-element-admin 升级的 Vue3 版本,没有对框架(Element Plus)的组件再封装,上手成本低和扩展性高。
-----------------------------------------------------------
这点说到点了,很多框架为了展示自己的水平,过度封装,看着很高级,但耦合性太高,非常难用[抓狂]
1
评论
主包只留重要页面和公共资源,其他全扔分包
点赞
评论
顺便向作者推荐个pinia持久化插件persistedstate 挺好用的
1
1
点赞
回复
作者太全了 可以称之为vite的保姆级教程了!
2
1
感谢~
点赞
回复
[赞][赞][赞]
1
1
thks!
点赞
回复
博主厉害,刚好公司有个新项目要开发,去向老板请示用这个模板[不失礼貌的微笑]
1
2
[比心]感谢认可。
点赞
回复
别跟老板说 跟设计说
点赞
回复
真的写的太好了,学习来用做参考真的爽[呲牙][色],爱了爱了
2
1
感谢评价[嘿哈]
点赞
回复
mark一个vue3的模板,有备无患
1
1
1
回复
正瞌睡,就送过来枕头啊,开搞[赞]
2
1
点赞
回复
作者大大 ,为何切换成mock之后运行项目,报这个错啊
1
1
备注:获取master分支最新代码后正常
点赞
回复
超赞哦,真的很好,开箱即用。如果能出一个react ts的版本就更完美拉。[呲牙]
点赞
5
感谢~
点赞
回复
我看了码云上的地址,各个分支有什么不同?我在项目中想直接开箱即用应该选择哪一个分支啊[呲牙]
点赞
回复
查看全部 5 条回复
回车缩进变成tab了?
1
评论
作者你好,最近我在使用你的项目时遇到了一个问题,我在本地电脑上启动项目什么问题都没有,但是发布在云服务器上之后导出数据的功能就不能用了,浏览器控制台会报一个错(见图),导出来的文件是0B的,不知道作者有没有遇到过这个问题
点赞
1
线上没问题。你这看着反向代理给移除了,你这要么把后端导出接口换成https,要么按照项目readme的部署说明在nginx配置反向代理。
点赞
回复
你好,我是名初学者,我在使用的时候不知道如何实现一个页面的缓存,我将这个路由加入了白名单,当跳转到另一个路由返回后页面没有进行缓存,请问要做?
点赞
4
1. 路由keepAlive=true; 2. 路由的name和页面name一致
点赞
回复
我都有设置的了,主要可能是因为我在里现还有个子router-view 引起的,如果是在主router-view 上没有问题
点赞
回复
查看全部 4 条回复
前端工程师
作者写的很好,里面有很多东西值得我去学习,谢谢分享[愉快]
1
1
同样谢谢你的评价[比心]
点赞
回复
学习了 学习了 有机会用用里面的东西
1
1
谢谢评价
点赞
回复
大佬很强!学习了
1
1
谢谢评论[抱拳]
点赞
回复
前端 @夺冠互动
点个赞
1
1
感谢[抱拳]
点赞
回复
谢谢题主,刚好用到该系统
2
1
同样感谢你的评价[抱拳]
点赞
回复
这个确实不错,保姆级的了 生怕你学不会
2
1
谢谢评价[抱拳]
点赞
回复
一看就知道很肝
1
1
[抱拳]谢谢评价
点赞
回复
挖坑仔 @@工种号: 前端桃花源 @ikun
cool
1
1
谢谢[抱拳]
点赞
回复
初学者。。 一步一步按教程来的,请问这是什么问题
1
1
缺少导入,截图只能截对应章节的关键部分。初学者还是建议把项目熟悉一下再按照文章0到1吧,不然这种错误没法判断很难继续下去的。 gitee.com
点赞
回复
找不到path 模块的通过npm install @types/node --save-dev 引入即可
1
1
系列文章有说明的,不过在这也谢谢你补充
点赞
回复
history打包以后部署能打开但是不能刷新,刷新就404请问什么原因?
点赞
1
你是改成了html5了history模式了?
点赞
回复
作者你好,请问下演示环境为何使用Websocket中的功能会报错呢?
1
1
websocket 前端还没完成
点赞
回复
[呲牙]参考有了,vue3框架开搞
1
3
点赞
回复
后端都有api文档,为啥不直接生成前端的接口和输入输出ts文件?
点赞
回复
查看全部 3 条回复
65555
1
1
谢谢评价
点赞
回复
可以升级了哦
1
2
点赞
回复
[不失礼貌的微笑]你要是敢用nest重写后端项目,我给你筹集1000个star
点赞
回复
JQKA @牛马
请问大佬,登录的接口为post 但是值在地址栏可见,可以改为不可见吗
点赞
6
你意思是f12控制台可查看用户名密码明文密码?这个传输过程使用https会被加密,所以不必太过担心。如果你担心身边人使用你浏览器密码历史登录可以使用jsencrypt 对密码rsa加密。
点赞
回复
post请求不应该是地址栏不显示具体传参吗?如果这样我在后端打印参数,接收post参数接收为空,用get可以接到,所以显示有点迷茫这是post传值还是get传值
点赞
回复
查看全部 6 条回复
感谢分享
1
1
谢谢评价[啤酒]
点赞
回复
web前端
用此模板码了好几个项目了,谢谢有来团队[色]
1
1
[灵光一现]感谢认可
点赞
回复
[赞]
1
1
[抱拳]感谢
点赞
回复
牛波一[流泪]
1
1
no no no~ 谢谢评价[啤酒]
点赞
回复
就怕我学不会[泣不成声][赞][赞][赞]
1
1
[熊吉]学不会欢迎回来砸场
点赞
回复
专业酱油
阿祖,收手吧
1
评论
一位不愿透漏姓名的员工
[赞]
2
1
感谢[灵光一现]
点赞
回复
感谢分享
1
1
感谢评价[抱拳]
点赞
回复
一起喵喵喵喵喵 @喵星人
感谢开源[赞]
1
1
也感谢你的评价[抱拳]
点赞
回复
very nice
1
1
谢谢评价[西瓜]
点赞
回复
搬运工 @四海全球投资集团
斯国一
1
1
点赞
回复
前端开发 @上海星眼科技
除了外瑞古德就是外瑞古德
1
1
点赞
回复
点赞
1
1
感谢[抱拳]
点赞
回复
小小前端
[赞]
1
1
点赞
回复
前端切图仔、摸鱼怪 @删库跑路公司
懒人福利了属于。
1
1
点赞
回复
很好为啥没有js版本
点赞
1
vue2 是js版本的,服务端、管理端和移动端都要升级,所以没精力单独维护一套js版本了。
点赞
回复
爪哇搬砖工程师 @上海掘友摸鱼信息科技有限公司
学习了,很棒;感谢分享
1
1
感谢认可[奋斗]
点赞
回复
谢谢您提供这篇文章,它让我更好地了解了某项技术
1
1
点赞
回复
前端开发
感谢作者分享,已start+fork
1
1
[抱拳]感谢
点赞
回复
前端开发 @有你的公司
有木有js版本
2
1
veu3没有js,vue2版本是js版本的,不过已经很长时间没维护了,gitee.com
点赞
回复
好东西.话说vue3都是自己看文档学的吗到现在只会v2pina都没用过不知道能不能看懂
1
3
个人觉得2和3差别不大的,主要引入了ts,即使没有vue2基础也可以照着一步步完成搭建。
1
回复
我试试一点点学习这个感谢分享
1
回复
查看全部 3 条回复
mark
1
评论
mark
1
评论
前端小菜鸡
先收藏了后边再看[嘿哈]
1
评论
准备用这个替换一个vue2的项目了
1
1
点赞
回复
已经在用了,
1
1
[灵光一现]感谢支持!
点赞
回复
准备学习一下[色]
1
1
[啤酒]相互学习,加油,谢谢你的赞和评论。
点赞
回复
前端开发 @保密
收藏了,下周有时间撸一遍[看]
1
1
感谢![谢谢]
点赞
回复
看这目录就值得点赞收藏[机智]
2
1
点赞
回复
写的蛮详细的,适合收藏学习😌
1
1
谢谢评价[抱拳]
点赞
回复
作者
Developer | 有来开源
虽然文章流量不咋地,但在这感谢素不相识帮忙点赞、评论、收藏和阅读的大家,真的有心了[谢谢]
1
评论
先收藏
1
1
[谢谢]谢谢
点赞
回复
收藏了,后面学学,谢谢作者[送心]
1
1
感谢[抱拳]
点赞
回复
睡觉工程师 @Only Sleep
没啥好说的,前后端都用了非常全面的技术,而且项目本身还不是特别臃肿的那种,必须点赞好好学习,希望有一天我也可以成为你们开源组织的一员[可怜]
2
1
[流泪]有心了,感谢这么用心的评价。
[西瓜][啤酒]加油,开源组织也期待你的加入。
点赞
回复
韭零后前端
使用pnpm是不是好一些?
2
1
[赞]是的,项目启动说明使用的是pnpm,文章面向广大读者朋友使用 npm,命令使用上也就是 npm → pnpm 词的替换。
点赞
回复
学习学习
1
1
谢谢评价~~
点赞
回复
跟我之前写的一个后台很像
点赞
1
嗯,这个是基于花裤衩的开源项目 vue-element-admin 升级的 vue3 版本,所以看着眼熟了吧[奸笑]
点赞
回复
作者大大 以前没有怎么接触到eslint 按照文章一步一步到自动引入那块对于eslint是怎么配置的不是太了解 有空能不能讲一下
1
评论
这个有的,因为我怕篇幅过长导致阅读体验不好 ESLint 单独写了文章的,文中 【规范配置】一节中有对应文章的链接地址,你可以看下,都是完整的。
点赞
回复
好的 谢谢 您写的很详细 非常不错
点赞
回复