【6】【vue3+elementplus+springboot+mybatisplus】 管理系统 【前后端实践】

第一部分:

elementplus官网:一个 Vue 3 UI 框架 | Element Plus (element-plus.org)

1、安装elementplus

 npm install element-plus --save

 查看package.json中存在依赖表示成功安装

2、引入elementplus

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'





const app = createApp(App)
app.use(ElementPlus)

注意:vue3不支持elementUI,所以选择以下搭配其中之一(这里我选的是2)

(1)vue2+elementUI

(2)vue3+elementPlus

3、使用组件设置布局

点击官网上方菜单栏的组件,点击

点击布局容器可以看到示例,我们选择使用最下面的侧边栏的示例代码

 

Container 布局容器 | Element Plus (element-plus.org)

 查看示例的代码,这里选择手动引入(elementplus还支持自动按需引入)

 代码主要分成四部分,第一部分我们需要放在template里面,第二部分是选择按需部分引入的方式,我们可以不管,第三部分是放在script里的data部分里面也就是提供页面的数据,具体填入形式如下

<script >
export default {
  name: "HomeView",
  data(){
    const item = {
      date: '2016-05-02',
      name: 'Tom',
      address: 'No. 189, Grove St, Los Angeles',
    };
    return {
      tableData : (Array.from({ length: 20 }).fill(item)),
    }
  }
}

</script>

第四部分是页面的样式,不重要,我们可以不使用

引入完成功编译运行后的结果:

4、在asserts文件夹里面创建一个global.css用来书写全局样式

 //global.css

html,body,div{
    margin: 0;
    padding: 0;
}
html,body{
    height: 100%;
}

然后再main.js里引入 (不要忘了加 ./ )

 将所有菜单的高度改成100% 这个时候滚动页面整个页面不会跟随滚动,菜单栏可以单独滚动

自行调整样式后:

第二部分

1、改变e-menu的背景颜色等一些属性

 <el-menu :default-openeds="['1', '3']" style="height: 100%;overflow-x:hidden"
          background-color="rgb(48,65,86)"      //菜单背景设为藏蓝
                   text-color="#fff"            //字体设为白色
                   active-text-color="#ffd04b"  //选中后字体变成黄色
                   :collapse-transition="false"
                   :collapse="isCollapse"  //设置初始被展开的属性为false(初始为折叠)
          >

效果:

 2、添加一些图标

先安装elementPlus图标:

npm install --save @element-plus/icons

到elementPlus官网图标下赋值图标标签代码

Icon 图标 | Element Plus (element-plus.org)

<span>Tom
                <el-icon style="margin-left: 5px">
                  <ArrowDownBold></ArrowDownBold>
                </el-icon>
              </span>

 效果:

 

编写收缩的方法

 methods: {
    collapse(){ //点击收缩按钮触发
      this.isCollapse=!this.isCollapse
    }
  }

此时收缩会出现问题,文字没有隐藏,宽度不对 

 所以需要将宽度设为动态的

 然后再将导航的名称都放在span标签里,就会自动收缩进去

然后更改如果收缩了,图标就改变,这里我用v-if和v-else的组合

再结合前面设置的isCollapse状态

使得图标可以切换

 效果:

     ------------------》

3、设置菜单栏标题:

 <div  style="height: 60px;line-height: 60px;text-align: center">
              <img src="../assets/logo.png" alt="" style="width: 20px;position: relative;top:5px;margin-right: 5px">
              <b v-show="!isCollapse" style="color: white">后台管理系统</b>
            </div>

效果:

  ----------------------》

 linehight如果设置和垂直高度一致,就会居中,否则就会在上方或者下面

style="line-height: 60px"

效果: 

 ----------》

4、 阴影特效:

首先去掉e-menu的边框的颜色

border-right-color: transparent

再在e-side里面添加阴影属性

box-shadow: 2px 0 6px rgb(0 21 41 / 35%)

效果:

第三部分

1、设置表格页码数

 进到官网页面,pagination 

Pagination 分页 | Element Plus (element-plus.org)

 copy“完整功能”代码

黏贴在e-mian标签下的e-table下

 <div style="padding: 10px 0">
              <el-pagination
                  v-model:current-page="currentPage4"
                  v-model:page-size="pageSize4"
                  :page-sizes="[100, 200, 300, 400]"
                  :small="small"
                  :disabled="disabled"
                  :background="background"
                  layout="total, sizes, prev, pager, next, jumper"
                  :total="400"

              />
            </div>

此时可以看到底部的效果,并且还没有生效

2、设置搜索栏(一定要看script里面是否自动导入了输入框图标)

       <el-input style="width: 300px" type="text"  placeholder="请输入名称">
              <template #prefix>
                <el-icon class="el-input__icon">
                  <search></search>
                </el-icon>
              </template>
            </el-input>

            <el-button  class="ml-5" type="" style="color: orangered">搜索</el-button>

效果:

 扩展多个输入框:

3、设置表头颜色和边框

 <el-table :data="tableData" border stripe >
<style >
.el-table th{
  background-color: beige !important
}
</style>

效果:

4、添加一些按钮:

增删导入导出

 <div style="margin: 10px 0;text-align: left">
            <el-button type="primary">新增
              <el-icon><CirclePlus></CirclePlus></el-icon>
            </el-button>
            <el-button type="danger">批量删除
              <el-icon><remove></remove></el-icon>
            </el-button>
            <el-button type="primary">导入
              <el-icon><bottom></bottom></el-icon>
            </el-button>
            <el-button type="primary">导出
              <el-icon><top></top></el-icon>
            </el-button>
          </div>

表格里的编辑、删除

  <el-table-column  label="操作" >
                <el-table-column>
                  <template>slot-scope="scope"</template>
                  <el-button type="success">编辑
                    <el-icon><edit></edit></el-icon>
                  </el-button>
                  <el-button type="danger">删除
                    <el-icon><remove></remove></el-icon>
                  </el-button>
                </el-table-column>
              </el-table-column>

最后的效果:

5、固定表头优化:

(108条消息) vue项目中table表格固定表头和首尾列_梨城毒妃的博客-CSDN博客_vue固定表头

   <el-table :data="tableData" border stripe height="500px" max-height="400px" >

 至此管理系统的前端雏形已经产生

第四部分

1、加一个页签

在官网的面包屑模块下:

Breadcrumb 面包屑 | Element Plus (element-plus.org)

 <div style="margin-bottom: 30px">
            <el-breadcrumb separator="/">
              <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
              <el-breadcrumb-item :to="{path: '/new'}">用户管理</el-breadcrumb-item>
              <el-breadcrumb-item>promotion list</el-breadcrumb-item>
              <el-breadcrumb-item>promotion detail</el-breadcrumb-item>
            </el-breadcrumb>
          </div>

效果:

2、SpringBoot框架搭建

1、创建项目,引入lombok,mybatisplus,mysql等等依赖

后两个依赖在项目勾选时候可以自动导入,所以只用关注前面的


    <dependencies>
<!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
<!--            mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
            <version>8.0.31</version>
        </dependency>


        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <version>3.4.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、编写yml文件

spring:

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
    username: root
    password: 123456

  main:
    banner-mode: off

mybatis-plus:
  global-config:
    banner: false
server:
  port: 8081

效果:

项目结构:

 测试springboot

3、数据库设计

建立表:sys_user

 写入数据:

建立包和实体接口

使接口继承BaseMapper,并注解为Mapper

 在写任何controller时候前面都要加一个注解:RestController

测试连接数据库

用lambdaquery来写查询

   @RequestMapping("/select")   //访问路径
    public List<User> select(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//        lqw.like(User::getUsername,'a');
        List<User> userList = userDao.selectList(lqw);
        return userList;

    }

结果:

4、springBoot使用MybastisPlus实现分页查询

先设置拦截器:(一定要把pom里面的所有关于mybatisplus的依赖的版本号改成一致的

 编写接口:

后来发现要加total,所以改成返回Map

@GetMapping("/page")
    public List<User> fingPage(@RequestParam Integer pageNum,@RequestParam Integer pageSize){
        pageNum = (pageNum-1)*pageSize;
        IPage page = new Page(pageNum,pageSize);
       userDao.selectPage(page, null);
        return page.getRecords();

    }

 用postman测试:(查出两条数据)

在vue页面查看要传输的数据:

 编写传数据的方法:

  created() {
    //请求分页查询数据
    fetch("http://localhost:9090/user/page?pageNum=1&pageSize=2").then(res => res.json()).then(
        res =>{
            console.log(res)
        })
  },

查看,出现了跨域问题 (前端端口与后端端口不一致)

选择在后端处理跨域

最简单方法:在controller类名前加@CrossOrigin注解,这是最简单的处理方式

比较完善的方法:加入如下代码

(94条消息) SpringBoot解决跨域问题_程序员青戈的博客-CSDN博客_springboot解决跨域问题 青戈

 这个时候我们再次启动后台:此时控制台显示我们的数据获取成功

返回改成map后:(就分成了两组)

封装渲染时加载数据的代码为load

 load(){
      fetch("http://localhost:8081/page?pageNum="+this.pageNum+"&pageSize="+this.pageSize).then(res => res.json()).then(res => {
        console.log(res)
        this.tableData=res.data
        this.total=res.total
      })
    }

在created函数内调用load

  created() {
    //请求分页查询数据
    this.load()

  }

created函数:在页面刷新时,重新加载数据,在vue3中有一个setup()注意里面不能用method里的方法也不能使用this

 

 编写跟随页数更改跳转和重新渲染页面的两个方法:用于动态更改页码和显示数据数目

handleSizeChange(pageSize)
handleCurrentChange

  ​​​​​​Pagination 分页 | Element Plus (element-plus.org)

  handleSizeChange(pageSize){
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum){
      this.pageNum=pageNum
      this.load()
    }


效果:

至此页码的所有功能都已激活

第五部分

 绑定搜索框,返回搜索数据

增加查询的名称username关键字

@GetMapping("/page")
    public Map<String,Object> fingPage(@RequestParam Integer pageNum,
                                       @RequestParam Integer pageSize,
                                       @RequestParam String username){
        pageNum = (pageNum-1)*pageSize;
        Page page = new Page(pageNum,pageSize);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.like(User::getUsername,username);
        userDao.selectPage(page, lqw);
        Map<String,Object> hm = new HashMap<>();
        hm.put("data",page.getRecords());
        hm.put("total",page.getTotal());

        return hm;
//        return userService.findAll();
    }

 我们可以看到我们写完QuerWrapper后可以直接填写到selectPage函数第二个参数里(原来为null)非常巧妙

 我们用postman输入参数尝试查询结果成功,注意此时我们并没有更改任何前端

我们成功后即可用v-model绑定前端的搜索框

 然后写入data()(默认为空),然后在fetch函数里添加username字段

 绑定前端搜索按钮事件为重新渲染数据

 第一个搜索框的功能已经激活成功

扩展多条件查询(将其它两个搜索框也激活)

总结上面一个框激活的流程:

1、在接口里增加查询的参数,并且增加querymapper对象的方法

2、用v-model绑定搜索框数据,并且绑定按钮渲染函数

3、在后端data增加参数,并且增加fectch渲染函数的参数

清楚了流程,我们做起来就事半功倍了

 

 

 至此多条件查询已激活所有搜索框

 

第六部分

激活新增,批量删除,导入,导出四个按钮 

驼峰映射是自动的,它会把下划线去掉并把下划线原来后面的字母大写

如:user_name->userName

用postman测试如果觉得复杂,可以用swaggerUI(一个接口测试文档)

(94条消息) SpringBoot集成swagger-ui以及swagger分组显示_程序员青戈的博客-CSDN博客_swagger ui 分组 第一步:

导入swagger依赖:

创建swaggerConfig类:

更改包的地址:

 我觉得还是postman好用

使用axios代替fetch (fetch太繁琐)

(94条消息) Vue项目搭建常用的配置文件,request.js和vue.config.js_程序员青戈的博客-CSDN博客_vue config.headers配置

安装axios

npm i axios -s

 新建utils文件夹,并新建js文件

 黏贴封装的request.js代码:

import axios from 'axios'

const request = axios.create({
	baseURL: '/api',  // 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
    timeout: 5000
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
  
 // config.headers['token'] = user.token;  // 设置请求头
    return config
}, error => {
    return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        let res = response.data;
        // 如果是返回的文件
        if (response.config.responseType === 'blob') {
            return res
        }
        // 兼容服务端返回的字符串数据
        if (typeof res === 'string') {
            res = res ? JSON.parse(res) : res
        }
        return res;
    },
    error => {
        console.log('err' + error) // for debug
        return Promise.reject(error)
    }
)


export default request

设置request为全局变量:

在vue2中用prototype设置,vue3中可以用app.config.globalProperties来代替prototype

(94条消息) vue 3.0 prototype 替代用法_寻ing的博客-CSDN博客_vue3 prototype

 我们使用axios后可以看到前后代码对比

使用前:

fetch("http://localhost:8081/page?pageNum="+this.pageNum+"&pageSize="+this.pageSize+"&username="+this.username
          +"&email="+this.email+"&address="+this.address).then(res => res.json()).then(res => {
        console.log(res)
        this.tableData=res.data
        this.total=res.total
      })

 使用后:

 request.get("http://localhost:8081/page",{
        params:{
          pageNum:this.pageNum,
          pageSize:this.pageSize,
          username:this.username,
          email:this.email,
          address:this.address
        }
      }).then(res => {
        console.log(res)
        this.tableData= res.data
        this.total = res.total

      })

变得更加简洁,而且达到相同的功能效果

在查询后为了回复原来的页面,增加一个重置按钮

 编写reset方法,思路就是把表单清空,并重新渲染页面,超级简单

    reset(){
      this.username=""
      this.email=""
      this.address=""
      this.load()
    }

dialog对话框编写:

在elementplus官网黏贴代码

 Dialog 对话框 | Element Plus (element-plus.org)

  <el-dialog
    v-model="dialogVisible"
    title="Tips"
    width="30%"
    :before-close="handleClose"
  >
    <span>This is a message</span>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogVisible = false">
          Confirm
        </el-button>
      </span>
    </template>
  </el-dialog>

选择下面的嵌套表单的代码

  <el-dialog v-model="dialogTableVisible" title="Shipping address">
    <el-table :data="gridData">
      <el-table-column property="date" label="Date" width="150" />
      <el-table-column property="name" label="Name" width="200" />
      <el-table-column property="address" label="Address" />
    </el-table>
  </el-dialog>

  <!-- Form -->
  <el-button text @click="dialogFormVisible = true">
    open a Form nested Dialog
  </el-button>

  <el-dialog v-model="dialogFormVisible" title="Shipping address">
    <el-form :model="form">
      <el-form-item label="Promotion name" :label-width="formLabelWidth">
        <el-input v-model="form.name" autocomplete="off" />
      </el-form-item>
      <el-form-item label="Zones" :label-width="formLabelWidth">
        <el-select v-model="form.region" placeholder="Please select a zone">
          <el-option label="Zone No.1" value="shanghai" />
          <el-option label="Zone No.2" value="beijing" />
        </el-select>
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogFormVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogFormVisible = false">
          Confirm
        </el-button>
      </span>
    </template>
  </el-dialog>

 绑定新增按钮的事件handleAdd

 编写确认按钮的事件save

在这之前要先把接口写好

这里写一个业务层的save接口,然后放在controller里调用即可

 

然后我们设置id为自增规则,并且表数据为倒叙排序,此时我们新增数据功能就完成了:

注意:若想让数据按id倒叙排序,不能并列式的写LambdaQuery,要单独起一行

 我们输入数据即可在数据库中插入数据(id不是随机且自增)

 接下来我们来实现编辑按钮:

在vue3中不能使用 slot-scope 在vue3中用#相当于vue2中的slot=

(94条消息) vue3的slot插槽用法,`slot-scope` are deprecated_浩星的博客-CSDN博客_`slot-scope` are deprecated

 这个时候我们就可以打开这个数据的el-dialog 点击保存即可完成编辑

删除:

编写业务层接口:

 这里写错了,应该用RemoveById方法

编写控制层的接口:

 绑定删除按钮 注意要加id,血的教训

增加一个气泡弹出窗口用来确定是都真正删除

<!--                        确认组件-->
                  <el-popover :visible="visible" placement="top" :width="160" class="ml-5">

                      <p>您确定删除吗?</p>
                      <div style="text-align: right; margin: 0">
                        <el-button size="small" text @click="visible = false">我再想想</el-button>
                        <el-button size="small" type="primary" @click="handleDelete(scope.row.id)"
                        >确认</el-button
                        >
                      </div>

                  <template #reference>
                    <el-button type="danger" >删除
                      <el-icon><remove></remove></el-icon>
                    </el-button>
                  </template>
                  </el-popover>

注意不要忘了在templete标签里写#reference 不然按钮会不显示  

多选: 

 

在e-table标签下加入一行代码,并且在e-table标签里写入

@selection-change="handleSelectionChange

 之后编写批量删除的接口

    //批量删除的接口
    @DeleteMapping("/del/batch")
    public boolean deleteBatch(@RequestBody List<Integer> ids){
        return userService.removeByIds(ids);
    }

在vue里编写delBatch方法

 delBatch(){
      let ids = this.multipleSelection.map(v => v.id)
      console.log(ids)//[{}.{}.{}] => [1,2,3]
      request.delete("http://localhost:8081/del/batch",{data:ids}).then(res => {
        if(res){
          this.$message.success("批量删除成功")

        }else{
          this.$message.error(("批量删除失败"))
        }
        this.load();
      })
    }

设置批量删除气泡弹窗,并绑定事件


            <!--                        确认组件-->
            <el-popover :visible="visible" placement="top" :width="160" class="ml-5">

              <p>您确定批量删除数据吗?</p>
              <div style="text-align: right; margin: 0">
                <el-button size="small" text @click="visible = false">我再想想</el-button>
                <el-button size="small" type="primary" @click="delBatch"
                >确认</el-button
                >
              </div>

              <template #reference>
                <el-button type="danger" >批量删除
                  <el-icon><remove></remove></el-icon>
                </el-button>
              </template>
            </el-popover>

效果:

优化:

发现:如果不把load()重新加载数据放在最后一行,在新增操作保存后多选数据气泡窗口可能不显示

新增多选按钮,点击出现多选框,再次点击隐藏

给多选框绑定v-if

            <el-table-column v-if="letapear" type="selection" width="55" />

将letapear初始值默认为false

设置多选按钮 用双括号绑定名称

<!--              多选按钮-->
              <el-button type="info" @click="Apearselection">{{duoxuan}}
                <el-icon><CirclePlus></CirclePlus></el-icon>
              </el-button>

编写方法:

Apearselection(){
      if(!this.letapear){
        this.letapear=true;
        this.duoxuan="取消多选"
      }else{
        this.letapear=false;
        this.duoxuan="多选"
      }

    }

效果:

---------------->

第七部分

 1、封装侧边栏

新建一个Aside.vue将以下代码(e-menu)从menage.vue移动到里面

 <el-menu :default-openeds="['1', '3']" style="height: 100%;overflow-x:hidden;border-right-color: transparent"
                   background-color="rgb(48,65,86)"
                   text-color="#fff"
                   active-text-color="#ffd04b"
                   :collapse-transition="false"
                   :collapse="isCollapse"
          >
            <div  style="height: 60px;line-height: 60px;text-align: center">
              <img src="../assets/logo.png" alt="" style="width: 20px;position: relative;top:5px;margin-right: 5px">

              <b v-show="!isCollapse" style="color: white">后台管理系统</b>
            </div>
            <el-sub-menu index="1">
              <template #title>
                <el-icon>
                  <message></message>
                </el-icon>
                <span>导航一</span>
              </template>
              <el-menu-item-group>
                <template #title>Group 1</template>
                <el-menu-item index="1-1">Option 1</el-menu-item>
                <el-menu-item index="1-2">Option 2</el-menu-item>
              </el-menu-item-group>
              <el-menu-item-group title="Group 2">
                <el-menu-item index="1-3">Option 3</el-menu-item>
              </el-menu-item-group>
              <el-sub-menu index="1-4">
                <template #title>Option4</template>
                <el-menu-item index="1-4-1">Option 4-1</el-menu-item>
              </el-sub-menu>
            </el-sub-menu>
            <el-sub-menu index="2">
              <template #title>
                <el-icon>
                  <Menu></Menu>

                </el-icon>
                <span>导航二</span>
              </template>
              <el-menu-item-group>
                <template #title>Group 1</template>
                <el-menu-item index="2-1">Option 1</el-menu-item>
                <el-menu-item index="2-2">Option 2</el-menu-item>
              </el-menu-item-group>
              <el-menu-item-group title="Group 2">
                <el-menu-item index="2-3">Option 3</el-menu-item>
              </el-menu-item-group>
              <el-sub-menu index="2-4">
                <template #title>Option 4</template>
                <el-menu-item index="2-4-1">Option 4-1</el-menu-item>
              </el-sub-menu>
            </el-sub-menu>
            <el-sub-menu index="3">
              <template #title>
                <el-icon>
                  <Setting></Setting>
                </el-icon>
                <span>导航三</span>
              </template>
              <el-menu-item-group>
                <template #title>Group 1</template>
                <el-menu-item index="3-1">Option 1</el-menu-item>
                <el-menu-item index="3-2">Option 2</el-menu-item>
              </el-menu-item-group>
              <el-menu-item-group title="Group 2">
                <el-menu-item index="3-3">Option 3</el-menu-item>
              </el-menu-item-group>
              <el-sub-menu index="3-4">
                <template #title>Option 4</template>
                <el-menu-item index="3-4-1">Option 4-1</el-menu-item>
              </el-sub-menu>
            </el-sub-menu>
          </el-menu>
    

然后再AsideView里面编写props将用到的原来组件的参数传入进来,并且编写components将原来用到的组件传递过来

然后在原来的界面引入AsideView组件,并且使用该组件

 e-side我们就转移完毕了

 然后我们转移e-header

将e-header代码转移到HeaderView里面,然后将style样式做部分迁移,因为转移后,都放在外面不好使。

<div style="text-align: left; font-size: 12px; line-height: 60px;
          display: flex">

  <div style="flex:1;font-size: 18px">
    <el-icon style="cursor: pointer" @click="collapse">
      <Fold v-if="isCollapse"></Fold>
      <Expand v-else></Expand>
    </el-icon>
  </div>
  <div class="toolbar">

    <el-dropdown style="width: 100px;cursor: pointer" >
              <span style="line-height: 60px">Tom
                <el-icon style="margin-left: 5px">
                  <ArrowDownBold></ArrowDownBold>
                </el-icon>
              </span>
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item>个人信息</el-dropdown-item>
          <el-dropdown-item>退出</el-dropdown-item>
          <el-dropdown-item>Delete</el-dropdown-item>
        </el-dropdown-menu>
      </template>
    </el-dropdown>

  </div>
</div>

 然后本来collapse方法在原来的主vue里面,所以我们要定义一个方法,并且通过$emit

将本组件的请求发送过去:

 然后再在主vue引入我们的header,并且在header里面连接方法

 这样header就拆装完毕了

之后我们再把e-main拿出来单独封装

建立一个TableView.vue  

 之后将e-main里面的代码放在里面

<template>
  <div style="margin-bottom: 30px">
    <el-breadcrumb separator="/">
      <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
      <el-breadcrumb-item :to="{path: '/new'}">用户管理</el-breadcrumb-item>
      <el-breadcrumb-item>promotion list</el-breadcrumb-item>
      <el-breadcrumb-item>promotion detail</el-breadcrumb-item>
    </el-breadcrumb>
  </div>

  <div style="margin: 10px 0;text-align: left" >
    <!--            名称搜索-->
    <el-input style="width: 200px" type="text"  placeholder="请输入名称" v-model="username">
      <template #prefix>
        <el-icon class="el-input__icon">
          <search></search>
        </el-icon>
      </template>
    </el-input>
    <!--            邮箱输入-->
    <el-input style="width: 200px" type="text"  placeholder="请输入邮箱" class="ml-5"  v-model="email">
      <template #prefix>
        <el-icon class="el-input__icon">
          <message></message>
        </el-icon>
      </template>
    </el-input>
    <!--           地址搜索-->
    <el-input style="width: 200px" type="text"  placeholder="请输入地址" class="ml-5"  v-model="address">
      <template #prefix>
        <el-icon class="el-input__icon">
          <location></location>
        </el-icon>
      </template>
    </el-input>
    <!--            搜索按钮-->
    <el-button  class="ml-5" type="" style="color: orangered" @click="load">搜索</el-button>
    <el-button  class="mr-5" type="warning" style="color: white" @click="reset">重置</el-button>
  </div>

  <div style="margin: 10px 0;text-align: left">
    <el-button type="primary" @click="handleAdd">新增
      <el-icon><CirclePlus></CirclePlus></el-icon>
    </el-button>

    <!--              多选按钮-->
    <el-button type="info" @click="Apearselection">{{duoxuan}}
      <el-icon><CirclePlus></CirclePlus></el-icon>
    </el-button>
    <!--                        确认组件-->
    <el-popover :visible="visible" placement="top" :width="160" class="ml-5">

      <p>您确定批量删除数据吗?</p>
      <div style="text-align: right; margin: 0">
        <el-button size="small" text @click="visible = false">我再想想</el-button>
        <el-button size="small" type="primary" @click="delBatch"
        >确认</el-button
        >
      </div>

      <template #reference>
        <el-button type="danger" >批量删除
          <el-icon><remove></remove></el-icon>
        </el-button>
      </template>
    </el-popover>

    <el-button type="primary">导入
      <el-icon><bottom></bottom></el-icon>
    </el-button>
    <el-button type="primary">导出
      <el-icon><top></top></el-icon>
    </el-button>
  </div>
  <!--          <el-scrollbar>-->

  <el-table :data="tableData" border stripe height="500px" max-height="350px"
            @selection-change="handleSelectionChange">
    <!--            多选框-->
    <el-table-column v-if="letapear" type="selection" width="55" />

    <el-table-column prop="id" label="ID" width="50" />
    <el-table-column prop="username" label="用户名" width="140" />
    <el-table-column prop="nickname" label="昵称" width="120" />
    <el-table-column prop="address" label="地址" />
    <el-table-column prop="email" label="邮箱" />
    <el-table-column prop="phone" label="电话" />

    <el-table-column  label="操作" >
      <el-table-column>
        <template #default="scope">
          <el-button type="success" @click="handleEdit(scope.row)" >编辑
            <el-icon><edit></edit></el-icon>
          </el-button>

          <!--                        确认组件-->
          <el-popover :visible="visible" placement="top" :width="160" class="ml-5">

            <p>您确定删除吗?</p>
            <div style="text-align: right; margin: 0">
              <el-button size="small" text @click="visible = false">我再想想</el-button>
              <el-button size="small" type="primary" @click="handleDelete(scope.row.id)"
              >确认</el-button
              >
            </div>

            <template #reference>
              <el-button type="danger" >删除
                <el-icon><remove></remove></el-icon>
              </el-button>
            </template>
          </el-popover>

        </template>
      </el-table-column>
    </el-table-column>

  </el-table>
  <!--            页尾-->
  <div style="padding: 10px 0">
    <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="pageNum"
        :page-size="pageSize"
        :page-sizes="[2, 5, 10, 20]"
        :small="small"
        :disabled="disabled"
        :background="background"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total">

    </el-pagination>
  </div>
  <!--          </el-scrollbar>-->



  <el-dialog title="用户信息"  v-model="dialogFormVisible" width="30%">
    <el-form :model="form" label-width="80px" size="small">
      <el-form-item label="用户名" >
        <el-input v-model="form.username" autocomplete="off" />
      </el-form-item>
      <el-form-item label="昵称" >
        <el-input v-model="form.nickname" autocomplete="off" />
      </el-form-item>
      <el-form-item label="邮箱" >
        <el-input v-model="form.email" autocomplete="off" />
      </el-form-item>
      <el-form-item label="电话" >
        <el-input v-model="form.phone" autocomplete="off" />
      </el-form-item>
      <el-form-item label="地址" >
        <el-input v-model="form.address" autocomplete="off" />
      </el-form-item>
    </el-form>
    <template #footer>
                <span class="dialog-footer">
                  <el-button @click="dialogFormVisible = false,this.load">取消</el-button>
                  <el-button type="primary" @click="save">
                    确认
                  </el-button>
                </span>
    </template>
  </el-dialog>
</template>

<script>
import {Bottom, CirclePlus, Edit, Location, Message, Remove, Search, Top} from "@element-plus/icons";


export default {
  name: "TableView",
  components: {
    Edit,
    Top,
    Bottom,
    Remove,
    CirclePlus,
    Location, Search,
    Message,
    },
  data(){
    // const item = {
    //   date: '2016-05-02',
    //   name: 'Tom',
    //   address: 'No. 189, Grove St, Los Angeles',
    // };

    return {
      // tableData : (Array.from({ length: 20 }).fill(item)),
      tableData :[],
      pageNum:1,
      pageSize:2,

      total:0,
      username:"",
      email:"",
      address:"",
      isCollapse:true,
      sidewidth:200,
      dialogFormVisible:false,
      form:{},
      multipleSelection:[],
      letapear:false,
      duoxuan:"多选"
    }
  },
  created() {
    //请求分页查询数据
    this.load()

  },
  methods:{
    load(){
      this.request.get("http://localhost:8081/page",{
        params:{
          pageNum:this.pageNum,
          pageSize:this.pageSize,
          username:this.username,
          email:this.email,
          address:this.address
        }
      }).then(res => {
        console.log(res)
        this.tableData= res.data
        this.total = res.total
      })
    },
    save(){
      this.request.post("http://localhost:8081/save",this.form).then(res => {
        if(res){
          this.$message.success("保存成功")

        }else{
          this.$message.error(("保存失败"))

        }
        this.load();
        this.dialogFormVisible=false;
      })
    },
    handleSelectionChange(val){
      // console.log(val)
      this.multipleSelection = val;

    },
    handleAdd(){
      this.dialogFormVisible=true;
      this.form={}
    },
    handleEdit(row){
      this.dialogFormVisible=true;
      this.form = Object.assign({},row);

    },
    handleDelete(id){
      this.request.delete("http://localhost:8081/"+id).then(res => {
        if(res){
          this.$message.success("删除成功")

        }else{
          this.$message.error(("删除失败"))
        }
        this.load();
      })
    },
    delBatch(){
      let ids = this.multipleSelection.map(v => v.id)
      console.log(ids)//[{}.{}.{}] => [1,2,3]
      this.request.delete("http://localhost:8081/del/batch",{data:ids}).then(res => {
        if(res){
          this.$message.success("批量删除成功")

        }else{
          this.$message.error(("批量删除失败"))
        }
        this.load();
      })
    },
    Apearselection(){
      if(!this.letapear){
        this.letapear=true;
        this.duoxuan="取消多选"
      }else{
        this.letapear=false;
        this.duoxuan="多选"
      }

    },
    reset(){
      this.username=""
      this.email=""
      this.address=""
      this.load()
    },
    handleSizeChange(pageSize){
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum){
      this.pageNum=pageNum
      this.load()
    }
  }
}
</script>

<style scoped>

</style>

再把meanagor组件的大部分方法和属性和组件(除了折叠的方法,和用到的属性,其它都迁移到TableView里面)

然后把TableView放在路由里面:作为

它的子路由,这样就可以通过主路由加载子路由的方式拼装整个页面,这样侧边栏就会一直出现在页面里供我们操作 

我们也定义一个home放在里面当子路由:

 设置重定向:

 之后我们就可以通过路由入口的方式把路由入口router-view放在父组件需要加载子组件的地方

这样我们就可以通过访问子路由的方式 让整个页面加载

 

至此我们就把整个menager组件的所有功能拆分开成不同的组件,这样方便管理,方便维护更改

然后我们设置侧边栏点击可以跳转我们之前设置的两个路由界面:home,table

这个很简单我们只需要更改el-menu-item标签里的 index即可

 不过在此之前一定要记得把e-menu里面的router属性自己手动设为true ,它默认为false

 

 这样我们就实现了点击侧边栏跳转到我们设置的路由

 之后我们实现导入导出功能

这个功能要依赖于hutool,所以我们导入以下依赖

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>

hutool官网;Hutool — 🍬A set of tools that keep Java sweet. 

 Excel工具-ExcelUtil (hutool.cn)

在getwriter参数里面可以写输出路径,我们这里不写,为了在浏览器直接点击下载

在控制类里封装一个export接口

//导出的接口
    @GetMapping("/export")
    public void export(HttpServletResponse response)throws Exception{
        //从数据库中查出所有的数据
        List<User>list = userService.list();
        //通过工具类创建writer 写出到磁盘路径
//        ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath+"/用户信息.xlsx");
       //在内存操作,写出到浏览器
        ExcelWriter writer = new ExcelUtil().getWriter(true);
        //自定义标签别名
        writer.addHeaderAlias("username","用户名");
        writer.addHeaderAlias("password","密码");
        writer.addHeaderAlias("nickname","昵称");
        writer.addHeaderAlias("email","邮箱");
        writer.addHeaderAlias("address","地址");
        writer.addHeaderAlias("createTime","创建时间");
        writer.addHeaderAlias("avatarUrl","头像");

        //一次性写出list内的对象到excel,使用默认样式,强制输出标题
        writer.write(list,true);

        //设置浏览器响应的格式
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("用户信息","UTF-8");
        response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");

        ServletOutputStream out = response.getOutputStream();
        writer.flush(out,true);
        out.close();
        writer.close();


    }

第八部分

设置登陆注册页面

登录页面:

(95条消息) Vue3+SpringBoot实现【登录】【毛玻璃】【渐变色】_白开水为啥没味的博客-CSDN博客

注册页面

复制登陆界面作为注册界面,给注册界面添加路由,编写接口

增加确认密码,并更改按钮,添加事件,具体代码如下

<template>
   <div class="wrapper">
     <div class="logincss" style="margin: 150px auto;  width: 350px; height: 400px;padding: 20px;
     border-radius: 10px">
       <div   style="margin: 20px 0;text-align: center;font-size: 24px "><b>注册</b></div>
       <el-form  :model=user :rules="rules" label-width="auto">
<!--应该让前端的校验都通过之后,才可以触发登陆按钮,否则没有太大意义做表单校验提示-->
         <el-form-item  label="用户名" prop="username" >
           <el-input placeholder="请输入账号" size="default"  v-model="user.username" >
             <template #prefix>
               <el-icon class="el-input__icon">
                 <user></user>
               </el-icon>
             </template>
           </el-input>
         </el-form-item>


         <el-form-item  label="密码" prop="password" >
         <el-input placeholder="请输入密码" size="default"  show-password v-model="user.password" >
           <template #prefix>
             <el-icon class="el-input__icon">
               <lock></lock>
             </el-icon>
           </template>
         </el-input>
         </el-form-item>
<!--         确认密码-->
         <el-form-item  label="确认密码" prop="confirmPassword" >
           <el-input placeholder="请确认密码" size="default"  show-password v-model="user.confirmPassword" >
             <template #prefix><el-icon class="el-input__icon"><lock></lock></el-icon></template>
           </el-input>
         </el-form-item>
         <div style="margin: 10px 0; text-align: right">
           <el-button type="primary" size="small" autocomplete="off" @click="register">注册</el-button>
           <el-button type="warning" size="small" autocomplete="off" @click="$router.push('/login')">返回登录</el-button>
         </div>
       </el-form>
     </div>
   </div>
</template>

<script>
import {Lock, User} from "@element-plus/icons";
export default {
  name: "LoginView",
  components: {Lock, User},
  data(){

    return {
      user: {},
      rules: {
        username: [
          {required: true, message: '请输入用户名', trigger: 'blur'},
          {min: 3, max: 5, message: '名称长度应该为3到5', trigger: 'blur'},
        ],
        password: [
          {required: true, message: '请输入密码', trigger: 'blur'},
          {min: 3, max: 5, message: '密码长度应该为3到5', trigger: 'blur'},
        ],
        confirmPassword: [
          {required: true, message: '请再次输入密码', trigger: 'blur'},
          {min: 3, max: 5, message: '密码长度应该为3到5', trigger: 'blur'},
        ],
      }
    }
  },
  methods:{
    register(){
      if (this.user.password !== this.user.confirmPassword){
        this.$message.error("两次输入的密码不一致")
        return false
      }
          this.request.post("http://localhost:8081/register",this.user).then(res =>{
            // if(!res){
            //   this.$message.error("用户名或密码错误")
            // }else {
            //   this.$message.success("登陆成功")
            //   this.$router.push("/new/home")
            // }
            if(res.code === '200'){

              this.$message.success("注册成功")
            }else {
              this.$message.error(res.msg)
            }
          })
    }
  }
}
</script>

<style scoped>
 .wrapper{

   height: 100vh;
   /*红蓝渐变*/
   /*background-image: linear-gradient(to bottom right,#FC466B,#3F5EFB);*/

   background-image: linear-gradient(to bottom right, #9b1919, #3ffbf5);
   overflow:hidden;
 }

 .logincss{
   background-color:rgba(255,255,255,0.1);
   backdrop-filter: blur(10px);
 }

.e-form-item label {

}


</style>

绑定头像和昵称:

首先在数据库添加字段:

注意要按照驼峰映射来命名,如数据库中用两个小写单词中间用下划线隔开命名

实体类中的属性名称用小写单词加大写单词命名

 在实体类添加字段

user

userDto

 

绑定前端数据(头像图片和用户昵称)

编写获取user对象 信息(账号,邮箱,昵称,头像连接)

注意,在退出的时候清楚缓存

 效果:

组件命名最好符合驼峰映射:

驼峰映射:

  • 7
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Vue3 是一种现代化的 JavaScript 框架,它提供了响应式的数据绑定和组件化的开发模式。ElementPlus 是一个基于 Vue3 的 UI 组件库,它提供了一系列美观易用的组件,帮助开发者快速构建漂亮的后台管理系统。Vite 是一个新一代的前构建工具,它利用了 ES 模块化的特性,实现了快速的冷启动和热模块替换,在开发过程中具有很高的效率。TS 是 TypeScript 的简称,它是一种多功能的 JavaScript 的超集,提供了静态类型检查和面向对象编程的特性。 通过结合这些技术,我们可以开发出一个高效、可维护和可扩展的后台管理系统。在使用 Vue3 开发时,我们可以充分利用其提供的 Composition API,编写可复用的逻辑代码,并利用响应式的数据绑定实现页面的数据驱动。ElementPlus 提供了丰富的组件库,可以用于构建菜单、表格、表单、图表等常见的后台管理系统所需的功能。Vite 的快速启动和热模块替换功能,可以大大提升开发效率,使开发者能够更快地看到修改后的效果。而在使用 TypeScript 进行开发时,静态类型检查可以帮助我们在编码过程中发现潜在的问题,并提供更好的代码提示,提高开发效率和代码可读性。 综上所述,Vue3、ElementPlus、Vite和TS 的组合,给后台管理系统的开发带来了很多便利和优势,它们的使用可以提高开发效率,减少开发错误,并且使得系统更加稳定和易于维护。对于开发者来说,掌握这些技术将对提升自身的开发能力和竞争力非常有益。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白开水为啥没味

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值