赞
踩
基于vue3 和element-plus的电商后台管理系统
用户列表:实现用户的增删改查、分页查询以及分配角色的功能
角色列表
实现角色的增删改查以及分配权限、删除权限功能
权限列表
展示所有权限以及权限等级
商品列表:实现商品的增删改查,分页查询
分类参数: 实现分类参数的增删改查
商品分类:实现商品分类的增删改查以及查看改分类下的所有子类
订单列表
实现修改地址
实现查看物流进度
数据报表
电商后台管理系统里系统整体采用前后端分离的开发模式,其中前端是基于Vue框架的SPA项目
技术栈 :vue3、vue-router、Element-Plus、Axios、Echarts
Mac安装Git_mac git_rockvine的博客-CSDN博客
Git使用教程,最详细,最傻瓜,最浅显,真正手把手教 - 知乎
1、登录
什么时候使用token 什么时候使用session和cookie?
token是后端产生的,前端可以生成token么?
token唯一身份辨识。
2、登录
表单验证规则三步骤:
1、给form表单绑定规则 :rules = rules ;
2、data中编写rules,rules是一个对象,每个属性是一个数组,每个数组是一个对象;
3、el-form-item 中prop绑定对应的规则;
重置表单2步:
1、拿到表单实例对象 el-form 添加 ref="loginFormRef"
2、通过this访问到$refs的loginFormRef对象
2、调用resetFields方法
表单的预校验:
当我们点击登录按钮,发送登录请求之前我们需要对表单进行预校验;
调用表单的validata方法
发送登录请求:
通过axios进行网络请求,传入的参数是用户名和用户密码,使用post请求,
在响应拦截器中判断,如果返回的状态码不是200,那么登录失败,否则登录成功。
message弹框组件的导入:
1、引入弹框提示组件:import {Message} from 'element-ui'
2、挂载为全局的属性: Vue.prototype.$message = Message
3、失败的弹窗信息,this.$message.error('登录失败!') ; 成功的弹窗信息, this.$message.success('登录成功')
登录组件登录成功后的行为:
1、将登录成功之后的token,保存到客户端的sessinStorage中;
1.1项目中出现了登录之外的其他API接口,必须在登录之后才能访问;
1.2 token只应在当前网站打开期间生效,所以将token保存在sessionStorage中;
2、通过编程式导航跳转到后台主页,路由地址是 /home
- <template>
- <div class="login_wrap">
- <div class="form_wrap" v-if="!registerState">
- <el-form
- ref="loginForm"
- :model="loginData"
- label-width="80px"
- class="demo-dynamic"
- label-position="left"
- :rules="rules"
- >
- <el-form-item prop="username" label="用户名">
- <el-input v-model="loginData.username"></el-input>
- </el-form-item>
-
- <el-form-item prop="password" label="密码">
- <el-input v-model="loginData.password" type="password"></el-input>
- </el-form-item>
- <div style="display: flex">
- <el-button class="login_btn" @click="handleLogin">登录</el-button>
- <el-button class="login_btn" @click="registerState = true"
- >注册</el-button
- >
- </div>
- </el-form>
- </div>
- <!-- 注册表单 -->
- <div class="register_wrap" v-if="registerState">
- <el-form
- ref="registerRef"
- :model="registerForm"
- status-icon
- :hide-required-asterisk="true"
- :rules="rules"
- label-width="80px"
- class="login-form"
- >
- <!-- 用户名注册 -->
- <el-form-item label="用户名" prop="username">
- <el-input
- v-model.number="registerForm.username"
- minlength="6"
- maxlength="10"
- autocomplete="off"
- placeholder="请注册用户名"
- ></el-input>
- </el-form-item>
-
- <!-- 邮箱注册 handleGetCaptcha -->
- <el-form-item label="邮箱" prop="email">
- <el-input
- v-model="registerForm.email"
- autocomplete="off"
- placeholder="请输入注册邮箱"
- >
- <!-- 发送验证码按钮 -->
- <template #append>
- <el-button :disabled="false" @click="handleGetCaptcha">{{
- codeText
- }}</el-button>
- </template>
- </el-input>
- </el-form-item>
-
- <!-- 验证码输入 校验 -->
- <el-form-item label="验证码" prop="capcha">
- <el-input
- v-model="registerForm.capcha"
- maxlength="10"
- autocomplete="off"
- placeholder="请输入验证码"
- ></el-input>
- </el-form-item>
-
- <!-- 密码设置 -->
- <el-form-item label="密码" prop="password">
- <el-input
- v-model="registerForm.password"
- type="password"
- autocomplete="off"
- placeholder="请输入密码"
- ></el-input>
- </el-form-item>
-
- <!-- 确认密码registerForm.checkPass -->
- <el-form-item label="确认密码" prop="checkPass">
- <el-input
- v-model="registerForm.checkPass"
- type="password"
- autocomplete="off"
- ></el-input>
- </el-form-item>
-
- <!-- 完成注册按钮 handleRegister -->
- <el-form-item>
- <div class="btn-container">
- <el-button
- type="primary"
- style="width: 100%"
- @click="handleRegister()"
- >完成注册</el-button
- >
- </div>
- <div class="go-login">
- <span
- class="to-login"
- @click="registerState = !registerState"
- style="display: block; margin-left: 90px"
- >已有账号<em>去登陆</em></span
- >
- </div>
- </el-form-item>
- </el-form>
- </div>
- </div>
- </template>
-
- <script>
- import { reactive, toRefs, ref, computed, onMounted } from "vue";
- import { useStore } from "vuex";
- import { useRouter } from "vue-router";
- import { ElMessage } from "element-plus";
- import { loginApi, registerApi, getCaptchaApi } from "@/util/request";
- // import { encrypt } from "@/util/aes.ts";
-
- // import {store} from '/Users/dingmiao/Desktop/商店后台管理系统/myproject/src/store'
- export default {
- name: "login",
-
- setup() {
- const store = useStore();
- const router = useRouter();
- const loginForm = ref();
- const registerRef = ref();
- const sendingCode = ref(false);
- const data = reactive({
- loginData: {
- username: "",
- password: "",
- },
- registerState: false,
- registerForm: {
- username: "",
- email: "",
- capcha: "",
- password: "",
- checkPass: "",
- },
- codeText: "获取验证码",
- });
-
- // 登录表单校验规则
- const rules = {
- password: [
- { validator: validatePass, tigger: "blur" },
- {
- required: true,
- min: 6,
- max: 10,
- message: "长度在6 到 10个字符",
- trigger: "blur",
- },
- ],
- username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
- checkPass: [{ validator: validatePass2, tigger: "blur" }],
- email: [
- { required: true, message: "请输入注册邮箱", trigger: "blur" },
- { type: "email", message: "请输入正确的邮箱地址", trigger: "blur" },
- ],
- capcha: [{ required: true, message: "请输入验证码", trigger: "blur" }],
- };
-
- // 二次校验
- function validatePass2(rule, value, callback) {
- if (value === "") {
- callback(new Error("请再次输入密码"));
- } else if (value != data.registerForm.password) {
- callback(new Error("两次输入的密码不一致"));
- } else {
- callback();
- }
- }
-
- // 校验密码函数
- function validatePass(rule, value, callback) {
- if (value === "") {
- callback(new Error("请输入密码"));
- } else {
- callback();
- }
- }
-
- // 通过 mutation 改变登录状态
- function handleLogin() {
- loginForm.value.validate(async (valid) => {
- if (valid) {
- try {
- loginApi(data.loginData).then((res) => {
- // console.log("res====>", res.data);
- if (!res.data.status) {
- store.commit("changeLoginStatus", data.loginData);
- localStorage.setItem(
- "loginData",
- JSON.stringify(data.loginData)
- ); //将用户信息存入localStorage
- // 跳转url
- router.push({
- path: "/user",
- });
- } else {
- alert("账户与密码不符合");
- }
- });
- } catch (err) {
- ElMessage({
- type: "warning",
- message: err.message,
- });
- }
- }
- return false;
- });
- }
-
- // 处理注册信息
- function handleRegister() {
- registerRef.value.validate(async (valid) => {
- if (valid) {
- try {
- const { username, email, password, capcha } = data.registerForm;
- const data1 = { username, email, capcha, password };
-
- registerApi(data1)
- .then((res) => {
- console.log("注册用户信息", res);
- if (res.data.status === 0) {
- console.log("注册完毕");
- localStorage.setItem("registerData", JSON.stringify(data1));
- // store.commit("storeRegisterData", data1);
- ElMessage({
- type: "success",
- message: "注册成功",
- });
- data.registerState = false;
- } else {
- ElMessage({
- type: "warning",
- message: res.message,
- });
- }
- })
- .catch((err) => {
- ElMessage({
- type: "warning",
- message: err.message,
- });
- });
- } catch (err) {
- ElMessage({
- type: "warning",
- message: err.message,
- });
- }
- }
- });
- }
-
- // getCodeSuccess 获取验证码状态
- const getCodeSuccess = () => {
- let countDown = 60;
- sendingCode.value = true;
- const interval = setInterval(() => {
- if (countDown > 0) {
- data.codeText = `已发送(${countDown}s)`;
- countDown -= 1;
- } else {
- clearInterval(interval); //清楚定时器
- sendingCode.value = false;
- data.codeText = "获取验证码";
- }
- }, 1000);
- };
- //发送验证码 handleGetCaptcha
- const handleGetCaptcha = async () => {
- try {
- const { email } = data.registerForm;
- if (!email) {
- ElMessage({
- type: "waring",
- message: "请输入注册邮箱",
- });
- return false;
- }
- const data1 = { email };
- // 发送获取验证码的请求 接口为registerApi
- await getCaptchaApi(data1).then((res) => {
- if (res.data.status === 0) {
- // console.log('验证码获取成功')
- ElMessage({
- type: "success",
- message: "成功发送请求",
- });
- getCodeSuccess();
- return true;
- }
- ElMessage({
- type: "warning",
- message: res.message,
- });
- return false;
- });
- } catch (error) {
- ElMessage({
- type: "warning",
- message: error.message,
- });
- }
- };
-
- return {
- ...toRefs(data),
- handleLogin,
- handleRegister,
- handleGetCaptcha,
- rules,
- loginForm,
- registerRef,
- sendingCode,
- };
- },
- };
- </script>
-
- <style scoped>
- .login_wrap {
- width: 100%;
- height: 100vh;
- background-image: url("@/assets/bg.JPG");
- background-size: cover;
- background-repeat: no-repeat;
- position: relative;
- }
- .form_wrap,
- .register_wrap {
- position: fixed;
- top: calc(50% - 101px);
- left: calc(50% - 390px);
- background-color: rgb(199, 224, 245);
- transform: transiton(-50%, -50%);
- border-style: double;
- border-width: 2px;
- padding: 30px 50px;
- border-radius: 5px;
- /* background-color: transparent;
- opacity: 0.9; */
- }
-
- .login_btn {
- display: block;
- margin: 10px auto;
- }
-
- .el-select {
- width: 300px;
- }
- .el-input {
- width: 300px;
- }
- .dialog-footer button:first-child {
- margin-right: 10px;
- }
- </style>
- /**
- * @description register mock data right here
- * 使用mock.js库来模拟接口数据,使用 Mock.js,可以在前端开发过程中模拟接口数据,以便进行接口调用和功能开发,而无需依赖后端接口的实际实现。
- * 通过定义模拟数据,可以在前端开发过程中模拟各种接口返回的数据结构和格式,以及测试不同的场景和状态
- * Mock.mock()用来定义接口的模拟数据
- */
- import Mock from 'mockjs'
- Mock.setup({
- timeout: "1900"
- })
-
- // 定义mock请求拦截,登录请求返回的数据 一定要写全请求地址http://localhost:8080xxxxxx
- Mock.mock('http://localhost:8080/api/auth/user/login', 'post', (option) => {
- console.log('拦截到了')
- console.log(option)
- // 拦截到请求后的处理逻辑
- const { username, password } = JSON.parse(option.body)
-
- // 情况1:账户admineSuper 密码:123456 超级管理员
- if (username === 'admineSuper' && password === '123456') {
- return {
- status: 0,
- data: {
- accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IjIyODA1MjAxMjhAcXEuY29tIiwic3ViIjo5LCJpYXQiOjE2MjU4MzQ3MTksImV4cCI6MTYyODQyNjcxOX0.YQLVi-zw4XWQEd8Hy2YZGlFaqX8c7xyRPrYuxcFywFE'
- },
- success: true,
- message: '登录成功'
- }
- }
- // 情况2:账户admine 密码:123456 普通用户
- if (username === 'admine' && password === '123456') {
- return {
- status: 0,
- data: {
- accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IjIyODA1MjAxMjhAcXEuY29tIiwic3ViIjo5LCJpYXQiOjE2MjU4MzQ3MTksImV4cCI6MTYyODQyNjcxOX0.YQLVi-zw4XWQEd8Hy2YZGlFaqX8c7xyRPrYuxcFywFE'
- },
- success: true,
- message: '登录成功'
- }
- }
- // 情况3:密码不正确时候返回的数据
- return {
- status: 1,
- data: null,
- message: '账户或者密码错误'
- }
- })
-
- // 用户注册
- Mock.mock('http://localhost:8080/api/auth/user/register', 'post', (option) => {
- return {
- status: 0,
- data: { message: '注册请求成功' },
- success: true,
- message: '成功'
- }
- })
3、效果展示
4、路由导航守卫控制访问权限
如果用户没有登录,但是直接通过URL访问特定页面,需要重新导航到登录页面;
在全局路由守卫router.beforeEach((to, from, next)=>{}) 中,
1、判断用户当前要去的页面是否为登录页,如果是,则放行next;
2、读取sessionStorage中是否存在token,如果存在token,则放行;否则强制跳转到登录页。
5、退出功能
基于token的方式实现退出比较简单,只需要销毁本地的token即可,这样,后续的请求就不会携带token,必须重新登录生成一个新的token之后才能访问页面。
6、处理语法警告
当eslint和格式化冲突的时候,在根目录上添加配置文件prettierrc来进行格式化的配置,将格式化时候的默认双引号变成单引号,默认添加分号关闭。
当eslint中报错,可以通过在eslintrc.js中的规则rules进行配置
五、优化element-ui的按需导入
在plugins/element-ui.js文件中
7、提交登录功能代码
1、git status命令 查看当前源代码的状态 能够看到了修改文件和新增文件
2、git add . 将文件添加到暂存区
3、git commit -m “提示信息“ 将当前代码提交到本地仓库中
4、git branch 查看当前的分支
5、将login 内容合并到master分支上,1)要切换到master分支 git checkout master 2)合并 git merage login
6、git push 将本地的master分支中的代码推送到云端的master分支中
7、本地的login分支推送到云端的仓库中 1)切换到login分支 git checkout login 2)推送到云端 git push -u origin login
1、整体布局:
1-引入element-ui
2-设置样式 :注意el-mian el-container就是组件类名,可以直接.el-main{}来设置样式
2、header布局
3、侧边栏布局
1)elementui按需导入 2)只设置一二级菜单栏,多余部分删除
4、通过接口获取菜单数据
通过axios请求拦截器添加token,保证拥有获取数据的权限;
打印config,config的属性如下:我们要使用的是header属性
5、发起请求获得左侧菜单栏
1)封装菜单请求api
2)定义菜单源数据
3)获取所有菜单列表
6、左侧菜单UI绘制
1)一级菜单栏渲染: v-for="item in menuList" :key="item.id" index="item.id" 文本{{item.authName}}
【注意】:index必须是唯一的 否则的话就会导致操作一个菜单,其他的菜单也会有同样的动作,所以将index设置为item.id + ''
2)二级菜单:循环item.children即可 v-for="subItem in item.children" :key="subItem.id"
文本:<span>{{subItem.authName}}</span>
index更改为 subItem.id + ''
7、左侧菜单格美化
1) 改变选中菜单时的颜色: 修改el-menu标签中的active-text-color属性的颜色
2)修改二级菜单的图标:修改二级菜单中 i标签的class属性为el-icon-menu
3)修改一级菜单的图标
4)每次只展开一个子菜单
5)边框线对不齐的效果:子菜单展开的时候,会多出一个块
8、实现侧边栏的展开与折叠
1)通过给el-menu绑定:collapse="isCollapse"属性来进行折叠,并消除组件自带的动画效果 :collapse-transition="false"
定义一个变量isCollapsed,动态的绑定collapse属性
2)给折叠按钮绑定点击事件
3) 折叠时候背景色区域并没有跟着折叠
原因:整个区域都属于侧边栏el-side, 而el-side的width属性写死了200px,导致不能跟随侧边栏折叠而折叠;
解决方法: width宽度随着 isCollapse变化而变化,即:width="isCollapse ? '64px' : '200px'"
9、实现首页的路由重定向
访问/home,可以重定向到Welcome组件,
也就是Home组件嵌套Welcom组件,一定要在Home组件中使用router-view给Welcome 组件占位。
1)创建Welcom组件
2)编写路由表
3)在Home组件中占位
10、左侧菜单改造为路由链接
需求:点击二级子菜单,路由进行跳转
方法:el-menu中的router属性,可以实现路由跳转,并且以index为path进行跳转
1)el-menu开启router
2)二级菜单index设置为path,注意加/
1)components/user/Users.vue创建组件
2)配置路由表
Home组件是一级组件
Users组件嵌套在Home组件中,也就是二级路由
13、解决用户列表小问题
问题1:点击子菜单时,不高亮;刷新页面的时候,也应该有高亮效果。
0)设置链接高亮
1)每次点击链接(子菜单)的时候,将地址保存到sessionStorage中,并且设置高亮
2)刷新的时候,读取sessionStorage的地址,并赋值给default-active
14、绘制用户列表的基本UI结构
1)面包屑
2)卡片视图区域 el-card
设置全局样式:
src/css/global.css
3)设置搜索与添加区域
el-row 的gutter属性是设置 列与列之间的间距
el-col的span属性是设置 每列的宽度
15、获取用户列表数据
1)封装请求用户列表api
2)发送请求,并对请求后的数据处理 userList 《用户列表》和total 《数据条数》
16、渲染用户列表数据
data: 指定数据源
label:当前列的标题
prop:当前列指向的数据
响应数据:
1)用户列表区域:border表示带边框, stripe表示隔行变色
2)设置table的样式 src/assets/css/gloabal.css
17、为用户列表添加索引列
索引列:table的每一行的最前面的一列, 即带# 序号的那一列
设置索引列,在“姓名”前添加一列,设置type="index"即可
18、改造状态列的显示效果
由于请求返回的数据mg_state是布尔值,我们需要将el-switch的状态和mg_state关联起来。
方法:通过作用域插槽,scope.row得到的是当前这一行的数据,打印scope.row结果如下图所示。
[注意] prop:"mg_state"可以直接删除了
19、插槽形式自定义列的渲染
1)作用域插槽获得作用域的数据 <template slot-scope></template>
2)在模版中放入三个按钮, el-button
3)在最后一个按钮外层包裹 el-tooltip用于显示提示信息
el-tooltip的属性:设置为false表示,进入tooltip中文字提示就消失
20、实现数据分页效果
1)按需导入分页组件 el-pagination
2)定义 @size-change 事件、@current-change事件
3)不同的属性配置页码条
21、实现用户状态的修改
问题:用户状态改变后刷新页面后,状态恢复默认值,无法实现保存更改
解决:1)监听用户状态的改变《switch的change事件》;2)封装接口,将状态改变提交数据库
22、实现搜索的功能
1)为文本框绑定查询参数
2)点击搜索按钮,发起getUser请求
3)input添加清除按钮,并监听@clear事件
23、实现添加用户的功能
1)按需引入el-dialog组件 2)控制对话框的显示与隐藏 addDialogVisible = false/true,当点击“添加”按钮的时候显示对话框;当点击“取消”/“确定”按钮的时候隐藏对话框;
24、添加用户的对话框中渲染一个添加用户的表单
1)将“内容主体区域”替换为el-form,prop绑定具体的校验规则
2)data中添加addForm数据源,校验规则表addformRules
25、实现自定义规则
“邮箱”、“手机号”通过el-form的自定义校验规则。
1) 定义校验规则 checkEmail, checkMobile
checkEmail是一个箭头函数,接受三个参数,rules、value、callback,函数内部使用正则表达式进行校验,当正则表达式结果为true时,调用callback(),否则,callback(new Error('请输入合法的邮箱'))
2)addFormRlues中添加{validator: checkEmile, triggle:'blur'}
26、实现添加用户表单的重置功能
当对话框关闭后再打开,对话框是初始状态《无数据》
1)监听对话框的关闭事件 @close =“addDialogClose”
2)在关闭事件中重置对话框 通过表单对象的resetFields方法 this.$refs.addFormRef.resetFields()
27、添加用户的预检验证功能
点击“确定”按钮时,对表单进行预检验。
1)“确定”按钮i添加点击事件 @click="addUser"
2)点击事件addUser中使用form对象的 validate方法进行校验,参数是一个箭头函数,箭头函数的参数是valid,如果校验通过valid是true,否则为false;
28、发起请求添加一个新用户
校验成功后,发起添加用户的请求
1)封装添加用户请求的api
2)在预校验函数中,调用api接口,发送请求; 请求完毕后,关闭添加用户的对话框,并调用getUser()接口,刷新table数据
29、添加用户修改的操作
点击“修改”按钮,弹出对话框,对话框上显示当前用户的信息,其中 用户名是“只读的”, 邮箱和手机是“可修改的”。
1)引入对话框,“修改”按钮绑定点击事件,弹出对话框
30、根据ID查询用户信息
根据用户的id,发送查询用户信息的请求
1)封装查询用户信息的api
2)发送请求,对请求结果进行处理
31、修改绘制用户表单
1)为表单绑定数据、添加校验规则
32、实现修改表单的关闭之后的重置操作
监听对话框的close事件,拿到表单的引用,然后调用resetFields函数。
33、提交修改之前表单预检验操作
1)给“确定”按钮绑定click事件
2)监听点击事件,触发预检验的回调函数 this.$refs.editForm.validate((valid) =>{})
34、修改用户信息的操作
1)封装修改用户数据请求接口
2)发送请求
35、实现删除用户的操作
点击“删除”按钮,弹出提示消息框MessageBox,提示是否确定删除,防止误删除
1)按需引入MessageBox, 挂载到vue原型上
2)给删除按钮绑定点击事件, 弹出删除提示框
this.$confirm返回值是一个promise,可以使用async/await进行优化,优化后返回值就是一个字符串;async表示该函数是一个异步任务,返回值是一个promise, await是将promise的执行结果返回,并且将await后的语句加入微任务队列,类似于then()后面的语句。
36、完成删除用户的操作
1)封装删除用户信息的Api
2)调用api,执行删除用户操作
37、提交用户列表功能代码
//创建一个user分支,并推送到云端
1)git branch 检查当前分支
2)git checkout -b user 新建一个user分支,并切换到该分支上
3)git branch 检查分支
4)git status 检查user分支上的文件
5)git add . 添加所有文件到暂存区
6)git commit -m "完成用户列表功能的开发" 提交代码到user分支
7)git status 检查user分支上的文件,发现没有新增的文件了,说明提交完毕
8)git push -u origin user 将本地分支user 推送到码云上
//将master分支更新,并推动到云端
9)git checkout master 切换到master主分支
10)git merge user 将master中的代码更新
11)git push 将云端的master分支也更新
1、权限管理开发开始:
创建component/power/Right.vue组件,配置路由表。
1)git branch 查看当前分支
2)git checkout -b rights 创建一个rights子分支
3)git push -u origin rights 将rights子分支推送到远程仓库中
2、开发权限列表对应规格
3、权限列表的基本页面布局
4、获取权限列表数据
1)封装请求权限列表api
2)发送请求
5、权限列表数据渲染
引入el-tag
6、用户、角色、权限三者之间的关系
“用户” 具有不同的“角色”, 每个“角色”对应不同的“权限”
====》 不同的“用户” 有不同的“权限”
7、角色列表管理
1)创建src/components/power/Roles.vue组件
2)配置路由规则
8、角色列表的基础布局及数据获取
1)基本布局
2)封装角色列表请求Api
3)发送请求,渲染页面
多了一个展开列 设置type为expand即可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。