乐于分享
好东西不私藏

HTML前端导出Excel功能,后端java

HTML前端导出Excel功能,后端java

一、后端:导出 Excel 接口

1. 先确认 pom.xml 有 POI 依赖(没有就加上)

<dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi</artifactId>    <version>4.1.2</version></dependency><dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi-ooxml</artifactId>    <version>4.1.2</version></dependency>
2. Controller 新增导出接口(Controller)
import org.apache.poi.ss.usermodel.*;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import javax.servlet.http.HttpServletResponse;import java.io.OutputStream;import java.net.URLEncoder;/** * 导出员工扣分统计Excel */@GetMapping("/exportEngineerStat")public void exportEngineerStat(KhSearchForm queryForm, HttpServletResponse response, HttpSession session) {    // 权限校验    Object qxObj = session.getAttribute("qx");    if (qxObj == null) {        return;    }    List<EngineerKoufenStatVO> list = khuserinfoMapper.statEngineerKoufen(queryForm);    try (Workbook workbook = new XSSFWorkbook()) {        Sheet sheet = workbook.createSheet("员工扣分统计");        // 表头样式        CellStyle headerStyle = workbook.createCellStyle();        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);        headerStyle.setAlignment(HorizontalAlignment.CENTER);        Font headerFont = workbook.createFont();        headerFont.setBold(true);        headerStyle.setFont(headerFont);        // 表头        String[] headers = {"序号""员工姓名""所属部门""监督项目数量""问题总条数""总扣分"};        Row headerRow = sheet.createRow(0);        for (int i = 0; i < headers.length; i++) {            Cell cell = headerRow.createCell(i);            cell.setCellValue(headers[i]);            cell.setCellStyle(headerStyle);        }        // 数据行        CellStyle dataStyle = workbook.createCellStyle();        dataStyle.setAlignment(HorizontalAlignment.CENTER);        for (int i = 0; i < list.size(); i++) {            EngineerKoufenStatVO item = list.get(i);            Row row = sheet.createRow(i + 1);            row.createCell(0).setCellValue(i + 1);            row.createCell(1).setCellValue(item.getEngineerName() == null ? "" : item.getEngineerName());            row.createCell(2).setCellValue(item.getTeamName() == null ? "" : item.getTeamName());            row.createCell(3).setCellValue(item.getProjectCount() == null ? 0 : item.getProjectCount());            row.createCell(4).setCellValue(item.getIssueCount() == null ? 0 : item.getIssueCount());            row.createCell(5).setCellValue(item.getTotalKoufen() == null ? 0 : item.getTotalKoufen().doubleValue());            for (int c = 0; c < headers.length; c++) {                row.getCell(c).setCellStyle(dataStyle);            }        }        // 列宽自适应        for (int i = 0; i < headers.length; i++) {            sheet.autoSizeColumn(i);            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);        }        // 文件名        String fileName = "员工扣分统计_" + System.currentTimeMillis() + ".xlsx";        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");        response.setHeader("Content-Disposition""attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));        OutputStream out = response.getOutputStream();        workbook.write(out);        out.flush();        out.close();    } catch (Exception e) {        e.printStackTrace();    }}
import lombok.Data;import java.math.BigDecimal;@Datapublic class EngineerKoufenStatVO {    /**姓名 xmbz*/    private String engineerName;    /**所属团队 tuandui*/    private String teamName;    /**监督项目数量(该工程师项目条数)*/    private Integer projectCount;    /**总扣分*/    private BigDecimal totalKoufen;    /**问题总条数*/    private Integer issueCount;}
/** * 工程师扣分统计列表 * @param searchForm 查询条件 * @return */List<EngineerKoufenStatVO> statEngineerKoufen(@Param("query") KhSearchForm searchForm);
<!--工程师扣分统计列表--><selectid="statEngineerKoufen"resultType="kaohe.vo.EngineerKoufenStatVO">    SELECT        k.xmbz AS engineerName,        k.tuandui AS teamName,        COUNT(DISTINCT k.id) AS projectCount,        COUNT(l.id) AS issueCount,        IFNULL(SUM(l.koufen),0) AS totalKoufen    FROM userinfo k    LEFT JOIN wentilist l ON k.id = l.userid    <where>        1=1        <!--团队筛选-->        <iftest="query.tuandui != null and query.tuandui !=''">            AND k.tuandui = #{query.tuandui}        </if>        <!--监督日期区间 -->        <iftest="query.jdrqStart !=null and query.jdrqStart !=''">            AND k.jdrq &gt;= #{query.jdrqStart}        </if>        <iftest="query.jdrqEnd !=null and query.jdrqEnd !=''">            AND k.jdrq &lt;= #{query.jdrqEnd}        </if>        <!--过滤姓名不为空,过滤空编制人-->        AND k.xmbz IS NOT NULL AND k.xmbz !=''    </where>    GROUP BY k.xmbz,k.tuandui    ORDER BY totalKoufen DESC</select>
3、前端完整页面(增加导出按钮 + 行点击跳转)
<!DOCTYPE html><htmlxmlns:th="http://www.thymeleaf.org"><head>    <metaname="renderer"content="webkit"/>    <metaname="force-rendering"content="webkit"/>    <metahttp-equiv="X-UA-Compatible"content="IE=Edge,chrome=1"/>    <metahttp-equiv="Content-Type"content="text/html; charset=utf-8"/>    <metaname="viewport"content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">    <title>所有项目监督记录汇总</title>    <style>        .val_yes {            background-color#ea5757;            color#fff !important;            border-radius3px;            padding2px 5px;        }        .search-row {            margin8px 0;            display: flex;            align-items: center;            flex-wrap: wrap;            gap12px;        }        .search-item {            display: flex;            align-items: center;        }        .search-item label {            margin-right6px;            white-space: nowrap;        }        .bor.int {            padding4px;        }        select.bor.int {            width130px;        }        .badge-status span {            padding3px 8px;            border-radius4px;            color#fff;            font-size12px;            white-space: nowrap;        }        .status-wait { background-color#f53f3f; }        .status-done { background-color#00b42a; }        .status-unknown { background-color#86909c; }        .status-pass { background-color#00b42a; }        .status-fail { background-color#f53f3f; }        /*员工统计卡片样式*/        .stat-engineer-wrap{            margin:15px 8px;            border:1px solid #e8e8e8;            padding:16px 20px;            background:#ffffff;            border-radius:8px;            box-shadow0 1px 4px rgba(0,0,0,0.06);        }        .stat-toolbar{            margin-bottom:14px;            display:flex;            align-items:center;            gap:10px;        }        .stat-tip{            margin-left:12px;            color:#606266;            font-size:13px;        }        /*统计表格*/        #engineerStatTable{            width:100%;            border-collapse:collapse;            font-size:13px;        }        #engineerStatTable th,#engineerStatTable td{            border:1px solid #e5e7eb;            padding:9px 12px;            text-align:center;        }        #engineerStatTable th{            background:#f2f3f5;            font-weight:bold;        }        #engineerStatTable tbody tr{            cursor:pointer;            transition:background 0.15s;        }        #engineerStatTable tbody tr:hover{            background:#ecf5ff !important;        }        #engineerStatTable tbody tr:nth-child(even){            background:#fafafa;        }        .empty-tip{            text-align:center;            padding:60px 0;            color:#909399;            font-size:14px;        }        .btn-export{            background:#67c23a;            color:#fff;            border:1px solid #67c23a;            padding:4px 14px;            border-radius:4px;            cursor:pointer;        }        .btn-export:hover{            background:#5daf34;        }    </style></head><body><divclass="cssout">    <divclass="menuboxs">        <ul>            <li><ahref="index">返回首页</a></li>        </ul>        <divclass="clear"></div>    </div>    <spanclass="fastsou">        <formclass="list-form layui-form"name="searchform"action="tongji"method="get">            <divclass="search-row">                <!-- 团队筛选 -->                <divclass="search-item">                    <label>团队:</label>                    <selectname="tuandui"id="tuandui"class="bor int">                        <optionvalue="">全部团队</option>                        <optionth:each="item : ${teamList}"                                th:value="${item.tdjc}"                                th:text="${item.tdname}"                                th:selected="${form.tuandui == item.tdjc}">                        </option>                    </select>                </div>                <!-- 文档类型筛选 -->                <divclass="search-item">                    <label>文档类型:</label>                    <selectname="dalx"id="dalx"class="bor int">                        <optionvalue="">全部文档</option>                        <optionth:each="item : ${daList}"                                th:value="${item.daleixingjc}"                                th:text="${item.daname}"                                th:selected="${form.dalx == item.daleixingjc}">                        </option>                    </select>                </div>                <!-- 文档归还状态 -->                <divclass="search-item">                    <label>归还状态:</label>                    <selectname="daghzt"id="daghzt"class="bor int">                        <optionvalue="">全部</option>                        <optionvalue="0"th:selected="${form.daghzt == 0}">未还</option>                        <optionvalue="1"th:selected="${form.daghzt == 1}">已还</option>                    </select>                </div>                <!-- 文档合格状态 -->                <divclass="search-item">                    <label>合格状态:</label>                    <selectname="dahgzt"id="dahgzt"class="bor int">                        <optionvalue="">全部</option>                        <optionvalue="0"th:selected="${form.dahgzt == 0}">未知</option>                        <optionvalue="1"th:selected="${form.dahgzt == 1}">合格</option>                        <optionvalue="2"th:selected="${form.dahgzt == 2}">不合格</option>                    </select>                </div>                <!-- 监督日期区间 -->                <divclass="search-item">                    <label>监督日期:</label>                    <inputname="jdrqStart"id="jdrqStart"type="text"class="bor int"placeholder="起始"th:value="${form.jdrqStart}"                           onClick="WdatePicker({el:this})" readonly="readonly" style="width:100px;">                    <span>~</span>                    <inputname="jdrqEnd"id="jdrqEnd"type="text"class="bor int"placeholder="结束"th:value="${form.jdrqEnd}"                           onClick="WdatePicker({el:this})" readonly="readonly" style="width:100px;">                </div>                <!-- 搜索按钮 -->                <divclass="search-item">                    <inputtype="submit"id="fast_sou_btn"name="submit"class="btn1 btnsou"value="搜 索"/>                    <buttontype="button"class="btn1 btnsou"onclick="refreshStat()">刷新统计</button>                </div>            </div>            <inputid="CurrentPage"type="hidden"name="currentPage"value="1"/>        </form>    </span>    <divclass="clear"></div></div><divclass="bgdiv"></div><divclass="cssout">    <divclass="stat-engineer-wrap">        <divclass="stat-toolbar">            <buttontype="button"class="btn-export"onclick="exportExcel()">                <iclass="fa fa-download"></i> 导出Excel            </button>            <spanclass="stat-tip">统计说明:按总扣分降序排列;点击表格行可跳转查看该员工项目明细</span>        </div>        <divid="statTableWrap"></div>    </div></div><scriptth:inline="javascript">    $(function (){        refreshStat();    });    // 收集当前筛选条件    function getQueryParams(){        return {            tuandui:$("#tuandui").val(),            dalx:$("#dalx").val(),            daghzt:$("#daghzt").val(),            dahgzt:$("#dahgzt").val(),            jdrqStart:$("#jdrqStart").val(),            jdrqEnd:$("#jdrqEnd").val()        };    }    function refreshStat(){        $.get("/statEngineerData"getQueryParams(), function(res){            if(res.success){                renderTable(res.data);            }else{                alert(res.msg||"获取统计失败");            }        },"json");    }    function renderTable(list){        if(!list || list.length ===0){            $("#statTableWrap").html('<div class="empty-tip">暂无员工统计数据</div>');            return;        }        let html = `        <table id="engineerStatTable">            <thead>                <tr>                    <th>序号</th>                    <th>员工姓名</th>                    <th>所属团队</th>                    <th>监督项目数量</th>                    <th>问题总条数</th>                    <th>总扣分</th>                </tr>            </thead>            <tbody>        `;        for(let i=0;i<list.length;i++){            let item = list[i];            // 行点击跳转:把员工姓名作为keyname,携带所有筛选条件            let jumpUrl = buildJumpUrl(item.engineerName);            html += `<tr onclick="jumpToProjectList('${jumpUrl}')">                <td>${i+1}</td>                <td>${item.engineerName||""}</td>                <td>${item.teamName||""}</td>                <td>${item.projectCount}</td>                <td>${item.issueCount}</td>                <td>${item.totalKoufen}</td>            </tr>`;        }        html += "</tbody></table>";        $("#statTableWrap").html(html);    }    // 构建跳转URL:员工姓名作为keyname(编制人搜索),携带当前筛选条件    function buildJumpUrl(engineerName){        let p = getQueryParams();        let url = "kaohejghz?";        url += "keyname=" + encodeURIComponent(engineerName || "");        if(p.tuandui)  url += "&tuandui="  + encodeURIComponent(p.tuandui);        if(p.dalx)     url += "&dalx="     + encodeURIComponent(p.dalx);        if(p.daghzt)   url += "&daghzt="   + encodeURIComponent(p.daghzt);        if(p.dahgzt)   url += "&dahgzt="   + encodeURIComponent(p.dahgzt);        if(p.jdrqStart)url += "&jdrqStart="encodeURIComponent(p.jdrqStart);        if(p.jdrqEnd)  url += "&jdrqEnd="  + encodeURIComponent(p.jdrqEnd);        return url;    }    // 行点击跳转项目列表    function jumpToProjectList(url){        window.location.href = url;    }    // 导出Excel:携带当前筛选条件    function exportExcel(){        let p = getQueryParams();        let url = "/exportEngineerStat?";        let arr = [];        for(let k in p){            if(p[k]) arr.push(k + "=" + encodeURIComponent(p[k]));        }        url += arr.join("&");        window.location.href = url;    }</script><divclass="h50b"></div><divclass="fixed_bg">    <divclass="pagenums">        <div>© 2026 测试程序</div>    </div></div></body></html>

功能说明

1. 导出 Excel

  • 工具栏新增绿色【导出 Excel】按钮,点击直接下载 xlsx 文件
  • 导出内容与当前筛选条件一致(团队、档案类型、归还 / 合格状态、日期区间)
  • 表头加粗灰底,数据居中,列宽自适应
  • 文件名带时间戳,避免重复

2. 行点击跳转项目列表

  • 表格行鼠标悬浮变浅蓝色,手型光标,提示可点击
  • 点击后跳转到 /kaohejghz(管理人员综合查看页)
  • 跳转时自动携带:
    • keyname
       = 工程师姓名(利用该页原有的 "综合搜索 - 编制人" 筛选)
    • 当前页面所有筛选条件(团队、档案类型、状态、日期区间)
  • 跳转后项目列表自动筛选出该工程师的项目,无需手动再输入

3. 注意事项

  • 如果项目列表页 /kaohejghz 的 keyname 不支持按编制人搜索,需要在该页 Mapper XML 的 keyname 条件里加上 OR k.xmbz LIKE ...,确保工程师姓名能搜到
  • 导出接口路径 /exportEngineerStat,确保 Controller 类上有 @RequestMapping("/kaohe")
  • POI 版本 4.1.2 兼容 JDK 8,和你现有环境一致