github: https://github.com/heyongsheng/hevue3-admin 码云: https://gitee.com/ihope_top/hevue3-admin 线上体验地址 https://ihope_top.gitee.io/hevue3-admin
相信绝大多数的前端小伙伴就业初期或多或少都了解或使用过花裤衩大佬的vue-element-admin,部分小伙伴还看过框架配套的文章——手摸手撸后台系列。但很多小伙伴上来就用框架,很多实现方法都不了解怎么实现的,比如权限管理怎么做的?标签切换怎么做的?暗黑模式自定义主题又是如何实现的?诸如此类的细节还有很多,像我之前就不是很懂,用是会用,但是框架出点什么毛病就很难去修改。所以趁着失业,正好静下心来学习一下,用vite+vue3+element-plus+Ts来从0开始写一个通用的后台管理模板,ts由于我也不是太熟,写着用着,以不报错为主,所以ts用法部分仅供参考。
本篇文章会从初始化项目开始讲,尽量会提到每一个步骤,但由于写作经验不足,难免会有遗漏和啰嗦,欢迎指正,另外可能有些部分在写完之后又调整顺序,所以代码截图部分可能会出现后面的内容,不过不影响,成品我也会上传github。
另外因为在写文章期间代码也在不断调整,所以部分章节的截图可能和实际有出入,最终结果以实际代码为准,但整体思路基本没什么问题。
项目已开源,文章头部附有github及码云地址,也有体验链接,由于项目初期完善性不能保证,仅供参考,不建议作为公司项目使用,但我也会持续进行更新优化,欢迎大家提出自己的意见,也希望可以给个⭐️。
在此先列举一下文章主要涉及的知识点,文章不一定会按照以下顺序进行
除此之外还有一些其他方面的小坑,比如如何阻止浏览器默认填充密码时候的背景框、如何阻止浏览器填充密码还有各种官方文档没提到的报错等,文章里有些代码我没有说是因为我也不是太精通,但都是我四处查询资料查到的用法,如果你也是小白的话,照着抄就行了,基本不会出错。
因为着急找工作,所以文章和项目都有很多待优化的地方,如果我还能找到前端工作的话,会继续对文章和项目进行优化,也希望北京、上海的朋友如果有要求低(不要求学历)的工作的话可以推荐一下
首先我们使用vite来初始化项目,vite支持使用npm、yarn和pnpm来初始化项目,本文采用yarn的方式。
// 使用npm
npm create vite@latest
// 使用yarn
yarn create vite
// 使用pnpm
pnpm create vite
输入命令之后会提示我们输入项目名,如果我们已经建好项目,并且是在项目根目录执行的命令的话,只需输入一个点表示在当前目录创建就行,需要注意的是当前项目文件夹必须是空文件夹,否则vite会提示你是否清空已存在的文件继续
如果还没建项目,是在父目录执行的命令,则需输入要创建的项目名
之后会让我们选择使用的框架和选择使用js还是ts,我们选择vue和ts
之后我们按照提示命令进入项目目录执行命令安装依赖,运行项目即可
之后我们把一些默认的没用的文件给干掉,再在相关引用的地方把引用给清空
再把APP.vue里面的内容给清空,这个时候整个项目就是干干净净的了
使用vscode的用户注意了,之前我们开发vue可能使用的是vetur插件,但是从vue3开始,官方已经推荐使用Volar了,所以我们需要把旧的插件给禁用或者直接卸载掉
再去扩展里搜索volar进行安装就可以了
建议进行此更换操作,否则可能引起意外的编辑器报错
在项目开发中,我们通常会引用其他目录的文件,但如果文件不在一个目录的话,就会使用../的方式去查找,如果层级多的话很不方便,所以我们通常会给一些常用目录配置一个别名,比如下面我们就在vite中给src目录配置一个‘@’别名
首先我们需要安装node的类型声明文件,要不然ts可能会报警告
yarn add @types/node -D
之后我们在vite.config.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 { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import path from 'path' // 增加此行代码
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: { // 增加此行代码
alias: { // 增加此行代码
'@': path.resolve(__dirname, 'src'),// 增加此行代码
}, // 增加此行代码
}, // 增加此行代码
})
之后还要在tsconfig.json
中增加以下配置
"paths": {
"@/*": ["./src/*"]
}
我们都知道浏览器对于html标签都会有一些默认的样式,从而使页面美观,但不同的浏览器有不同的差异,所以我们一般都会在建项目时候重置浏览器样式,通常这个重置文件是各个项目通用的,大家如果没有也可以去网上搜一个。我们这里直接在assets/style
文件夹下新建一个normalize.css
,写入重置样式
html,
body,
div,
span,
applet,
object,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
font-weight: normal;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
ol,
ul,
li {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
th,
td {
vertical-align: middle;
}
/* custom */
a {
outline: none;
color: #16418a;
text-decoration: none;
-webkit-backface-visibility: hidden;
}
a:focus {
outline: none;
}
input:focus,
select:focus,
textarea:focus {
outline: -webkit-focus-ring-color auto 0;
}
然后在main.ts
中引入即可
// main.ts
import '@/assets/style/normalize.css'
首先我们来安装一下vue-router
yarn add vue-router@4
之后在src目录下新建router/index.ts
,写入一些基本路由,这里需要注意的是,vue3和vue2时候定义路由的方式发生了细微的变化,我还没有深入学习,暂时知道是这么写的就行。我们的路由通常分为无需权限路由(比如登录)和需要权限的路由(比如某个数据页面),我们这里先简单的定义一个主页面和登录页,meta里面的一些属性我们后面会讲
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'
export const publicRouters: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/layout/index.vue'),
redirect: '/home',
children: [
{
path: '/home',
name: 'home',
component: () => import('@/views/home/home.vue'),
meta: { title: '首页', icon: 'shouye', affix: true, sort: '99.99' },
},
],
meta: {
sort: '99',
},
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/login.vue'),
meta: {
hidden: '1',
},
},
]
const router = createRouter({
history: createWebHashHistory(),
routes: publicRouters,
})
export default router
这里需要注意的是,vue-router指定路由模式从字符串变为了使用方法,history
是createWebHistory
(地址栏不带#),hash
是createWebHashHistory
(地址栏带#)
这时候我们可能还会发现,编辑器中ts会报找不到vue文件相关类型声明的错误
解决办法:我们可以在根目录创建一个env.d.ts
,在里面写入以下内容
/// <reference types="vite/client" />
declare module '*.vue' {
//引入vue模块中ts的方法
import type { DefineComponent } from 'vue'
// 定义vue組件以及类型注解
const component: DefineComponent<{}, {}, any>
export default component
}
之后还要记得在tsconfig.ts
中引入
现在报错没有了,我们还需要在main.ts
中进行引入
import router from './router'
createApp(App).use(router).mount('#app')
在vue2中,我们往往使用vuex来做全局的状态管理,在vue3中,官方推荐我们使用pinia来做全局的状态管理,由于官方文档十分详细易懂,我使用的时候也没发现什么坑,这里就不啰嗦了,直接讲流程,有使用经验的建议跳过。
安装pinia和axios
yarn add axios
yarn add pinia
pinia安装之后需要先在main/ts
引入。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import '@/assets/style/normalize.css'
createApp(App).use(router).use(createPinia()).mount('#app')
一般后台管理系统会有用户管理和后台使用人员,为了区分,这里我们把后台使用人员称之为员工,pinia使用时需要根据存储用途创建对应的实例,这里我们先创建一个员工相关的pinia实例。
import { defineStore } from 'pinia'
export const useStaffStore = defineStore('staff', {
state: () => ({
token: '',
staff: null,
}),
actions: {
setToken(token: string) {
this.token = token
},
logOut() {
this.token = ''
},
},
})
这里简单说明一下,pinia用defineStore
创建实例,接收的第一个参数要求是一个独一无二的名字,第二个配置项就是常用的配置了,在pinia中,你可以认为 state
是 store 的数据 (data
),getters
是 store 的计算属性 (computed
),而 actions
则是方法 (methods
),相比vuex,pinia没有mutations
之后我们创建一个操作token的常用方法utils/auth.ts
const TokenKey = 'token'
export function getToken() {
return localStorage.getItem(TokenKey)
}
export function setToken(token: string) {
return localStorage.setItem(TokenKey, token)
}
export function removeToken() {
return localStorage.setItem(TokenKey, '')
}
我们先在根目录创建一下环境变量,我这里参数仅供参考啊,实际以你实际项目为主
// .env.development
MODE = 'development'
VITE_APP_BASE_PREFIX = '/api'
VITE_APP_BASE_API = http://localhost:6001
// .env.production
MODE = 'production'
VITE_APP_BASE_PREFIX = '/accountApi'
之后我们来对axios进行封装,首先封装一个utils/request.ts
,做一些简单的拦截和返回结果处理的封装
import axios from 'axios'
import { useStaffStore } from '@/stores/staff'
import { getToken } from '@/utils/auth'
import { ElNotification } from 'element-plus'
// create an axios instance
const service = axios.create({
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout: 20000, // request timeout
})
service.interceptors.request.use(
(config) => {
const staffStore = useStaffStore()
config.baseURL = import.meta.env['VITE_APP_BASE_PREFIX']
if (staffStore.token) {
config.headers['x-authorization'] = 'Bearer ' + getToken()
}
return config
},
(error) => {
console.log(error) // for debug
return Promise.reject(error)
}
)
service.interceptors.response.use(
(response) => {
const staffStore = useStaffStore()
const res = response.data
if (res.code === 401) {
ElNotification.error({
title: '错误',
message: res.msg,
})
staffStore.logOut()
return Promise.reject(res)
} else if (res.code !== 200) {
ElNotification.error({
title: '错误',
message: res.msg || '网络错误,请稍后重试',
})
return Promise.reject(res)
} else {
return res
}
},
(error) => {
console.log(error) // for debug
const errContent = error.response
const staffStore = useStaffStore()
if (errContent?.status === 401) {
ElNotification.error({
title: '错误',
message: errContent.data.msg,
})
staffStore.logOut()
return Promise.reject(error)
} else {
ElNotification.error({
title: '错误',
message: errContent?.data?.msg || '网络错误,请稍后重试',
})
return Promise.reject(error)
}
}
)
export default service
之后我们来vite.config.ts
中配置一下跨域,这里我们还需要对之前的写法做一些改造
之后再加入跨域配置
server: {
proxy: {
[config.VITE_APP_BASE_PREFIX]: {
target: config.VITE_APP_BASE_API,
changeOrigin: true,
rewrite: (path) =>
path.replace(new RegExp(`^${config.VITE_APP_BASE_PREFIX}`), ''),
},
},
},
这里写的比较简略,如果有任何建议或者问题,欢迎提出,我会完善
现在我们的项目初始化就完成啦,下一章我们来学习如何自动引入element-plus、element-plus图标,以及自定义图标的解决方案。