一、概述

基于vue3 和element-plus的电商后台管理系统

1.1 实现功能

        用户登录/退出
        用户管理

                用户列表:实现用户的增删改查、分页查询以及分配角色的功能

        权限管理

                角色列表

                        实现角色的增删改查以及分配权限、删除权限功能

                权限列表

                        展示所有权限以及权限等级

        商品管理

                商品列表:实现商品的增删改查,分页查询

                分类参数: 实现分类参数的增删改查

                商品分类:实现商品分类的增删改查以及查看改分类下的所有子类

        订单管理

                订单列表

                        实现修改地址

                        实现查看物流进度

        数据统计分析        

                        数据报表

1.2 前端技术栈

 电商后台管理系统里系统整体采用前后端分离的开发模式,其中前端是基于Vue框架的SPA项目

        技术栈 :vue3、vue-router、Element-Plus、Axios、Echarts

1.3 前端项目初始化步骤

        1. 安装vue脚手架-vue-cli
        2. 通过vue脚手架创建项目
        3. 配置vue路由
        4. 配置element-plus组件库
        5. 配置axios库
        6. 初始化git远程仓库
        7. 将本地项目托管到Gitee中   

https://gitee.com/ding-miao9898/vue_shop/blob/master/vue.config.jsicon-default.png?t=N7T8https://gitee.com/ding-miao9898/vue_shop/blob/master/vue.config.js

                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即可

Logo

前往低代码交流专区

更多推荐