整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

SpringBoot集成LayUI实现查询列表

SpringBoot集成LayUI实现查询列表

面介绍了SpringBoot集成LayUI和前端模板Thymeleaf,今天介绍一下数据列表的实现,线上一个简单的支持查询条件的列表实现。话不多说上代码

上前端界面user.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>后台管理系统</title>
    <link rel="stylesheet" href="/css/layui.css">
</head>
<body>
<div style="padding: 15px;">
    <blockquote class="layui-elem-quote layui-text">
        系统用户
    </blockquote>
    <form class="layui-form layui-row layui-col-space16">
        <div class="layui-form-item">
            <div class="layui-inline">
                <label class="layui-form-label">用户标识</label>
                <div class="layui-input-inline">
                    <input type="text" name="userId" value="" placeholder="" class="layui-input"
                           lay-affix="clear">
                </div>
                <label class="layui-form-label">邮箱</label>
                <div class="layui-input-inline">
                    <input type="text" name="email" value="" placeholder="" class="layui-input"
                           lay-affix="clear">
                </div>
                <label class="layui-form-label">用户状态</label>
                <div class="layui-input-inline">
                    <input type="text" name="userStatus" value="" placeholder="" class="layui-input"
                           lay-affix="clear">
                </div>
            </div>
        </div>
        <div class="layui-form-item">
            <button class="layui-btn" id="demo-table-search" lay-submit lay-filter="demo-table-search">查询</button>
            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
            <button type="button" class="layui-btn layui-btn-primary" lay-on="test-iframe-handle">创建</button>
        </div>
    </form>
    <table class="layui-hide" id="ID-table-demo-search"></table>

</div>
<script src="/layui.js"></script>
<!-- 工具栏模板 -->
<script type="text/html" id="barDemo">
    <div class="layui-clear-space">
        <a class="layui-btn layui-btn-xs" lay-event="detail">查看</a>
        <a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
        <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
    </div>
</script>
<script>
    layui.use(function () {
        var table=layui.table;
        var form=layui.form;
        var laydate=layui.laydate;
        var $=layui.$;
        var util=layui.util;

        // 创建表格实例
        table.render({
            elem: '#ID-table-demo-search',
            contentType: 'application/json',
            url: '/sysUser/list',
            method: 'POST',
            cols: [
                [
                    {field: 'userId', title: '用户标识'},
                    {field: 'nickName', title: '昵称'},
                    {field: 'email', title: '邮箱'},
                    {field: 'userStatus', title: '用户状态'},
                    {field: 'createTime', title: '创建时间'},
                    {fixed: 'right', title: '操作', width: 234, minWidth: 125, templet: '#barDemo'}
                ]
            ],
            page: true,
            height: 510,
            limit: 10, // 每页默认显示的数量,
        });
       
        // 搜索提交
        form.on('submit(demo-table-search)', function (data) {
            var field=data.field; // 获得表单字段
            // 执行搜索重载
            table.reloadData('ID-table-demo-search', {
                page: {
                    curr: 1 // 重新从第 1 页开始
                },
                where: field // 搜索的字段
            });
            return false; // 阻止默认 form 跳转
        });

    });
</script>
</body>
</html>

这里有个坑

表格渲染列的时候,注意 cols: [[ 不要连着写,尽量换个行,因为集成了Thymeleaf,会把 [[ 作为特殊符号

LayUI列表页请求结构

// 忽略getset方法
public class PageReq {
    // 返回数据量
    public int limit;
    // 页数
    public int page;
    // 排序字段
    private String orderField;
    // 排序
    private String orderBy;

    @JsonIgnore
    public int getOffset() {
        if (page==0) {
            return 0;
        }
        int tmp=(page - 1) * limit;
        return tmp < 0 ? 0 : tmp;
    }
}

需要分页的请求参数

// 忽略getset方法
public class SysUserParam extends PageReq{

    private Long userId;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 用户状态
     */
    private String userStatus;

    private String nickNameFullLike;
}

LayUI列表页统一返回结构

// 忽略getset方法  
public class Response<T> implements Serializable {
    //错误信息
    private String msg;
    //错误码 0 成功 -1024 失败
    private Integer code;
    // 返回数据
    private T data;
    // 返回数据量
    private Integer count;

    public static <T> Response buildSuccess() {
        return new Response("", 0, null, null);
    }

    public static <T> Response buildFail() {
        return new Response("", -1024, null, null);
    }

    public static <T> Response buildFail(String msg) {
        return new Response(msg, -1024, null, null);
    }

    public static <T> Response buildSuccess(T data, Integer count) {
        return new Response("", 0, data, count);
    }

    public Response(String msg, Integer code, T date, Integer count) {
        this.msg=msg;
        this.code=code;
        this.data=date;
        this.count=count;
    }
 
}

Controller代码


@RestController
@RequestMapping("/sysUser")
public class UserController {
    private Logger logger=LoggerFactory.getLogger(UserController.class);
    @Resource
    private SysUserBiz sysUserBiz;
    /**
     * 根据主键查询系统用户
     */
    @PostMapping("/list")
    public Response<List<SysUserVO>> queryByParam(@RequestBody SysUserParam sysUserParam){
        try {
            return sysUserBiz.selectByCondition(sysUserParam.adaptor());
        }catch (Exception e){
            logger.error("queryUserList Exception",e);
            return Response.buildFail();
        }
    }
}


后面的底层SQL就忽略了,主要是看前端和controller的交互,以及参数返回值的约定

看下实现效果

默认进入的截图

带查询条件的截图

总感觉有点水,下次努力

立和使用列表

定义列表

<dl></dl>列表标签定义列表;
<dt>定义列表标题;
<dd>定义列表内容;

说明:

1. dt和dd对应着的, 一个dt可以对应着多个dd;

2. dd完全是为了dt服务的, 对标题进行描述;

实例:

<dl>
<dt>标题1</dt><dd>内容11</dd><dd>内容12</dd>
<dt>标题2</dt><dd>内容21</dd><dd>内容22</dd>
</dl>


有序列表(unordered list)

<ol></ol> 列表标签定义一个标有数字的列表;

<ol type="value"></ol>

1 默认值。数字有序列表。(1、2、3、4)

a 按字母顺序排列的有序列表,小写。(a、b、c、d)

A 按字母顺序排列的有序列表,大写。(A、B、C、D)

i 罗马字母,小写。(i, ii, iii, iv)

I 罗马字母,大写。(I, II, III, IV)

<ol>
<li>联系人:</li>xxx
<li>联系地址:</li>北京市丰台区
<li>邮政编码:</li>100036
</ol>


无序列表(ordered list)

<ul></ul> 列表标签定义一个标有圆点的列表;

<ul type="value"></ul>

disc 默认值,实心圆。

circle 空心圆。

square 实心方块。

<ul>
<li></li>
<li></li>
<li></li>
</ul>


目录列表 所有主流浏览器都支持 <dir> 标签。。

<dir></dir>标签定义目录列表。

<dir>
<li>HTML</li>
<li>XHTML</li>
<li>CSS</li>
</dir>

菜单列表 目前所有主流浏览器都不支持 <menu> 标签。

<menu></menu>标签可定义一个菜单列表。

<menu>
<li>html</li>
<li>xhtml</li>
</menu>

在实际工作中, 它的用途较少, 大部分我们还是用ul;

另外还可以使用:

<div align=""></div>分区标签,用来排版大块HTML段落,也用于格式化表


.搭建文章列表页面

<script setup>
import {
    Edit,
    Delete
} from '@element-plus/icons-vue'

import { ref } from 'vue'

//文章分类数据模型
const categorys=ref([    ])

//用户搜索时选中的分类id
const categoryId=ref('')

//用户搜索时选中的发布状态
const state=ref('')

//文章列表数据模型
const articles=ref([])

//分页条数据模型
const pageNum=ref(1)//当前页
const total=ref(20)//总条数
const pageSize=ref(3)//每页条数

//当每页条数发生了变化,调用此函数
const onSizeChange=(size)=> {
    pageSize.value=size
    articleList()
}
//当前页码发生变化,调用此函数
const onCurrentChange=(num)=> {
    pageNum.value=num
    articleList()
}


//回显文章分类
import { articleCategoryListService, articleListService,articleAddService } from '@/api/article.js'
const articleCategoryList=async ()=> {
    let result=await articleCategoryListService();

    categorys.value=result.data;
}

//获取文章列表数据
const articleList=async ()=> {
    let params={
        pageNum: pageNum.value,
        pageSize: pageSize.value,
        categoryId: categoryId.value ? categoryId.value : null,
        state: state.value ? state.value : null
    }
    let result=await articleListService(params);

    //渲染视图
    total.value=result.data.total;
    articles.value=result.data.items;

    //处理数据,给数据模型扩展一个属性categoryName,分类名称
    for (let i=0; i < articles.value.length; i++) {
        let article=articles.value[i];
        for (let j=0; j < categorys.value.length; j++) {
            if (article.categoryId==categorys.value[j].id) {
                article.categoryName=categorys.value[j].categoryName;
            }
        }
    }
}


articleCategoryList()
articleList();

import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { Plus } from '@element-plus/icons-vue'
//控制抽屉是否显示
const visibleDrawer=ref(false)
//添加表单数据模型
const articleModel=ref({
    title: '',
    categoryId: '',
    coverImg: '',
    content: '',
    state: ''
})


//导入token
import { useTokenStore } from '@/stores/token.js';
const tokenStore=useTokenStore();

//上传成功的回调函数
const uploadSuccess=(result)=>{
    articleModel.value.coverImg=result.data;
    console.log(result.data);
}

//添加文章
import {ElMessage} from 'element-plus'
const addArticle=async (clickState)=>{
    //把发布状态赋值给数据模型
    articleModel.value.state=clickState;

    //调用接口
    let result=await articleAddService(articleModel.value);

    ElMessage.success(result.msg? result.msg:'添加成功');

    //让抽屉消失
    visibleDrawer.value=false;

    //刷新当前列表
    articleList()
}
</script>
<template>
    <el-card class="page-container">
        <template #header>
            <div class="header">
                <span>文章管理</span>
                <div class="extra">
                    <el-button type="primary" @click="visibleDrawer=true">添加文章</el-button>
                </div>
            </div>
        </template>
        <!-- 搜索表单 -->
        <el-form inline>
            <el-form-item label="文章分类:">
                <el-select placeholder="请选择" v-model="categoryId">
                    <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
                    </el-option>
                </el-select>
            </el-form-item>

            <el-form-item label="发布状态:">
                <el-select placeholder="请选择" v-model="state">
                    <el-option label="已发布" value="已发布"></el-option>
                    <el-option label="草稿" value="草稿"></el-option>
                </el-select>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="articleList">搜索</el-button>
                <el-button @click="categoryId=''; state=''">重置</el-button>
            </el-form-item>
        </el-form>
        <!-- 文章列表 -->
        <el-table :data="articles" style="width: 100%">
            <el-table-column label="文章标题" width="400" prop="title"></el-table-column>
            <el-table-column label="分类" prop="categoryName"></el-table-column>
            <el-table-column label="发表时间" prop="createTime"> </el-table-column>
            <el-table-column label="状态" prop="state"></el-table-column>
            <el-table-column label="操作" width="100">
                <template #default="{ row }">
                    <el-button :icon="Edit" circle plain type="primary"></el-button>
                    <el-button :icon="Delete" circle plain type="danger"></el-button>
                </template>
            </el-table-column>
            <template #empty>
                <el-empty description="没有数据" />
            </template>
        </el-table>
        <!-- 分页条 -->
        <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5, 10, 15]"
            layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"
            @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />

        <!-- 抽屉 -->
        <el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%">
            <!-- 添加文章表单 -->
            <el-form :model="articleModel" label-width="100px">
                <el-form-item label="文章标题">
                    <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>
                </el-form-item>
                <el-form-item label="文章分类">
                    <el-select placeholder="请选择" v-model="articleModel.categoryId">
                        <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
                        </el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="文章封面">

                    <!-- 
                        auto-upload:设置是否自动上传
                        action:设置服务器接口路径
                        name:设置上传的文件字段名
                        headers:设置上传的请求头
                        on-success:设置上传成功的回调函数
                     -->
                   
                    <el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false"
                    action="/api/upload"
                    name="file"
                    :headers="{'Authorization':tokenStore.token}"
                    :on-success="uploadSuccess"
                    >
                        <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
                        <el-icon v-else class="avatar-uploader-icon">
                            <Plus />
                        </el-icon>
                    </el-upload>
                </el-form-item>
                <el-form-item label="文章内容">
                    <div class="editor">
                        <quill-editor theme="snow" v-model:content="articleModel.content" contentType="html">
                        </quill-editor>
                    </div>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="addArticle('已发布')">发布</el-button>
                    <el-button type="info" @click="addArticle('草稿')">草稿</el-button>
                </el-form-item>
            </el-form>
        </el-drawer>
    </el-card>
</template>
<style lang="scss" scoped>
.page-container {
    min-height: 100%;
    box-sizing: border-box;

    .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
}

/* 抽屉样式 */
.avatar-uploader {
    :deep() {
        .avatar {
            width: 178px;
            height: 178px;
            display: block;
        }

        .el-upload {
            border: 1px dashed var(--el-border-color);
            border-radius: 6px;
            cursor: pointer;
            position: relative;
            overflow: hidden;
            transition: var(--el-transition-duration-fast);
        }

        .el-upload:hover {
            border-color: var(--el-color-primary);
        }

        .el-icon.avatar-uploader-icon {
            font-size: 28px;
            color: #8c939d;
            width: 178px;
            height: 178px;
            text-align: center;
        }
    }
}

.editor {
    width: 100%;

    :deep(.ql-editor) {
        min-height: 200px;
    }
}
</style>

2.main.js引入语言包

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import App from './App.vue'

import locale from 'element-plus/dist/locale/zh-cn.js'

const app=createApp(App);

app.use(ElementPlus,{locale});
app.mount('#app')

3.article.js添加函数


//文章列表查询
export const articleListService=(params)=>{
   return  request.get('/article',{params:params})
}

4.获取文章列表,搜索按钮事件

//获取文章列表数据
const articleList=async ()=> {
    let params={
        pageNum: pageNum.value,
        pageSize: pageSize.value,
        categoryId: categoryId.value ? categoryId.value : null,
        state: state.value ? state.value : null
    }
    let result=await articleListService(params);

    //渲染视图
    total.value=result.data.total;
    articles.value=result.data.items;

    //处理数据,给数据模型扩展一个属性categoryName,分类名称
    for (let i=0; i < articles.value.length; i++) {
        let article=articles.value[i];
        for (let j=0; j < categorys.value.length; j++) {
            if (article.categoryId==categorys.value[j].id) {
                article.categoryName=categorys.value[j].categoryName;
            }
        }
    }
}

5.重置

<el-form-item>
    <el-button type="primary" @click="articleList">搜索</el-button>
    <el-button @click="categoryId=''; state=''">重置</el-button>
</el-form-item>

6.分页数据和函数