美文网首页SpringBoot + Vue后台维护系统spring boot
SpringBoot + Vue 后台管理系统(四):管理页面

SpringBoot + Vue 后台管理系统(四):管理页面

作者: Bertram_Wang | 来源:发表于2019-05-22 01:05 被阅读329次

    管理页面

    主要就是mybatis-plus使用,以及前端表单验证。

    MyBatis是一款非常热门的数据操作层(持久层)框架。

    优点:

    • 自定义SQL ,满足所有的复杂查询,方便SQL优化。
    • 相对(JPA)来说上手简单一些。

    mybatis-plus ?

    Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。参考mybatis-plus官网。其实就是它已经封装好了一些crud方法,我们不需要再写xml了,直接调用这些方法就行,就类似于JPA。

    效果图:

    列表
    新增/修改

    主要依赖:

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>
    

    直接在配置文件添加配置参数即可:

    spring: 
       ...... 
      mybatis-plus: 
        global-config:
          #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
          id-type: 0
          #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
          field-strategy: 2
          #驼峰下划线转换
          db-column-underline: true
          #刷新mapper 调试神器
          refresh-mapper: true
          #数据库大写下划线转换
          #capital-mode: true
          #序列接口实现类配置
          #key-generator: com.baomidou.springboot.xxx
          #逻辑删除配置
          #logic-delete-value: 0
          #logic-not-delete-value: 1
          #自定义填充策略接口实现
          #meta-object-handler: com.umeox.waas.domain.handler.MyMetaObjectHandler
          #自定义SQL注入器
          #sql-injector: com.baomidou.springboot.xxx
        configuration:
          map-underscore-to-camel-case: true  #entity类字段名映射表字段名
          cache-enabled: false
    

    MyBatis-Plus使用:

    public interface IService<T> {
    
        /**
         * <p>
         * 插入一条记录(选择字段,策略插入)
         * </p>
         *
         * @param entity 实体对象
         * @return boolean
         */
        boolean insert(T entity);
    
        /**
         * <p>
         * 插入一条记录(全部字段)
         * </p>
         *
         * @param entity 实体对象
         * @return boolean
         */
        boolean insertAllColumn(T entity);
    
        /**
         * <p>
         * 插入(批量),该方法不适合 Oracle
         * </p>
         *
         * @param entityList 实体对象列表
         * @return boolean
         */
        boolean insertBatch(List<T> entityList);
    
        /**
         * <p>
         * 插入(批量)
         * </p>
         *
         * @param entityList 实体对象列表
         * @param batchSize  插入批次数量
         * @return boolean
         */
        boolean insertBatch(List<T> entityList, int batchSize);
    
        /**
         * <p>
         * 批量修改插入
         * </p>
         *
         * @param entityList 实体对象列表
         * @return boolean
         */
        boolean insertOrUpdateBatch(List<T> entityList);
    
        /**
         * <p>
         * 批量修改插入
         * </p>
         *
         * @param entityList 实体对象列表
         * @param batchSize
         * @return boolean
         */
        boolean insertOrUpdateBatch(List<T> entityList, int batchSize);
    
        /**
         * <p>
         * 批量修改或插入全部字段
         * </p>
         *
         * @param entityList 实体对象列表
         * @return boolean
         */
        boolean insertOrUpdateAllColumnBatch(List<T> entityList);
    
        /**
         * 批量修改或插入全部字段
         *
         * @param entityList 实体对象列表
         * @param batchSize
         * @return boolean
         */
        boolean insertOrUpdateAllColumnBatch(List<T> entityList, int batchSize);
    
        /**
         * <p>
         * 根据 ID 删除
         * </p>
         *
         * @param id 主键ID
         * @return boolean
         */
        boolean deleteById(Serializable id);
    
        /**
         * <p>
         * 根据 columnMap 条件,删除记录
         * </p>
         *
         * @param columnMap 表字段 map 对象
         * @return boolean
         */
        boolean deleteByMap(Map<String, Object> columnMap);
    
        /**
         * <p>
         * 根据 entity 条件,删除记录
         * </p>
         *
         * @param wrapper 实体包装类 {@link Wrapper}
         * @return boolean
         */
        boolean delete(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 删除(根据ID 批量删除)
         * </p>
         *
         * @param idList 主键ID列表
         * @return boolean
         */
        boolean deleteBatchIds(Collection<? extends Serializable> idList);
    
        /**
         * <p>
         * 根据 ID 选择修改
         * </p>
         *
         * @param entity 实体对象
         * @return boolean
         */
        boolean updateById(T entity);
    
        /**
         * <p>
         * 根据 ID 修改全部字段
         * </p>
         *
         * @param entity 实体对象
         * @return boolean
         */
        boolean updateAllColumnById(T entity);
    
        /**
         * <p>
         * 根据 whereEntity 条件,更新记录
         * </p>
         *
         * @param entity  实体对象
         * @param wrapper 实体包装类 {@link Wrapper}
         * @return boolean
         */
        boolean update(T entity, Wrapper<T> wrapper);
    
        /**
         * <p>
         * 根据 whereEntity 条件,自定义set值更新记录
         * </p>
         *
         * @param setStr  set值字符串
         * @param wrapper 实体包装类 {@link Wrapper}
         * @return boolean
         */
        boolean updateForSet(String setStr, Wrapper<T> wrapper);
    
        /**
         * <p>
         * 根据ID 批量更新
         * </p>
         *
         * @param entityList 实体对象列表
         * @return boolean
         */
        boolean updateBatchById(List<T> entityList);
    
        /**
         * <p>
         * 根据ID 批量更新
         * </p>
         *
         * @param entityList 实体对象列表
         * @param batchSize  更新批次数量
         * @return boolean
         */
        boolean updateBatchById(List<T> entityList, int batchSize);
    
        /**
         * <p>
         * 根据ID 批量更新全部字段
         * </p>
         *
         * @param entityList 实体对象列表
         * @return boolean
         */
        boolean updateAllColumnBatchById(List<T> entityList);
    
        /**
         * <p>
         * 根据ID 批量更新全部字段
         * </p>
         *
         * @param entityList 实体对象列表
         * @param batchSize  更新批次数量
         * @return boolean
         */
        boolean updateAllColumnBatchById(List<T> entityList, int batchSize);
    
        /**
         * <p>
         * TableId 注解存在更新记录,否插入一条记录
         * </p>
         *
         * @param entity 实体对象
         * @return boolean
         */
        boolean insertOrUpdate(T entity);
    
        /**
         * 插入或修改一条记录的全部字段
         *
         * @param entity 实体对象
         * @return boolean
         */
        boolean insertOrUpdateAllColumn(T entity);
    
        /**
         * <p>
         * 根据 ID 查询
         * </p>
         *
         * @param id 主键ID
         * @return T
         */
        T selectById(Serializable id);
    
        /**
         * <p>
         * 查询(根据ID 批量查询)
         * </p>
         *
         * @param idList 主键ID列表
         * @return List<T>
         */
        List<T> selectBatchIds(Collection<? extends Serializable> idList);
    
        /**
         * <p>
         * 查询(根据 columnMap 条件)
         * </p>
         *
         * @param columnMap 表字段 map 对象
         * @return List<T>
         */
        List<T> selectByMap(Map<String, Object> columnMap);
    
        /**
         * <p>
         * 根据 Wrapper,查询一条记录
         * </p>
         *
         * @param wrapper 实体对象
         * @return T
         */
        T selectOne(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 根据 Wrapper,查询一条记录
         * </p>
         *
         * @param wrapper {@link Wrapper}
         * @return Map<String,Object>
         */
        Map<String, Object> selectMap(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 根据 Wrapper,查询一条记录
         * </p>
         *
         * @param wrapper {@link Wrapper}
         * @return Object
         */
        Object selectObj(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 根据 Wrapper 条件,查询总记录数
         * </p>
         *
         * @param wrapper 实体对象
         * @return int
         */
        int selectCount(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 查询列表
         * </p>
         *
         * @param wrapper 实体包装类 {@link Wrapper}
         * @return
         */
        List<T> selectList(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 翻页查询
         * </p>
         *
         * @param page 翻页对象
         * @return
         */
        Page<T> selectPage(Page<T> page);
    
        /**
         * <p>
         * 查询列表
         * </p>
         *
         * @param wrapper {@link Wrapper}
         * @return
         */
        List<Map<String, Object>> selectMaps(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 根据 Wrapper 条件,查询全部记录
         * </p>
         *
         * @param wrapper 实体对象封装操作类(可以为 null)
         * @return List<Object>
         */
        List<Object> selectObjs(Wrapper<T> wrapper);
    
        /**
         * <p>
         * 翻页查询
         * </p>
         *
         * @param page    翻页对象
         * @param wrapper {@link Wrapper}
         * @return
         */
        @SuppressWarnings("rawtypes")
        Page<Map<String, Object>> selectMapsPage(Page page, Wrapper<T> wrapper);
    
        /**
         * <p>
         * 翻页查询
         * </p>
         *
         * @param page    翻页对象
         * @param wrapper 实体包装类 {@link Wrapper}
         * @return
         */
        Page<T> selectPage(Page<T> page, Wrapper<T> wrapper);
    
    }
    

    需要继承的基础接口,有默认的实现。(以菜单为例)

    • 新增操作单个或者数组批量新增。直接调用相应的方法即可

    • 修改, 根据ID修改。
      只修改一个字段,或者某几个字段。

      # 不为null的属性都会被修改。 反之如果不想修改的属性直接设置为null即可。
      Menu menu = new Menu();
      menu.setId(1);
      menu.setName("新的名称")
      menuRepository.updateById(menu);
      # 相当于SQL语句。update menu set name = "新的名称" where id = 1;
      

      不是根据ID修改的怎么处理呢?把所有的按钮设置为无效的。

      Menu menu = new Menu(); 
      menu.setStatus(false)
      udpate(menu, new EntityWrapper<Menu>().eq("type", 1));
      # 相当于SQL语句。update menu set status = false where type = 1;
      
    • 查询操作

      根据ID查询一个:
      Menu menu = this.selectById(1);
      根据条件查询一个
      Menu menu = selectOne(new EntityWrapper<Menu>()
                       .eq("type", 1).last("limit 1"));
      # last会造成SQL注入的风险。所以这个参数不能是外部传入。
      # lt("column",value)  ==>  column< value;
      # le("column",value)  ==>  column<= value;
      # gt("column",value)  ==>  column> value;
      # ge("column",value)  ==>  column>= value;
      # between(column, val1, val2) ==>  columnbetween val1 and val2;
      # like(column,value)  ==> like "%value%"; 
      # orderBy(column, ture) ==> order By column asc ;false:表示倒序
      # selectPage(page) ==> 分页查询
      # selectPage(page, new Enw...) ==> 分页查询在家查询条件。
      

    当然也可以自己写SQL语句。多表关联查询时,需要自己定义SQL。
    和mybatis使用方式一致。

    前端表单校验

        <template>
          <div class="backdrop" v-loading="loading">
            <div v-show="isList">
              <el-row>
                <el-col :span="10" :offset="1">
                  <el-input placeholder="请输入内容" clearable v-model="queryObj.value" class="input-with-select">
                    <el-select v-model="queryObj.type" slot="prepend" placeholder="请选择">
                      <el-option label="名称" value="name"></el-option>
                      <el-option label="路径" value="path"></el-option>
                    </el-select>
                    <el-button slot="append" type="primary" icon="el-icon-search" @click="loadList"></el-button>
                  </el-input>
                </el-col>
                <el-col :span="6">
                  <el-button v-show="button.save" type="primary" @click="add" icon="el-icon-circle-plus-outline"
                             class="padding-button"></el-button>
                  <el-popover v-show="button.delete" placement="top" width="200" v-model="visible2">
                    <p>确定要删除这些菜单吗?</p>
                    <br/>
                    <div style="text-align: right; margin: 0">
                      <el-button size="mini" type="primary" class="padding-button" @click="visible2 = false">取消
                      </el-button>
                      <el-button size="mini" type="danger" class="padding-button" @click="remove">确定</el-button>
                    </div>
                    <el-button slot="reference" type="danger" @click="visible2 = true" icon="el-icon-delete"
                               class="padding-button"></el-button>
                  </el-popover>
                </el-col>
              </el-row>
              <div class="margin-bottom-10"></div>
              <el-table :data="menuses" style="width: 100%" height="590" border
                        @sort-change="solrLoadMenusess"
                        @selection-change="electRow">
                <!--多选框-->
                <el-table-column type="selection" width="55"></el-table-column>
                <el-table-column sortable="custom" prop="id" label="ID" ></el-table-column>
                <el-table-column sortable="custom" prop="name" label="名称" ></el-table-column>
                <el-table-column sortable="custom" prop="url" label="链接" ></el-table-column>
                <el-table-column sortable="custom" prop="type" label="类型" >
                  <template slot-scope="scope">
                    <el-tag type="success" size="medium" v-if="scope.row.type == 1">菜单</el-tag>
                    <el-tag type="info" size="medium" v-if="scope.row.type == 2">按钮</el-tag>
                  </template>
                </el-table-column>
                <el-table-column sortable="custom" prop="permission" label="权限" ></el-table-column>
                <el-table-column label="操作" fixed="right" width="180">
                  <template slot-scope="scope">
                    <el-tooltip v-show="button.update" class="item" effect="dark" content="修改菜单" placement="top">
                      <el-button size="mini" type="primary" @click="update(scope.$index, scope.row)"
                                 icon="el-icon-edit" circle></el-button>
                    </el-tooltip>
                    <el-tooltip v-show="button.delete" class="item" effect="dark" content="删除菜单" placement="top">
                      <el-button size="mini" type="danger" @click="closeMenu(scope.$index, scope.row)"
                                 icon="el-icon-delete" circle></el-button>
                    </el-tooltip>
                  </template>
                </el-table-column>
              </el-table>
              <el-pagination
                  @size-change="pageSizeChange"
                  @current-change="currentPageChange"
                  :current-page="queryObj.currentPage"
                  :page-sizes="[10, 20, 30, 40]"
                  :page-size="queryObj.pageSize"
                  layout="total, sizes, prev, pager, next, jumper"
                  :total="queryObj.total">
              </el-pagination>
            </div>
    
            <div v-show="!isList">
              <el-form ref="menu" :model="menu" :rules="rules" label-width="80px" label-position="right"
                       class="demo-ruleForm" size="mini">
                <el-form-item prop="name" label="菜单名称">
                  <el-col :span="10">
                    <el-input v-model="menu.name"></el-input>
                  </el-col>
                </el-form-item>
                <el-form-item prop="type" label="类型">
                  <el-col :span="3">
                    <el-select v-model="menu.type" placeholder="请选择类型">
                      <el-option label="目录" value="0"></el-option>
                      <el-option label="菜单" value="1"></el-option>
                      <el-option label="按钮" value="2"></el-option>
                    </el-select>
                  </el-col>
                </el-form-item>
                <el-form-item prop="path" label="菜单路由" v-if="menu.type == 1">
                  <el-col :span="10">
                    <el-input v-model="menu.path"></el-input>
                  </el-col>
                </el-form-item>
                <el-form-item label="父级菜单">
                  <el-col :span="10">
                    <el-input v-model="menu.parentName" readonly @focus="dialogFormVisible = true"></el-input>
                    <el-dialog title="选择父级菜单" width="30%"
                               :close-on-click-modal="false"
                               :close-on-press-escape="false"
                               :show-close="false"
                               :visible.sync="dialogFormVisible">
                      <el-tree
                          :data="this.buildMenus()"
                          @node-click="setParentId"
                          node-key="id"
                          :accordion=true
                          :highlight-current=true
                          :default-expanded-keys=[menu.parentId]
                          :current-node-key=menu.parentId
                          :props="{children: 'childs', label: 'name'}">
                      </el-tree>
                      <div slot="footer" class="dialog-footer">
                        <el-button @click="menu.parentId=0;menu.parentName='';dialogFormVisible = false;">取消
                        </el-button>
                        <el-button type="primary" @click="dialogFormVisible = false">确定</el-button>
                      </div>
                    </el-dialog>
                  </el-col>
                </el-form-item>
                <el-form-item prop="permission" label="菜单权限">
                  <el-col :span="10">
                    <el-input v-model="menu.permission"></el-input>
                  </el-col>
                </el-form-item>
                <el-form-item label="图标">
                  <el-col :span="10">
                    <el-input v-model="menu.icon"></el-input>
                  </el-col>
                </el-form-item>
                <el-form-item>
                  <el-col :span="10">
                    <el-button type="primary" @click="submitMenu('menu')">提交</el-button>
                    <el-button @click="notSubmitMenu('menu')">取消</el-button>
                  </el-col>
                </el-form-item>
              </el-form>
            </div>
          </div>
        </template>
    
        <script lang="ts">
          import {Component, Prop, Vue} from 'vue-property-decorator';
          import {Menu} from "@/entity/Menu";
          import {Query} from "@/utils/Query";
          import {StringUtils} from  "@/utils/StringUtils"
          @Component({})
          export default class MenuMana extends Vue {
            # 表单校验规则
            rules: object = {
                name: [ {required: true, message: '请输入菜单名称', trigger: 'blur'},
                    {min:2, max:5, message:"限制2~5个字符", trigger: 'blur'}
                ],
                type: [{ required: true, message: '请选择类型', trigger: 'change' }],
                permission: [{ validator: (rule: any, value: any, callback: any)=> {
                    let _this:any = this.$refs["menu"];
                    if(_this.model.type == "2"){
                      if (value === "") {
                        callback(new Error("请输入权限"));
                      }else {
                        callback();
                      }
                    }else {
                      callback();
                    }
                  }, trigger: 'change' }],
                url: [{ required: true, message: '请输入菜单路由', trigger: 'blur' }]
            }
    
            loading: boolean = false;
            isList: boolean = true;
            visible2: boolean = false;
            dialogFormVisible: boolean = false;
            menuses: Array<Menu> = [];
            electMenus: Array<Menu> = [];
            allMenus: Array<Menu> = [];
            // 初始化菜单信息
            menu: Menu = new Menu("", "1", "", "", "0", "");
            button: object = {
              save: StringUtils.isPermisson('sys:menu:save,sys:menu:select'),
              update: StringUtils.isPermisson('sys:menu:update,sys:menu:select'),
              delete: StringUtils.isPermisson('sys:menu:delete')
            }
            queryObj: Query = new Query();
    
            mounted():void{
              this.loadList();
              this.allMenu().then(resp=>{
                console.log("111111111")
              });
            }
            // 加载列表数据
            loadList():void {
              const _this = this;
              _this.loading = true;
              _this.axios.get("/menu/list",{params: _this.queryObj})
                      .then(resp=> {
                        _this.loading = false;
                        _this.menuses = resp.data.records;
                        _this.queryObj.total = resp.data.total;
                      })
            }
    
            // 切换搜索条件
            solrLoadMenusess(val: any):void{
              this.queryObj.isAsc = val.order == "ascending";
              this.queryObj.orderFields = val.prop;
              this.loadList();
            }
            // 切换每页数量
            pageSizeChange(val: any):void{
              this.queryObj.pageSize = val;
              this.loadList();
            }
            // 切换页面数
            currentPageChange(val:any):void{
              this.queryObj.currentPage = val;
              this.loadList();
            }
            // 选中的值
            electRow(val:any):void{
              this.electMenus = val;
            }
            // ——
    
            // 新增菜单
            add():void{
              this.isList = false;
              this.menu = new Menu("", "1", "", "", "0", "");
            }
            // 修改菜单
            update(index:number, row: Menu):void{
              this.isList = false;
              this.menu = new Menu("", "1", "", "", "0", "");
              this.menu.id = row.id;
              this.menu.name = row.name;
              this.menu.parentId = row.parentId + "";
              this.menu.url = row.url;
              this.menu.permission = row.permission;
              this.menu.type = row.type + "";
              this.menu.icon = row.icon;
              this.setMenuParentName();
            }
    
            // 设置当前菜单的父级菜单名字
            setMenuParentName():void{
              let parentName = "";
              let id = this.menu.parentId;
              this.allMenus.forEach(ele => {
                if (ele.id.toString() == id) {
                  parentName = ele.parentName;
                }else if (ele.childs != null) {
                  ele.childs.forEach(child=>{
                    if (child.id.toString() == id) {
                      parentName = child.parentName;
                    }
                  })
                }
              })
              this.menu.parentName = parentName;
            }
    
            // 批量删除
            remove():void{
              let _this = this;
              _this.visible2 = false;
              if (this.electMenus.length == 0) {
                _this.$message.error("请选择需要删除的菜单");
                return;
              }
              let arr = new Array<number>();
              _this.axios.delete("/menu/delete",{data:arr})
                      .then(resp=>{
                        localStorage.removeItem("allMenus");
                        _this.$message.success("删除成功了!");
                        if(_this.electMenus.length >= _this.menuses.length && _this.queryObj.currentPage > 1){
                          _this.queryObj.currentPage = _this.queryObj.currentPage - 1;
                        }
                        _this.loadList();
                      })
            }
            // 删除单个
            closeMenu(idnex:number, row: any):void{
              let _this = this;
              _this.$alert("确认删除这个菜单吗?", "友情提示", {
                confirmButtonText: "确认",
                callback: action => {
                  _this.axios.delete("/menu/delete",{data:[row.id]})
                          .then(resp=>{
                            localStorage.removeItem("allMenus");
                            _this.$message.success("删除成功了!");
                            if(_this.menuses.length <= 1 && _this.queryObj.currentPage > 1){
                              _this.queryObj.currentPage = _this.queryObj.currentPage - 1;
                            }
                            _this.loadList();
                          })
                }
              })
            }
            // 提交
            submitMenu(menu: string):void{
              let _this = this;
              let el: any = this.$refs[menu];
              el.validate((valid: any) => {
                if (valid) {
                  _this.loading = true;
                  if (_this.menu.id) {
                    _this.axios.put("/menu/update",_this.menu)
                            .then((resp: any) => {
                              _this.loading = false;
                              if (resp.code == 0){
                                _this.loading = false;
                                localStorage.removeItem("allMenus");
                                _this.$message.success("修改成功了!");
                                _this.loadList();
                              }
                            })
                  }else{
                    _this.axios.post("/menu/save",_this.menu)
                            .then((resp: any)=>{
                              _this.loading = false;
                              if(resp.code == 0) {
                                _this.isList = true;
                                localStorage.removeItem("allMenus");
                                _this.$message.success("添加成功了!")
                                _this.loadList();
                              };
                            })
                  }
                }else{
                  this.$message.error("错误的提交");
                  return;
                }
              })
            }
            // 不提交菜单
            notSubmitMenu(menu:string):void{
              this.isList = true;
              this.menu = new Menu("", "1", "", "", "0", "");
            }
            // 全部菜单
            allMenu():Promise<any>{
              let _this = this;
              return new Promise(function (resolve, reject) {
                let allMenus = localStorage.getItem("allMenus");
                if (allMenus!=null && StringUtils.isNotBlank(allMenus)) {
                  _this.allMenus = JSON.parse(allMenus.toString());
                  if (_this.allMenus.length != 0) {
                    resolve(_this.allMenus);
                  }
                }else{
                  _this.axios.get("/menu/select")
                          .then((resp: any)=>{
                            _this.allMenus = resp.data;
                            localStorage.setItem("allMenus", JSON.stringify(resp.data));
                            resolve(_this.allMenus);
                          })
                }
              });
            }
            // 处理菜单
            buildMenus(): Array<Menu> {
              let _this = this;
              let arr = new Array<Menu>();
              arr = _this.allMenus;
              /*arr.forEach(elv=>{
                if(elv.childs){
                  elv.childs.forEach((child:Menu)=>{
                    child.childs = new Array<Menu>();
                  })
                }
              })*/
              return arr;
            }
            // 选择父级菜单时,赋值
            setParentId(data: Menu,node: any, eml:any): void{
              this.menu.parentId = data.id.toString();
              this.menu.parentName = data.name;
            }
          }
        </script>
    
        <style scoped>
          .el-col{
            margin-bottom: 10px;
            margin-top: 10px;
          }
        </style>
    

    相关文章

      网友评论

        本文标题:SpringBoot + Vue 后台管理系统(四):管理页面

        本文链接:https://www.haomeiwen.com/subject/ahjezqtx.html