欢迎来到本次教程。这篇文章旨在深入解析在 B 站上的 Vue3 后台管理项目,同时也为您提供了创建此类项目的实践思路。我们将通过这个笔记系统地梳理一个项目的整体框架,涵盖了我们在 Vue 课程中学习的主要内容。
在此过程中,我尝试使用 Vant4 组件库替代了原先的 Element-Plus 组件,但请注意,由于 Vant4 和 Element-Plus 有所不同,有些 Element-Plus 组件可能无法找到完全相同的 Vant4 替代品。因此,本文中的 Vant4 代码仅供参考。
由于时间紧迫,尽管我已尽最大努力保证内容的准确性和完整性,但仍有可能存在一些疏漏或不够完善的地方。如果您在阅读过程中发现任何需要改进的地方,非常欢迎指出,您的建议将对我来说极为宝贵。
在此,小天同学再次向同学们表示感谢。我希望这篇文章能为你的学习提供有价值的参考,让我们一起探索 Vue3 的世界。
使用到的技术:
附本文使用技术的官网链接
vue3:https://cn.vuejs.org/
element-plus:http://element-plus.org/zh-CN/
vant4:https://vant-contrib.gitee.io/vant/#/zh-CN
pinia:https://pinia.web3doc.top/
vuex:https://vuex.vuejs.org/zh/
JSON-SERVER:https://github.com/typicode/json-server
axios:https://www.axios-http.cn/docs/intro
项目出处:[https://www.bilibili.com/video/BV1no4y1Y7gF?p=1&vd_source=6f495a4394638d66c7e55ede1b042f9e](https://www.bilibili.com/video/BV1no4y1Y7gF?
npm init vue@latest
按键盘的箭头按键选择yes或者no,回车就是下一步,以下是我的选择
只有vue Router和pinia我选择了yes,其他都是no
从这一步开始,所有的“在终端中打开,全部指的是右键vue-project再点击在集成终端中打开!!
右键点击vue-project的位置,选择在集成终端中打开
输入以下指令
npm i vite
npm i vant
至此,我们的项目基本内容已经创建完毕
(注:想要检查安装的文件版本,可以控package-lock.json中寻找)
最后一步,把没用的东西都删了,保留最基本的组件项目,
这些文件都可以把里面的内容删除成上图所示的样式。
接下来我们当然是要改写我们的相关配置了
通常情况下我们要考虑main.js、路由、pinia的stores的配置,部分文件也需要修改vite.config.js的配置。
(注意,以下采用了两种不同的引入方式:全局引入和部分引入,使用时可以二选一)
在main.js中加入以下代码
import { createApp } from 'vue';
// 1. 引入你需要的组件
import { Button } from 'vant';
// 2. 引入组件样式
import 'vant/lib/index.css';
const app = createApp();
// 3. 注册你需要的组件
app.use(Button);
之后每使用一个组件,就在main.js中添加一个就可以了,比如我想添加一个轮播图的组件,就上官网查一下全局引入需要什么代码,复制进去就可以了
比如我想引入一个轮播图,就从vant找到轮播他,打开main.js,复制代码引入就可以。
如果是部分引入,就需要先安装一个插件,再分别引入,操作方法如下:
1.在终端中输入
# 通过 npm 安装
npm i unplugin-vue-components -D
# 通过 pnpm 安装
pnpm add unplugin-vue-components -D
2.打开vite.config.js
3.从vant4官网–快速上手–方法二–配置插件复制以下内容
将原本没有的部分,添加到我们刚刚打开的vite.config.js中。
之后就非常简单,我们的组件需要哪一个就直接在script中引入就可以了。
比如我想引入一个轮播图,直接上vant4官网找相关的代码,
只需要这一行引入。
注意:如果使用的是以下写法,不需要引入这一行代码,可以直接使用
1
2
3
4
如果你从一开始就跟着我们的操作,左边的列表里是有router的字样的,里面有一个文件index.js,那就只需要
如果你一开始没有自动安装路由,那可就遭老罪喽
你需要在终端中输入
npm install vue-router@4
如果您从一开始就是跟我们的步骤做,那您相对来说比较轻松,直接跳到第2部分。
但是如果不是,那就遭老罪喽。请执行以下步骤:
1.在终端中输入以下代码:
npm install pinia
2.将以下代码补充到main.js中
import { createPinia } from 'pinia'
app.use(createPinia())
3.在src文件夹下创建一个文件夹,名叫store,在此文件夹下创建一个文件名为:index.js
并添加以下代码
import { defineStore } from 'pinia'
// useStore 可以是 useUser、useCart 之类的任何东西
// 第一个参数是应用程序中 store 的唯一 id
export const useStore = defineStore('main', {
// other options...
})
我们把pinia配置好了以后,现在主要的内容给写进去
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUsersStore = defineStore("main", {
// 这个相当于是一个数据仓库,是处理数据用的。
state: () => {
return {
// 比如我可以在这里定义年龄,姓名等等
// name: "xiaotian",
// age: 18,
// id: 1
};
},
// 这个相当于是一个计算属性,也是处理数据用的。比如说,我要把年龄加100岁,
//那么我就可以在这里写一个方法,然后在页面上调用这个方法,就可以得到加了100岁的年龄了。
getters: {
// 比如我想给state的年龄+100
return state.age + 100;
},
//这个相当于是一个method,写一些方法,比如处理数据的方法,比如点击事件的方法,比如axios请求的方法,比如定时器的方法...
actions: {
}
})
在你的组件中(就是写你页面内容的.vue结尾的文件中,引入以下代码)
我的建议是在store(如果你用的是Vuex生成的是store,如果你用的是pinia生成的是stores)文件夹下,创建一个state文件,并创建一个用户文件userInfo.JS和一个总页面index.js
如下图所示
// 注意,这里from后面是路径,别写错了,不行就先输入 ../ 看看弹出来的是啥
import { useUsersStore } from '@/stores/counter.js'
// 这里是做响应式用的,在子页面修改的数据,通过这种方式可以完成同步,原因官网上面写的很清楚
import { storeToRefs } from 'pinia'
// 这里的useUsersStore(),和你store/index.js中配置的是一样的
const store = useUsersStore();
注意最后一行使用的useUsersStore();和你store/index.js中配置的是一样的。
当然,官网中明确写道这里不能使用解构赋值
需要使用的话,加这个
也就是把原来的
//修改前
const {name,doubleCount} = store
//修改后
const {name,doubleCount} = storeToRefs(store)
// 加了一个storeToRefs()
至此,我们pinia的基本框架已经配置完成。
参考文章地址:https://juejin.cn/post/7043424909472563208#heading-4
分为以下几个部分:
官网地址:https://www.npmjs.com/package/json-server
安装指令(同样是终端中安装即可)
npm i json-server
// 想要全局安装选下面这个
npm install -g json-server
在你本机创建一个文件夹,然后新建一个 json 文件,再填入数据即可。
步骤:
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
运行指令
json-server --watch db.json
npm install -g json-server
如果是这个报错,这里就要修改配置
方法如下(参考文章:http://t.csdn.cn/JYIFA)
这一步你可以在浏览器修改,也可以在postman修改,这里我使用的是postman
postman下载地址:点击下载
https://www.postman.com/downloads/?utm_source=postman-home
接下来可以增添数据
输入您的posts链接
这里就会输出相关代码
我现在把相关的数据进行修改
就要确定他的id
公式:
http://localhost:3000/posts/{id}
比如我想把第一个修改为
{ "title": "php",
"author": "xiaotiantian" }
你进入终端中,通过json-server --watch db.json运行以后,红框部分就是你查的数据的全部信息。
如果你想查询posts数据中,id为1的数据,就这么写
http://localhost:3000/posts/{id}
如果你想查询title为php的数据,你就这么写
http://localhost:3000/posts?title=php
如过你想查张三,男性,你就用到"&"
http://localhost:3000/posts?name=php&sex=man
我想把数据中的xiaotiantian改成tian
步骤就是把操作改成patch,其他部分差不多
修改完成
如果你想要修改我们的端口,不再使用3000,那可以使用以下指令修改
json-server -p 8888 db.json
这样打开以后端口就写改成了8888
终端中输入以下代码修改主机号
json-server --host 0.0.0.0 db.json
如果你想了解更多内容,
请移步:https://www.breword.com/typicode-json-server
中文:
官网地址:https://www.axios-http.cn/docs/intro
首先,在Vue 3项目中下载Axios库,可以使用npm或yarn进行安装。在终端中进入项目目录,执行以下命令进行安装:
安装npm:
// 下面两个二选一
npm install axios
cnpm install axios -g
我的习惯是,在src目录下建立一个util文件夹,这个文件夹下建立两个文件,
分别命名为:
requese.js
service.js
如下图所示:
在我们的service.js文件中,引入axios,以及状态管理库的内容
// axios引入
import axios from "axios"
//pinia引入
import store from "../store/index.js"
//创建一个axios实例
const Service = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
request.js我们等会再配置。
你写一个vue页面,就加一段路由配置,比如你写了login.vue页面。
// 登陆页面
const routes = [
{
path: "/login",
name: "login",
component: () =>
import ("../views/pages/login.vue") // 懒加载引入登录页面组件
}
]
如果你想写一个嵌套路由,比如这种类型
那你就需要写一个嵌套路由。
他的基本格式是:
const routes = [
{
path: "/",
name: "layout",
component: () =>
import ("../views/Layout/LayOut.vue"),
redirect: "/index",
// 子路由/嵌套路由
children: [{
path: "/index",
name: "index",
component: () =>
import ("../views/pages/index.vue") // 懒加载引入首页组件
},]
}
]
也就是在配置中多加入一个children项目。
我的外边框(深色部分组件名称是layout),
嵌套部分组件名称是:index.vue,roles.vue,user.vue,goods.vue
那我一项一项写进去就可以了
{
path: "/",
name: "layout",
component: () =>
import ("../views/Layout/LayOut.vue"),
redirect: "/index",
// 子路由/嵌套路由
children: [{
path: "/index",
name: "index",
component: () =>
import ("../views/pages/index.vue") // 懒加载引入首页组件
},
{
path: "/roles",
name: "roles",
component: () =>
import ("../views/pages/rolesList.vue") // 懒加载引入角色列表组件
},
{
path: "/user",
name: "user",
component: () =>
import ("../views/pages/userList.vue") // 懒加载引入用户列表组件
},
{
path: "/goods",
name: "goods",
component: () =>
import ("../views/pages/goodsList.vue") // 懒加载引入商品列表组件
}
]
}
完整配置:
import { createRouter, createWebHashHistory } from 'vue-router'
import store from "../store/index.js" // 引入Vuex的store实例
// 路由配置
const routes = [
// 登陆页面
{
path: "/login",
name: "login",
component: () =>
import ("../views/pages/login.vue") // 懒加载引入登录页面组件
},
{
path: "/",
name: "layout",
component: () =>
import ("../views/Layout/LayOut.vue"),
redirect: "/index",
// 子路由/嵌套路由
children: [{
path: "/index",
name: "index",
component: () =>
import ("../views/pages/index.vue") // 懒加载引入首页组件
},
{
path: "/roles",
name: "roles",
component: () =>
import ("../views/pages/rolesList.vue") // 懒加载引入角色列表组件
},
{
path: "/user",
name: "user",
component: () =>
import ("../views/pages/userList.vue") // 懒加载引入用户列表组件
},
{
path: "/goods",
name: "goods",
component: () =>
import ("../views/pages/goodsList.vue") // 懒加载引入商品列表组件
}
]
}
]
// 生成hash路由对象
const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
/**
* to:即将要进入的目标
* from:正要离开的路由
* next:只有执行next()页面才会进行跳转
*/
// 判断用户是否登录
console.log("store", store.state.uInfo)
const uInfo = store.state.uInfo.userInfo
if (!uInfo.username) {
// 未登录,跳转到login
if (to.path === "/login") {
next()
return
}
next("/login")
} else {
next()
}
})
// 暴露路由对象
export default router
这一部分可以说是项目当中最难配置和理解的了。涉及到多种业务逻辑。如列表的登录,增删改查等等
这个部分的代码可以按照:
html+css->axios请求配置(配一次)->思考组件包含的功能->aixos基本的逻辑配置->vue组件逻辑->axios配置
这个顺序完成。
我们先写登录页面怎么实现跳转
这个最基础的写法是…
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<button><a href="登录地址">a>button>
body>
html>
我不建议这么写,因为这么写没分。
html和css我们就不细说了,我这里直接给出
登录
那在这个组件里的script怎么写呢?
那首先我们是要访问我们库里面有没有相关的数据在,比如账号、密码、个人信息等等,
并且核实账号密码是否正确
我们打开service.js写入以下部分**(看注释,只写新增部分!!最上边的代码之前我已经配置了,
不信点这里!!(点击这里))**
//这里是原本就有的,之前axios叫你们配置过的部分
import axios from "axios"
import store from "../store/index.js"
// 使用create创建axios实例
const Service = axios.create({
timeout:8000,
//这里写URL地址/接口地址等
baseURL:"http://122.114.229.181:3000/api/private/v1/",
headers:{
"Content-type":"application/json;charset=utf-8",
"Authorization":store.state.uInfo.userInfo.token
}
})
// ******************************************
//下面是新增的部分
// post请求
export const post=config=>{
return Service({
...config,
method:"post",
data:config.data
})
}
// get请求
export const get=config=>{
return Service({
...config,
method:"get",
params:config.data
})
}
// put请求
export const put=config=>{
return Service({
...config,
method:"put",
data:config.data
})
}// delete请求
export const del=config=>{
return Service({
...config,
method:"delete"
})
}
当然了,我们还需要状态管理器store/userInfo.js。
export default {
state: {
// 定义一个名为 userInfo 的变量,其值为一个对象。
// 初始值为通过判断 localStorage 中是否存在名为 loginData 的数据,
// 如果存在则将该数据解析为一个 JavaScript 对象,否则设置为空对象 {}。
userInfo: (localStorage.getItem('loginData') && JSON.parse(localStorage.getItem('loginData'))) || {},
},
actions: {
// 定义一个名为 setUserInfo 的 mutation 方法来修改 userInfo 变量的值。
// 这个方法接收两个参数:state 和 uInfo,其中 state 是当前模块的状态对象,
// 而 uInfo 是用于更新 userInfo 的新值。
setUserInfo(state, uInfo) {
state.userInfo = uInfo;
},
},
};
如果你的程序中要写很多个状态管理,我建议在stores文件夹下新建一个index.js文件
import { createStore } from 'vuex'
import uInfo from "./state/userinfo.state.js"
export default createStore({
// 数据比较多,分模块
modules: {
uInfo
}
})
以上内官网都有,
传送门:(点这里)
链接:https://www.axios-http.cn/docs/req_config
这里既然是登陆页面,就要考虑用户登录信息的核对和保存。
既然如此,我们首先想到需要连接状态管理器、路由以及响应式,于是我们导入以下代码
import { ref } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
既然我们我们需要发送api请求,我们就需要使用axios
这时候我们之前建立的util/request.js就排上了用场,我们把它当作一个中转站
我们打开util文件夹下的request.js文件,进行一个登录请求的配置
代码如下:
// 导入service中导出的HTTP方法
import { post, get, put, del } from "./service"
// 登录API
export const loginApi = data => {
return post({
url: "/login",
data
})
}
之后我们再导入登录请求的API
接下来的思路就是:
1.引入了以后,当然要调用store和router,所以我们要创建一个pinia实例和一个路由实例,同时,创建一个响应式应用,储存登陆数据。还要用commit方法更新用户信息以及本地储存。
于是我写出以下代码,注意,代码是对上述login.vue代码的补充
登录页面可以添加一个路由守卫,当登录了以后会怎么样,如果没有登录会怎么样。
详细代码请看注释路由守卫部分
至此,router.js的配置完全结束,大家看一下这一段完整代码
import { createRouter, createWebHashHistory } from 'vue-router'
import store from "../store/index.js" // 引入Vuex的store实例
// 路由配置
const routes = [
// 登陆页面
{
path: "/login",
name: "login",
component: () =>
import ("../views/pages/login.vue") // 懒加载引入登录页面组件
},
{
path: "/",
name: "layout",
component: () =>
import ("../views/Layout/LayOut.vue"),
redirect: "/index",
// 子路由/嵌套路由
children: [{
path: "/index",
name: "index",
component: () =>
import ("../views/pages/index.vue") // 懒加载引入首页组件
},
{
path: "/roles",
name: "roles",
component: () =>
import ("../views/pages/rolesList.vue") // 懒加载引入角色列表组件
},
{
path: "/user",
name: "user",
component: () =>
import ("../views/pages/userList.vue") // 懒加载引入用户列表组件
},
{
path: "/goods",
name: "goods",
component: () =>
import ("../views/pages/goodsList.vue") // 懒加载引入商品列表组件
}
]
}
]
// 生成hash路由对象
const router = createRouter({
history: createWebHashHistory(),
routes
})
//***************************************************
//*******************路由守卫在这里!!****************
//***************************************************
// 路由守卫
router.beforeEach((to, from, next) => {
/**
* to:从哪个页面
* from:到哪个页面
* next:只有执行next()页面才会进行跳转
*/
// 判断用户是否登录
console.log("store", store.state.uInfo)
const uInfo = store.state.uInfo.userInfo
if (!uInfo.username) {
// 未登录,跳转到login
if (to.path === "/login") {
next()
return
}
next("/login")
} else {
next()
}
})
// 暴露路由对象
export default router
我们的登陆页面及其功能详解到此结束。
与此同时,我们还需要写入登录
我们要做的是请求数据
在element-plus里面使用的是这个按钮,绑定了一个时间,再vant的按钮中可以随便找一个,button,绑定一个事件就好。
const handleLogin = async () => { //
const res = await loginApi(loginData.value); //
if (res.data) { //
store.commit('setUserInfo', res.data); //
localStorage.setItem('loginData', JSON.stringify(res.data)); //
router.push({ path: '/' }); //
}
};
现在考虑,如果登录正在加载,或服务器出错应该弹出提示,这个提示的信息也要在axios文件中配置
import { Toast } from 'vant';
// 请求拦截器
axios.interceptors.request.use(config => {
// 显示 loading
Toast.loading({
message: 'Loading...',
forbidClick: true, // 禁止点击背景
loadingType: 'spinner', // loading 样式
});
return config; // 返回请求配置
});
// 响应拦截器
axios.interceptors.response.use(response => {
// 隐藏 loading
Toast.clear();
const data = response.data; // 获取响应数据
if (data.code !== 200) {
// 处理错误
Toast.fail(data.msg || '服务器出错'); // 显示错误提示
return Promise.reject(data); // 返回 rejected 状态的 Promise
}
return data; // 返回响应数据
}, error => {
// 隐藏 loading
Toast.clear();
Toast.fail('服务器错误'); // 显示错误提示
return Promise.reject(error); // 返回 rejected 状态的 Promise
});
至此,我们的service.js已经基本配置完毕。
现提供完整代码
import axios from "axios"
import store from "../store/index.js"
import { Toast } from 'vant';
// 创建一个axios实例,并设置请求超时时间、基础URL和请求头
const Service = axios.create({
timeout: 8000,
baseURL: "http://122.114.229.181:3000/api/private/v1/",
headers: {
"Content-type": "application/json;charset=utf-8",
"Authorization": store.state.uInfo.userInfo.token
}
})
// 请求拦截器
axios.interceptors.request.use(config => {
// 显示 loading
Toast.loading({
message: 'Loading...',
forbidClick: true, // 禁止点击背景
loadingType: 'spinner', // loading 样式
});
return config; // 返回请求配置
});
// 响应拦截器
axios.interceptors.response.use(response => {
// 隐藏 loading
Toast.clear();
const data = response.data; // 获取响应数据
if (data.code !== 200) {
// 处理错误
Toast.fail(data.msg || '服务器出错'); // 显示错误提示
return Promise.reject(data); // 返回 rejected 状态的 Promise
}
return data; // 返回响应数据
}, error => {
// 隐藏 loading
Toast.clear();
Toast.fail('服务器错误'); // 显示错误提示
return Promise.reject(error); // 返回 rejected 状态的 Promise
});
// post请求
export const post = config => {
// 使用Service实例发送post请求,并传入请求配置
return Service({
...config,
method: "post",
data: config.data
})
}
// get请求
export const get = config => {
// 使用Service实例发送get请求,并传入请求配置
return Service({
...config,
method: "get",
params: config.data
})
}
// put请求
export const put = config => {
// 使用Service实例发送put请求,并传入请求配置
return Service({
...config,
method: "put",
data: config.data
})
}
// delete请求
export const del = config => {
// 使用Service实例发送delete请求,并传入请求配置
return Service({
...config,
method: "delete"
})
}
现在将login.vue的代码完整版整理出来
登录
在这个页面里我要实现用户的增删改查功能,也就是要获取数据并且完成修改
老规矩,html直接给出,因为都是element的配置。vant只要找到相同的功能直接写就可以
角色列表
新建角色
在上述代码中,我想要实现数据的实时响应,这里做了个单/双向绑定(注意这并不一定是我写的,有些事element-plus自带的,不需要去看)
:data=“rolesList”
v-model=“formData.roleName”
v-model=“formData.roleDesc”
以及按钮触发的方法
这里可以理解为一个点击事件,做了一个绑定,点击以后自动触发函数。
@close=“clearForm”//‘编辑角色’:‘新建角色’(这个是关闭事件)
@click=“goToHome”
@click=“dialogFormVisible=true”//新建角色
@click="editRow(item)//编辑
@click=“deleteRow(item)”//删除
@click="submitForm(userForm)//确定按钮
我们既然想要获取用户信息,肯定要通过axios发送请求,增删改查等等,也就是post get delect等等
那首先我们就要考虑axios配置
无非就是获取相关的api
打开util/request文件,把这些代码加进去
// 以上是之前写过的部分
// 导入service中导出的HTTP方法
import { post, get, put, del } from "./service"
// 登录API
export const loginApi = data => {
return post({
url: "/login",
data
})
}
// *******************************
// ******以下是新增部分************
// *******************************
// 获取角色API
export const getRolesApi = data => {
return get({
url: "roles",
data
})
}
// 新建角色API
export const addRolesApi = data => {
return post({
url: "roles",
data
})
}
// 编辑角色API
export const editRolesApi = data => {
return put({
url: `roles/${data.id}`,
data
})
}
// 删除角色API
export const rolesDeleteApi = data => {
return del({
url: `roles/${data.id}`
})
}
(其实内部结构差不多,文件名也写的很清楚。如果你喜欢,export const dog=data都可以。)
接下来我们应该处理script里面的内容,步骤如下:
1.数据和表单的响应式(数据可以用ref,表单可以用composables中的useForm)
2.调用接口
3.点击事件的逻辑。
打开roleList.vue文件
import { ref } from 'vue';
import { useForm } from '@/composables';
import { getRolesApi, addRolesApi, editRolesApi, rolesDeleteApi } from '@/util/request.js';
同时创建
响应式接收数据
const rolesList = ref([]);
const dialogFormVisible = ref(false);
const formData = ref({ roleName: '', roleDesc: '' });
const userForm = ref();
在表单中,创建一个规则,标记某个项目必须填写
const rules = {
roleName: {
required: true,
message: '此项必填',
trigger: 'blur',
},
};
我们还需要验证表单数据是否符合规则
const { validate } = useForm(userForm, formData, rules);
在上面的代码中,当表单提交时,我们调用validate 方法进行验证。如果验证通过,就执行表单提交操作。如果验证失败,validate 方法会返回 false,我们可以在组件中根据这个返回值来进行错误提示等操作。
我们表单已经制作完成,下面就要执行以下步骤:
我们的思路是,通过API实现各种功能。通过异步处理就可以了
// 定义获取角色列表的函数
const getList = async () => {
const res = await getRolesApi();
rolesList.value = res.data;
};
// 定义提交表单的函数
const submitForm = async () => {
const res = await validate();
if (!res) {
return;
}
if (formData.value.id) {
await editRolesApi(formData.value);
} else {
await addRolesApi(formData.value);
}
dialogFormVisible.value = false;
getList();
};
// 定义编辑角色的函数
const editRow = (row) => {
dialogFormVisible.value = true;
const { roleName, roleDesc, id } = row;
formData.value = { id, roleName, roleDesc };
};
// 定义删除角色的函数
const deleteRow = async (row) => {
await rolesDeleteApi(row);
getList();
};
// 定义清空表单的函数
const clearForm = () => {
formData.value = { roleName: '', roleDesc: '' };
};
// 初始化时获取角色列表
getList();
如果提交表单函数看不懂,我这里写了详细注释
// 定义提交表单的函数
const submitForm = async () => {
// 首先调用了validate()方法来验证表单数据
// validate()方法是由上文引入的useForm自定义Hook返回的方法之一,用于验证表单数据是否符合预设的规则
// validate()方法返回一个Promise,我们使用await语句等待这个Promise解析
const res = await validate();
// 如果验证失败(validate()方法返回的Promise解析为false),
// 那么我们立即返回,停止函数的执行,避免提交无效的表单数据
if (!res) {
return;
}
// 如果表单数据(formData.value)中包含id字段,那么我们认为用户正在编辑一个已存在的角色
// 在这种情况下,我们调用editRolesApi()方法来更新这个角色的信息
// editRolesApi()方法接受一个包含角色数据的对象作为参数,并返回一个Promise,我们使用await语句等待这个Promise解析
if (formData.value.id) {
await editRolesApi(formData.value);
} else {
// 如果表单数据(formData.value)中不包含id字段,那么我们认为用户正在创建一个新的角色
// 在这种情况下,我们调用addRolesApi()方法来创建新的角色
// addRolesApi()方法接受一个包含角色数据的对象作为参数,并返回一个Promise,我们使用await语句等待这个Promise解析
await addRolesApi(formData.value);
}
// 不论是创建新的角色还是编辑已存在的角色,我们都会在表单提交成功后隐藏表单对话框
// 我们通过将dialogFormVisible.value设为false来隐藏对话框
dialogFormVisible.value = false;
// 表单提交成功后,我们需要获取最新的角色列表以便在页面上显示
// 我们通过调用getList()方法来获取最新的角色列表
getList();
};
好了,下面是roleList.vue完整代码
角色列表
新建角色
接下来是用户列表
老规矩,html代码直接给出
先给element-plus的代码
首页
账号列表
新建用户
编辑
删除
取消
确定
取消
确定
(以下是vant平替版本文档是element修改的,很多组件我没找着vant4可以替代,以下vant4代码仅供参考)
新建用户
取消
确定
取消
确定
同样上面我已经绑定了数据
我们同样需要发送请求,打开axios进行配置,步骤:
1.uitl/request.js
2.输入以下代码
// 获取用户列表API
export const userListApi = data => {
return get({
url: "/users",
data
})
}
// 新增用户API
export const userAddApi = data => {
return post({
url: "/users",
data
})
}
// 更改用户状态API
export const userChangeStateApi = data => {
return put({
url: `users/${data.id}/state/${data.mg_state}`,
data
})
}
// 更改用户信息API
export const userChangeInfoApi = data => {
return put({
url: `users/${data.id}`,
data
})
}
// 删除用户API
export const userDeleteApi = data => {
return del({
url: `users/${data.id}`
})
}
接下来写入我们的方法,以下是条件限制部分
这个我就不一一详解了,直接写注释里去了
这一部分我们写入的是用户基本信息的格式(规则),以及提交单的数组等
// 使用Vue 3的reactive方法创建一个响应性对象
const data = reactive({
// searchParams对象包含了搜索参数
searchParams: {
query: "", // 查询字符串,默认为空
pagesize: 5, // 每页显示的数据量,默认为5
pagenum: 1 // 当前的页数,默认为第一页
},
total: 0, // 用于存储数据的总数量
userList: [], // 存储用户列表的数组
dialogFormVisible: false, // 控制添加用户的对话框是否可见,默认为不可见
dialogFormEVisible: false, // 控制编辑用户的对话框是否可见,默认为不可见
formData: { // 添加用户的表单数据
username: "", // 用户名
password: "", // 密码
email: "", // 邮箱
mobile: "", // 手机号
},
formData2: { // 编辑用户的表单数据
id: "", // 用户ID
email: "", // 邮箱
mobile: "", // 手机号
},
// 添加用户的表单验证规则
rules: {
username: [ // 用户名规则
{ required: true, message: "此项为必填", trigger: "blur" } // 必填项,失去焦点时触发
],
password: [ // 密码规则
{ required: true, message: "此项为必填", trigger: "blur" } // 必填项,失去焦点时触发
],
email: [ // 邮箱规则
{
required: false,
pattern: /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-])+$/, // 邮箱的正则表达式规则
message: "请填写正确邮箱",
trigger: "blur"
}
],
mobile: [ // 手机号规则
{
required: false,
pattern: /^[1][3,4,5,7,8][0-9]{9}$/, // 手机号的正则表达式规则
message: "请填写正确手机号",
trigger: "blur"
}
]
},
// 编辑用户的表单验证规则,规则同上
rules2: {
email: [
{
required: false,
pattern: /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-])+$/,
message: "请填写正确邮箱",
trigger: "blur"
}
],
mobile: [
{
required: false,
pattern: /^[1][3,4,5,7,8][0-9]{9}$/,
message: "请填写正确手机号",
trigger: "blur"
}
]
}
})
以下是功能性的方法
// 定义一个函数 searchList,它通过调用 userListApi 并传递搜索参数来获取用户列表
const searchList = () => {
userListApi(data.searchParams).then(res => {
if (res.data) { // 如果响应中有数据
data.userList = res.data.users // 将响应数据中的用户列表存到 data.userList
data.total = res.data.total // 将响应数据中的用户总数存到 data.total
}
})
}
// 定义一个函数 addUser,用于显示添加用户的表单
const addUser = () => {
data.dialogFormVisible = true // 将 data.dialogFormVisible 设置为 true,通常用于在 UI 上显示表单
}
// 定义一个函数 submitForm,用于提交添加用户的表单
const submitForm = (formEl) => {
formEl.validate(res => { // 验证表单字段
if (!res) { // 如果验证不通过
return // 直接返回,不再继续执行
}
// 调用 userAddApi 来添加用户,并传递表单数据
userAddApi(data.formData).then(res => {
if (res.data) { // 如果响应中有数据
data.dialogFormVisible = false // 隐藏表单
// 重置表单数据
data.formData = {
username: "",
password: "",
email: "",
mobile: "",
}
searchList() // 添加用户成功后,重新获取用户列表
}
})
})
}
// 定义一个函数 submitEForm,用于提交编辑用户的表单
const submitEForm = (formEl) => {
formEl.validate(res => { // 验证表单字段
if (!res) { // 如果验证不通过
return // 直接返回,不再继续执行
}
// 调用 userChangeInfoApi 来修改用户信息,并传递表单数据
userChangeInfoApi(data.formData2).then(res => {
if (res.data) { // 如果响应中有数据
data.dialogFormEVisible = false // 隐藏表单
searchList() // 修改用户信息成功后,重新获取用户列表
}
})
})
}
// 定义一个函数 switchChange,用于切换用户的状态
const switchChange = row => {
// 调用 userChangeStateApi 来切换用户状态,并传递当前行数据
userChangeStateApi(row).then(res => {
if (res.data) { // 如果响应中有数据
searchList() // 切换用户状态成功后,重新获取用户列表
}
})
}
// 定义一个函数 editRow,用于编辑用户信息
const editRow = row => {
const { email, mobile, id } = row // 从当前行数据中解构出 email, mobile, id
data.dialogFormEVisible = true // 显示编辑用户的表单
// 将 email, mobile, id 存到 data.formData2,这通常会自动填充到表单中
data.formData2.email = email
data.formData2.mobile = mobile
data.formData2.id = id
}
// 定义一个函数 deleteRow,用于删除用户
const deleteRow = row => {
// 调用 userDeleteApi 来删除用户,并传递当前行数据
userDeleteApi(row).then(res => {
searchList() // 删除用户成功后,重新获取用户列表
})
}
// 执行 searchList 函数,获取用户列表
searchList()
// 定义两个 ref,通常用于 Vue 3 Composition API 中的 ref 响应式引用
const userForm = ref()
const userForm2 = ref()
以下是uesrList.vue部分完整代码(element-plus版本)
首页
角色列表
新建角色
编辑
删除
取消
确定
这一段代码没啥难的,老思路先写html
首页
商品列表
{{switchState(scope.row.goods_state)}}
接下来一样是数据的传输,那我们就正常配置axios就好了
打开util/request.js
// 获取商品列表API
export const goodsListApi = data => {
return get({
url: "goods",
data
})
}
至此所有页面完成,axios配置完毕。
现在共享axios的全部代码如下:
// 导入service中导出的HTTP方法
import { post, get, put, del } from "./service"
// 登录API
export const loginApi = data => {
return post({
url: "/login",
data
})
}
// 获取用户列表API
export const userListApi = data => {
return get({
url: "/users",
data
})
}
// 新增用户API
export const userAddApi = data => {
return post({
url: "/users",
data
})
}
// 更改用户状态API
export const userChangeStateApi = data => {
return put({
url: `users/${data.id}/state/${data.mg_state}`,
data
})
}
// 更改用户信息API
export const userChangeInfoApi = data => {
return put({
url: `users/${data.id}`,
data
})
}
// 删除用户API
export const userDeleteApi = data => {
return del({
url: `users/${data.id}`
})
}
// 获取角色API
export const getRolesApi = data => {
return get({
url: "roles",
data
})
}
// 新建角色API
export const addRolesApi = data => {
return post({
url: "roles",
data
})
}
// 编辑角色API
export const editRolesApi = data => {
return put({
url: `roles/${data.id}`,
data
})
}
// 删除角色API
export const rolesDeleteApi = data => {
return del({
url: `roles/${data.id}`
})
}
// 获取商品列表API
export const goodsListApi = data => {
return get({
url: "goods",
data
})
}
(上文中使用的拦截器部分是vant4中的Toast,现在共享的是element-plus版本,代码不同,实现功能类似)
import axios from "axios"
import { ElLoading } from 'element-plus'
import { ElMessage } from 'element-plus'
import store from "../store/index.js"
// 创建一个axios实例,并设置请求超时时间、基础URL和请求头
const Service = axios.create({
timeout: 8000,
baseURL: "http://122.114.229.181:3000/api/private/v1/",
headers: {
"Content-type": "application/json;charset=utf-8",
"Authorization": store.state.uInfo.userInfo.token
}
})
// 请求拦截器:在发送请求之前执行
Service.interceptors.request.use(config => {
// 显示全屏加载动画
loadingObj = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
})
// 返回修改后的请求配置
return config
})
// 响应拦截器:在收到响应之后执行
Service.interceptors.response.use(response => {
// 关闭全屏加载动画
loadingObj.close()
const data = response.data
// 检查响应的状态码,如果不是200或201,显示错误消息
if (data.meta.status != 200 && data.meta.status != 201) {
ElMessage.error(data.meta.msg || "服务器出错")
return data
}
// 返回响应数据
return data
}, error => {
// 关闭全屏加载动画
loadingObj.close()
// 如果发生错误,显示错误消息
ElMessage({
message: "服务器错误",
type: "error",
duration: 2000
})
})
// post请求
export const post = config => {
// 使用Service实例发送post请求,并传入请求配置
return Service({
...config,
method: "post",
data: config.data
})
}
// get请求
export const get = config => {
// 使用Service实例发送get请求,并传入请求配置
return Service({
...config,
method: "get",
params: config.data
})
}
// put请求
export const put = config => {
// 使用Service实例发送put请求,并传入请求配置
return Service({
...config,
method: "put",
data: config.data
})
}
// delete请求
export const del = config => {
// 使用Service实例发送delete请求,并传入请求配置
return Service({
...config,
method: "delete"
})
}
好的,接下来写业务逻辑,每句话的用途我都在注释中标明了。
先提供HTML+css
<template>
<div class="common-layout">
<el-container class="el-container">
<el-header class="common-header flex-float">
<div class="flex">
<img class="logo" src="../../assets/logo.svg">
<h1 class="title">后台管理系统</h1>
</div>
<el-button type="danger" @click="loginOut">退出</el-button>
</el-header>
<el-container>
<el-aside class="common-aside" width="200px">
<el-row class="tac">
<el-col >
<el-menu
active-text-color="#ffd04b"
background-color="none"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
@open="handleOpen"
@close="handleClose"
:router="true"
>
<!-- 这里是账号管理这个分列-->
<el-sub-menu index="1">
<template #title>
<el-icon><Avatar /></el-icon>
<span>账号管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/user">账号列表</el-menu-item>
</el-menu-item-group>
<!-- <el-menu-item index="/">item two</el-menu-item>-->
</el-sub-menu>
<!-- 这里是账号管理分列-->
<el-sub-menu index="2">
<template #title>
<el-icon><Box /></el-icon>
<span>角色管理</span>
</template>
<el-menu-item-group>
<el-menu-item index="/roles">角色列表</el-menu-item>
</el-menu-item-group>
<!-- <el-menu-item index="/">item two</el-menu-item>-->
</el-sub-menu>
</el-menu>
</el-col>
<!-- <router-link to="/index">角色列表</router-link>-->
<!-- <router-link to="/user">用户列表</router-link>-->
</el-row>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
<el-row class="tac">
<el-col :span="12">
<h5 class="mb-2">Custom colors</h5>
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
@open="handleOpen"
@close="handleClose"
>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator One</span>
</template>
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-sub-menu>
<el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<span>Navigator Two</span>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon><document /></el-icon>
<span>Navigator Three</span>
</el-menu-item>
<el-menu-item index="4">
<el-icon><setting /></el-icon>
<span>Navigator Four</span>
</el-menu-item>
</el-menu>
</el-col>
</el-row>
</template>
<style>
.el-container {
height: 100vh;
overflow: hidden;
}
.common-header {
background: #272d41;
display: flex;
}
.common-aside {
background: #303741;
}
.logo {
width: 80px;
}
.title {
color: #f8f8f8;
}
.flex-float {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex {
display: flex;
align-items: center;
}
/*.title{*/
/* background-color: #fefefe;*/
/*}*/
</style>
这段代码中,我们导入了一些必要的库和组件,同时定义了处理菜单展开、折叠和退出登录功能的函数。以下是详细的解释:
import {
Document,
Menu as IconMenu,
Location,
Setting,
} from '@element-plus/icons-vue'; // 导入 Element Plus 的图标组件
import { useStore } from 'vuex'; // 导入 Vuex 的 useStore 函数,用于使用 Vuex 的 store
import { useRouter } from 'vue-router'; // 导入 Vue Router 的 useRouter 函数,用于使用 Vue Router 的实例
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath); // 当菜单展开时,输出展开的菜单项 key 和 keyPath
};
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath); // 当菜单折叠时,输出折叠的菜单项 key 和 keyPath
};
handleOpen
和 handleClose
函数在这个例子中只是简单地输出事件的信息。
const store = useStore(); // 使用 Vuex 的 store
const router = useRouter(); // 使用 Vue Router 的实例
const loginOut = () => {
localStorage.removeItem('loginData'); // 移除本地存储的登录数据
store.commit('setUserInfo', {}); // 通过 commit 函数,调用名为 'setUserInfo' 的 Vuex mutation,将用户信息设置为空对象
router.push({ path: '/login' }); // 使用 Vue Router 的 push 函数,跳转到登录页面
};
总结:这段代码主要导入了所需的库和组件,并定义了处理菜单展开、折叠和退出登录功能的函数。通过使用 Vuex 和 Vue Router,我们能够实现对登录信息和页面跳转的控制。
最后,附本文使用技术的官网链接
vue3:https://cn.vuejs.org/
element-plus:http://element-plus.org/zh-CN/
vant4:https://vant-contrib.gitee.io/vant/#/zh-CN
pinia:https://pinia.web3doc.top/
vuex:https://vuex.vuejs.org/zh/
JSON-SERVER:https://github.com/typicode/json-server
axios:https://www.axios-http.cn/docs/intro
项目出处:https://www.bilibili.com/video/BV1no4y1Y7gF?p=1&vd_source=6f495a4394638d66c7e55ede1b042f9e