vue3后台管理admin+ant-design-vue
vue3后台管理admin
vue3后台管理admin
1创建项目以及vue-router4
1.1-使用vite或者vuecli4创建项目
或者使用yarn
yarn add -g create-vite-app
yarn create vite-app
yarn初始化packeage依赖,并且运行
yarn
yarn dev
安装依赖
我需要使用
依赖库包
vue-cli4
vuex
vue-router
less
引入typescript
安装 typescript
yarn add typescript -D
初始化tsconfig.json
然后在控制台执行下面命令
npx tsc --init
使用vuecli4创建vue3项目
vue create 'project'
1.2-vue-router 4
安装vue-router
yarn add vue-router@next
了解下vue-router 4
Vue Router 4引入了 createRouter API,可以创建一个可与Vue 3一起安装的路由器实例。
import { createRouter } from "vue-router";
export default createRouter({
routes: [...],
});
History选项
在之前的Vue Router版本中,你可以设置 mode 选项设置导航的模式。
hash 模式利用URL hash来模拟完整的URL,这样当URL发生变化时,页面不会被重新加载。history 利用HTML5 History API来实现URL导航,而不需要重新加载页面。
src/router/index.js
// Vue Router 3
const router = new VueRouter({
mode: "history",
routes: [...]
});
在Vue Router 4中,这些模式已被抽象到模块中,可以将其导入并分配给新的 history 选项。这种额外的灵活性使你可以根据需要自定义路由历史记录的实现。
src/router/index.js
// Vue Router 4
import { createRouter, createWebHistory } from "vue-router";
export default createRouter({
history: createWebHistory(),
routes: [],
});
Vue.js 3.0下vue-router 4.0 hash模式
import { createRouter, createWebHashHistory } from 'vue-router';
const router = createRouter({
history: createWebHashHistory(),
routes
});
Vue.js 3.0下vue-router 4.0 history模式
import { createRouter, createWebHistory} from 'vue-router';
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
配置vue-router
在项目src目录下面新建router目录,然后添加index.ts文件,在文件中添加以下内容
import {createRouter, createWebHashHistory} from 'vue-router'
// 在 Vue-router新版本中,需要使用createRouter来创建路由
export default createRouter({
// 指定路由的模式,此处使用的是hash模式
history: createWebHashHistory(),
// 路由地址
routes: []
})
在main.ts中配置添加即可
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')
2 ant design vue
2.1-ant design vue
vue2 element ui
element3 & element plus
安装ant2.0 ui组件
yarn add ant-design-vue@next -s
完整引用ant在main.ts中引入组件
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const app = createApp(App)
app.use(Antd)
app.use(router)
app.mount('#app')
2.2-babel-plugin-import 按需引用
图片
如果你在开发环境的控制台看到下面的提示,那么你可能使用了 import { Button } from 'ant-design-vue'; 的写法引入了 antd 下所有的模块,这会影响应用的网络性能
安装
yarn add babel-plugin-import -D
然后,将 .babel.config.js 修改为:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
"plugins": [
[
"import",
{libraryName: "ant-design-vue", libraryDirectory: "es", style:"css"}
]
]
}
然后在main.ts按照需求引入组件
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import {Button} from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const app = createApp(App)
app.config.productionTip = false;
app.use(Button)
app.use(router)
app.mount('#app')
2.3-将ant组件封装
如果引入的组件比较多,会影响main.ts的优雅
我对A必组件进行封装
在src目录下新建 ant-design-vue/index.js的目录
import { Form, Input, Button } from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
export function setupAntd(app) {
app.use(Button)
app.use(Form);
app.use(Input);
}
ant-design-vue/index.js封装
import { Form, Input, Button } from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const components= [
Form,
Input,
Button
]
export function setupAntd(app) {
components.forEach(component => {
app.use(component)
})
}
然后在main.ts当中引入setupAntd即可
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import {setupAntd} from './ant-design-vue'
const app = createApp(App)
setupAntd(app)
app.use(router)
app.mount('#app')
3-首页layout布局
3.1-layout布局介绍
图片
3.2 -Ant 来实现layout骨架
1.index.vue为首页,安装下less 和less loader
yarn add less less-loader -D
2.配置ant 组件 layout menu
import { Form, Input, Button, Layout, Menu } from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
const components= [
Form,
Input,
Button,
Layout,
Menu
]
export function setupAntd(app) {
components.forEach(component => {
app.use(component)
})
}
3.先行适配desktop端
桌面端就此搞定
3.3-引用iconfont图标
引入iconfont
import { createFromIconfontCN } from '@ant-design/icons-vue';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_1617479_lw8fprdjcs.js',
});
4 登录模块
后台管理系统,需要实现导航菜单从后台拉取的效果;根据登录用户的权限不同分别拉出来的导航菜单也不一样,另外可操作的界面也存在区别
如果要完成权限 登录 查询表单
必须要有后端接口或者自己mock数据
首先安装mockjs和axios
yarn add mockjs -S
yarn add axios -S
登录接口
4.1-用户登录的简单实现思路
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
创建Mock文件夹
创建mockjs
import Mock from 'mockjs'
Mock.mock(
'/api/login',
'post',
(req) => {
const { password, username } = JSON.parse(req.body)
if (username === 'admin' && password === '123456') {
return {
success: true
}
} else {
return {
success: false
}
}
}
)
引入axios
创建request.js
import axios from 'axios'
const instance = axios.create({
baseURL: '/api/',
})
export default instance;
创建index.js
import instance from './request'
export const Login = (res) =>{
return instance({
url:'/login',
method: 'POST',
data:res
}).then(response =>{
console.log(response.data)
if (response.data.success) {
} else {
}
})
}
export default {Login}
创建 views文件夹
同时将共有界面error部分创建好
403报错
403
当前帐号没有操作角色,请联系管理员
404报错
404
当前页面不存在...
路由改造为
import {createRouter, createWebHashHistory} from 'vue-router'
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/Login.vue'),
hidden: true,
},
{
path: '/403',
name: '403',
component: () => import('@/views/error/403.vue'),
hidden: true,
},
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
hidden: true,
},
]
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes,
})
export default router
4.2 -登录页面form提交
vue3-admin
>
<a-form-item>
<a-input
v-model:value="form.username"
size="large"
placeholder="Username"
>
<template v-slot:prefix><user-outlined type="user" /></template>
</a-input>
</a-form-item>
<a-form-item>
<a-input
v-model:value="form.password"
size="large"
type="password"
placeholder="Password"
>
<template v-slot:prefix><lock-outlined type="user" /></template>
</a-input>
</a-form-item>
<a-form-item>
<a-button type="primary" size="large" @click="handleSubmit" block> Login in </a-button>
</a-form-item>
</a-form>
</div>
</a-col>
4.3- localstorage获取token值
了解下localstorage
对浏览器来说,使用 Web Storage 存储键值对比存储 Cookie 方式更直观,而且容量更大,它包含两种:localStorage 和 sessionStorage
sessionStorage(临时存储) :为每一个数据源维持一个存储区域,在浏览器打开期间存在,包括页面重新加载
localStorage(长期存储) :与 sessionStorage 一样,但是浏览器关闭后,数据依然会一直存在
所以上次使用cookie的时候就遇到了一个坑,设置后马上访问session会获取不到,蛋疼,还需要刷新一下,原因是:
当我们首次访问设置Cookie的页面时,服务器会把设置的Cookie值通过响应头发送过来,告诉浏览器将cookie存储的本地相应文件夹中(注意:第一次访问时本地还没有存储Cookie,所以此时获取不到值);
当第二次访问(或在进行cookie设置后,过期前所有的访问)时,请求头信息你中都会把Cookie值携带。(百度到的,暂时还没理解透彻,先搬过来).
使用方法
注意:sessionStorage 和 localStorage 的用法基本一致,引用类型的值要转换成JSON,所以这里就只列举localStorage
1 保存
//对象
const info = { name: 'hou', age: 24, id: '001' };
//字符串
const str="haha";
localStorage.setItem('hou', JSON.stringify(info));
localStorage.setItem('zheng', str);
2 获取
var data1 = JSON.parse(localStorage.getItem('hou'));
var data2 = localStorage.getItem('zheng');
3 删除
//删除某个 localStorage.removeItem('hou'
);
//删除所有 localStorage.clear();
mock/mock.js
import Mock from 'mockjs'
Mock.mock(
'/api/login',
'post',
(req) => {
const { password, username } = JSON.parse(req.body)
if (username === 'admin' && password === '123456') {
return {
code : 200,
token:'admin-token'
}
} else {
return {
success: false
}
}
}
)
新建api,并且创建request.js和user.js
request.js
import axios from 'axios'
const instance = axios.create({
baseURL: '/api/',
})
export default instance;
user.js
import instance from './request'
export const Login = (res) =>{
return instance({
url:'/login',
method: 'POST',
data:res
})
}
export default {Login}
login.vue
vue3-admin
4.4 vuex安装引入
安装和引入vuex
首先,在vuex中设置状态值 判定是否登录
yarn add vuex@next -S
创建store文件夹
新建index.js
import { createStore } from 'vuex'
export default createStore({
})
在main.js当中引入即可
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import {setupAntd} from './ant-design-vue'
import '../mock/mock'
import store from './store'
const app = createApp(App)
setupAntd(app)
app.use(store)
app.use(router)
app.mount('#app')
4.5 actions 的LoginResult
store/index.js
import { createStore } from 'vuex'
import {Login} from '@/api/user'
export default createStore({
state:{
token:''
},
mutations:{
SET_TOKEN: (state, token) => {
state.token = token
},
},
actions: {
LoginResult({commit}, userInfo) {
return new Promise((resolve,reject) =>{
Login(userInfo).then(response => {
const {token, code} = response.data
if(code == 200) {
localStorage.setItem('token',token)
commit('SET_TOKEN',token)
}
resolve(response.data)
}).catch(error =>{
reject(error)
})
})
}
}
})
login.vue
vue3-admin
首页跳转登录页面
4.6 beforeEach首页路由守卫
outer.beforeEach((to,from,next)=>{})
to,是将要跳转的路由,
from,是离开的路由
next是个方法,通往下一个钩子函数,判断to.path 或者 from.path ,如果符合条件,则允许跳转
在router文件夹下的index.js
export const constantRoutes = [
{
path: '/',
component: Layout,
redirect: '/index',
meta: {
title: '首页',
affix: true,
},
children: [
{
path: 'index',
name: 'Index',
component: () => import('@/views/index'),
meta: {
title: '首页',
affix: true,
},
},
],
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/Login.vue'),
hidden: true,
},
{
path: '/403',
name: '403',
component: () => import('@/views/error/403.vue'),
hidden: true,
},
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
hidden: true,
},
]
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('token');
if (!token) {
next('/login');
} else {
next();
}
}
})
4.7 header用户信息模块
header/index.vue
logo/index.vue
vue3-admin
layout/index.vue
</a-layout-header>
<!-- content -->
<a-layout-content :style="{ margin: '24px 16px', padding: '24px', background: '#fff', minHeight: '280px' }">
Content
</a-layout-content>
4.8 登录后获取用户getInfo
需要在mockjs->api->store三端都配置好
mockjs
import Mock from 'mockjs'
/* login */
Mock.mock(
'/api/login',
'post',
(req) => {
const { password, username } = JSON.parse(req.body)
if (username === 'admin' && password === '123456') {
return {
code : 200,
token:'admin-token'
}
} else if(username === 'editor' && password === '123456') {
return {
code : 200,
token:'editor-token'
}
} else if(username === 'test' && password === '123456') {
return {
code : 200,
token:'test-token'
}
}
}
)
/* getInfo */
Mock.mock(
'/api/getInfo',
'post',
(req) => {
const {token} = req.body
let roles = ['admin']
let username = 'admin'
if(token == 'admin-token') {
roles = ['admin']
username = 'admin'
}
if (token == 'editor-token') {
roles = ['editor']
username = 'editor'
}
if (token == 'test-token') {
roles = ['test']
username = 'test'
}
return {
code : 200,
msg: 'success',
data: {
roles,
username,
avatar:'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif'
}
}
}
)
api/user.js
import instance from './request'
export const Login = (res) =>{
return instance({
url:'/login',
method: 'POST',
data:res
})
}
export const UserInfo = (res) =>{
return instance({
url:'/getInfo',
method: 'POST',
data:res
})
}
export default {Login,UserInfo}
最后是store
import { createStore } from 'vuex'
import {Login,UserInfo} from '@/api/user'
export default createStore({
state:{
token:localStorage.getItem('token'),
avatar:localStorage.getItem('avatar'),
username:localStorage.getItem('username'),
roles:[]
},
mutations:{
SET_TOKEN:(state,token) =>{
state.token = token
},
SET_AVATAR:(state,avatar) =>{
state.avatar = avatar
},
SET_USERNAME:(state,username) =>{
state.username = username
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
},
actions:{
/* login /
LoginResult({commit}, userInfo) {
return new Promise((resolve,reject) =>{
Login(userInfo).then(response =>{
const {code ,token} = response.data
if(code == 200) {
localStorage.setItem('token',token)
commit('SET_TOKEN',token)
}
resolve(response.data)
}).catch(error =>{
reject(error)
})
})
},
/ getINfo */
GetInfo ({commit},token) {
return new Promise((resolve,reject) => {
UserInfo(token).then(response =>{
const {code ,data} = response.data
if(code == 200) {
commit('SET_AVATAR',data.avatar)
commit('SET_USERNAME',data.username)
commit('SET_ROLES',data.roles)
resolve(response.data)
}
}).catch(error =>{
reject(error)
})
})
},
/* logout */
LogoutResult({commit}) {
commit('SET_TOKEN','')
commit('SET_AVATAR','')
commit('SET_USERNAME','')
localStorage.removeItem('token')
localStorage.removeItem('username')
localStorage.removeItem('avatar')
}
},
getters:{
token: state => state.token,
avatar: state => state.avatar,
username: state => state.username,
roles: state => state.roles,
}
})
搞定后在login.vue中添加store中的getInfo
vue3-admin
4.9 用户登出logout
这里我们使用ant ui的modal和message,包括vue-router
5 路由权限篇
5.1 layout的logo组件
首先layout/logo/logo.vue
Vue3-admin
在layout的index.vue引入logo.vue
import AppLogo from "./logo/logo.vue";
5.2layout的menu组件
引入iconfont
//第一步
import { createFromIconfontCN } from "@ant-design/icons-vue";
//第二步
const IconFont = createFromIconfontCN({
scriptUrl: "//at.alicdn.com/t/font_1617479_lw8fprdjcs.js",
});
传入collapsed
props: {
collapsed: {
type: Boolean
}
}
然后创建menu组件,给组件传参数
v-model:open-keys="openKeys"
v-model:selected-keys="selectedKeys"
mode="inline"
theme="dark"
:inline-collapsed="collapsed"
vue 项目的权限控制都是通过 addRoutes来实现的。简单说就是:用户登录之后会返回一个权限凭证Token,用户在根据这个Token去问服务端询问自己的权限,辟如服务端返回权限是['editor'],前端再根据这个权限动态生成他能访问的路由,再通过addRoutes进行动态的路由挂载。
创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。
router.js
在登录请求中,获得用户的权限数据,前端根据权限数据,只显示当前用户能访问的权限内菜单
界面权限
如果用户没有登录,手动在地址栏输入管理界面的地址,需要跳转到登录页面;如果用户已经登录,手动在地址栏输入非权限内的地址,需要跳转到404页面
export const asyncRoutes = [
{
path: '/',
component: Layout,
redirect: '/index',
meta: {
title: '首页',
affix: true,
},
children: [
{
path: 'index',
name: 'Index',
component: () => import('@/views/index'),
meta: {
title: '首页',
affix: true,
},
},
],
},
{
path: '/demo',
component: Layout,
redirect: '/demo/table',
meta: {
title: '组件',
icon: 'apps-line',
roles: ['admin', 'editor']
},
children: [
{
path: 'table',
name: 'Table',
component: () => import('@/views/demo/table'),
meta: {
title: '表格',
icon: 'table-2',
},
},
{
path: 'icon',
name: 'Icon',
component: () => import('@/views/demo/icon'),
meta: {
title: '图标',
icon: 'remixicon-line',
},
},
],
},
{
path: '/test',
component: Layout,
redirect: '/test/test',
meta: {
title: '动态路由测试',
icon: 'test-tube-line',
},
children: [
{
path: 'test1',
name: 'Test1',
component: () => import('@/views/test1'),
meta: {
title: '路由测试1',
icon: 'test-tube-line',
},
},
{
path: 'test2',
name: 'Test2',
component: () => import('@/views/test2'),
meta: {
title: '路由测试2',
icon: 'test-tube-line',
},
},
{
path: 'test3',
name: 'Test3',
component: () => import('@/views/test3'),
meta: {
title: '路由测试3',
icon: 'test-tube-line',
},
},
],
},
{
path: '/error',
name: 'Error',
component: Layout,
redirect: '/error/403',
meta: {
title: '错误页',
icon: 'error-warning-line',
},
children: [
{
path: '403',
name: 'Error403',
component: () => import('@/views/error/403'),
meta: {
title: '403',
icon: 'error-warning-line',
},
},
{
path: '404',
name: 'Error404',
component: () => import('@/views/error/404'),
meta: {
title: '404',
icon: 'error-warning-line',
},
},
],
},
{
path:'/system',
name:'system',
component: Layout,
redirect: '/system/account',
meta:{
title:'system',
icon:'system',
roles: ['admin']
},
children:[
{
path:'account',
name:'account',
component: ()=> import('@/views/system/account'),
meta:{
title:'account',
icon:'account'
}
},
{
path:'group',
name:'group',
component: ()=> import('@/views/system/group'),
meta:{
title:'group',
icon:'group'
}
}
]
}
]
阅读 207
写下你的留言
:,。视频小程序赞,轻点两下取消赞在看,轻点两下取消在看