Luckysheet/Excel 模板解析:只提取指定单元格的数据完整思路 & 落地实现

整体方案:模板标记选区→上传解析→按标记坐标定向提取单元格,分 4 大步骤,适配 Excel 文件上传解析 + Luckysheet 在线模板两种场景,精准过滤灰色非采集单元格。

一、整体设计思路(核心原理)

1. 前置:在模板中给「红色采集框」绑定元数据(关键)

不能靠颜色识别(Excel 颜色易修改、色差干扰),推荐 2 种标记方案二选一

方案 A【推荐:选区坐标入库】
  1. 在前端 Luckysheet 里,用户鼠标框选红色录入区域,调用luckysheet.getRange()拿到所有红框坐标数组([{row:[s,e],col:[s,e]}]);
  2. 每个 sheet 的采集区域坐标、单元格规则 和模板文件绑定存入数据库:模板ID → [采集区域列表]

例:当前表格红框坐标:

  • C2:D2 → row1,1,col2,3
  • C5:D6 → row4,5,col2,3
  • D7 → row6,6,col3,3
  • C8:D9 → row7,8,col2,3
  1. 灰色禁用单元格不录入坐标,解析时自动跳过。
方案 B【单元格批注标记】

Excel/Luckysheet 给红色单元格加批注:need_collect=1,灰色单元格不加批注;解析文件时遍历单元格,仅提取批注带采集标识的单元格

❌ 不推荐靠单元格背景色区分:用户修改红颜色值、格式刷变色会导致解析失效。

2. 上传文件阶段

用户上传 Excel(.xlsx/.xls),后端先匹配数据库里该报表模板对应的采集坐标清单

3. 文件解析阶段

后端使用 POI (Java)/openpyxl (Python) 打开 Excel,只循环遍历预存的采集坐标单元格,不在坐标清单里的单元格(灰色区域)直接跳过不取数。

4. 数据结构化落地

项目名称(行维度)→字段(C/D列)组装成 JSON 入库。

二、分步落地实现(分前端 + 后端)

步骤 1:前端 Luckysheet 配置模板(标记红框采集区,存坐标)

1.1 用户框选红框,自动获取选区坐标

javascript

复制代码
// 用户框选完红框后,点击【保存模板采集区域】按钮触发
function saveCollectRange(){
    // 获取所有选中红框区域
    const collectRanges = luckysheet.getRange();
    // 1. 列下标转Excel列名、行+1转Excel真实行号
    const excelRanges = collectRanges.map(range=>{
        const {row,column} = range;
        const startRow = row[0]+1,endRow = row[1]+1;
        const startCol = numToABC(column[0]),endCol = numToABC(column[1]);
        return `${startCol}${startRow}:${endCol}${endRow}`
    })
    // 2. 把【模板ID+excelRanges采集区域数组】传给后端入库
    axios.post('/api/template/saveRange',{
        templateId:"G4B_III",
        sheetName:"Sheet1",
        collectAreas:excelRanges
    })
}
// 列数字→A/B/C工具函数
function numToABC(n){
    let str='';while(n>=0){str=String.fromCharCode(n%26+65)+str;n=Math.floor(n/26)-1}return str;
}

示例入库后数据库存储:templateId:G4B_III,collectAreas:"C2:D2","C5:D6","D7:D7","C8:D9"

1.2 预览 / 修改采集区:代码自动选中红框

javascript

复制代码
// 后端取出采集坐标,转回luckysheet下标,自动高亮红框
const areaList = ["C2:D2","C5:D6"];
// 坐标转luckysheet下标(A1→col:0,row:0),调用setRange自动选中

步骤 2:后端 Excel 文件解析(Java POI 示例,只提取标记区域)

2.1 逻辑流程
  1. 根据上传文件名 / 报表编码查库,取出该模板collectAreas:["C2:D2","C5:D6"...]
  2. POI 打开 Excel,遍历每个采集区域;
  3. 只读取区域内单元格值,灰色无标记单元格直接忽略。
2.2 关键代码片段(Java POI)

java

复制代码
// 1.从数据库查出该模板所有采集区域
List<String> collectAreas = templateMapper.getCollectAreaByTemplateId("G4B_III");
// 2.解析每个区域
for(String area:collectAreas){
    // 拆分 C5:D6 → 起始单元格C5、结束D6
    String[] split = area.split(":");
    CellRangeAddress range = CellRangeAddress.valueOf(area);
    // 循环区域内所有行、列
    for(int r=range.getFirstRow();r<=range.getLastRow();r++){
        Row row = sheet.getRow(r);
        for(int c=range.getFirstColumn();c<=range.getLastColumn();c++){
            Cell cell = row.getCell(c,Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
            String cellValue = cell.getStringCellValue();
            // 按行项目+列字段组装数据
            saveCellData(row,c,cellValue);
        }
    }
}

步骤 3:数据映射结构化(业务组装)

表格规则:

  • B 列(项目名称:1. 衍生工具...2. 证券融资...)作为数据主键;
  • C 列 = 名义本金,D 列 = 风险加权资产;提取后输出结构:

json

复制代码
[
  {"itemName":"1.与非中央交易对手的衍生工具...","C_名义本金":"","D_风险加权资产":""},
  {"itemName":"2.与非中央交易对手的证券融资交易...","C_名义本金":"","D_风险加权资产":""},
  {"itemName":"3.信用估值调整风险","D_风险加权资产":""},
  {"itemName":"4.与中央交易对手交易形成的信用风险","C_名义本金":"","D_风险加权资产":""},
  {"reportDate":"2025年12月","unit":"万元"}
]

三、补充优化方案(2 种备选)

备选 1:若无法提前配置模板坐标(无模板库)

  1. 前端导出模板时,红色单元格统一自定义单元格格式 / 批注标识needCollect=1
  2. 后端全量遍历 Excel 所有单元格,仅提取批注包含 needCollect 的单元格,灰色单元格无批注直接跳过。

备选 2:基于 Luckysheet 在线填报(不上传 Excel,在线填表)

用户在线在 Luckysheet 填表,前端监听红框区域单元格值变更,只收集标记区域数据,直接提交后端,无需解析 Excel 文件。

Java+Vue+Luckysheet 完整 Demo

整体架构:

  • 前端 Vue3 + Luckysheet:在线配置报表、框选红色采集区域、保存采集坐标到后端、预览模板
  • 后端 SpringBoot + POI:保存模板采集坐标、接收 Excel 上传、按预存红框坐标解析 Excel,只提取红框数据
  • 业务:G4B 报表,红框 = 采集单元格、灰色 = 跳过不采集

一、后端 SpringBoot(Java)

1. pom.xml 依赖

xml

复制代码
<!-- SpringBoot Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- POI解析Excel -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.5</version>
</dependency>
<!-- lombok简化代码 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2. 实体类 TemplateEntity(内存模拟库,正式换 Mybatis+MySQL)

java

复制代码
import lombok.Data;
import java.util.List;

@Data
public class TemplateEntity {
    // 模板编码 G4B_III
    private String templateId;
    // sheet页名称
    private String sheetName;
    // 采集区域:["C2:D2","C5:D6","D7:D7","C8:D9"] Excel区域字符串
    private List<String> collectAreas;
}

3. 内存存储工具(代替数据库)

java

复制代码
import java.util.HashMap;
import java.util.Map;

public class TemplateCache {
    public static Map<String, TemplateEntity> templateMap = new HashMap<>();
}

4. Controller 核心接口(3 个接口:保存选区、获取模板、解析 Excel)

java

复制代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.*;

@RestController
@RequestMapping("/api/template")
@CrossOrigin // 跨域
public class TemplateController {

    // 1. 前端保存红框采集区域到后端
    @PostMapping("/saveRange")
    public Map<String, Object> saveTemplate(@RequestBody TemplateEntity entity) {
        TemplateCache.templateMap.put(entity.getTemplateId(), entity);
        return Map.of("code",200,"msg","保存采集区域成功");
    }

    // 2. 根据模板ID查询已保存的采集区域(前端回显红框)
    @GetMapping("/get/{templateId}")
    public TemplateEntity getTemplate(@PathVariable String templateId) {
        return TemplateCache.templateMap.get(templateId);
    }

    // 3. 上传Excel,按预存红框区域解析,只提取红框数据
    @PostMapping("/parseExcel")
    public Map<String,Object> parseExcel(@RequestParam String templateId, @RequestParam MultipartFile file) throws Exception {
        TemplateEntity template = TemplateCache.templateMap.get(templateId);
        if(template == null){
            return Map.of("code",500,"msg","模板不存在,请先配置采集区域");
        }
        List<String> areaList = template.getCollectAreas();
        List<Map<String,Object>> resultData = new ArrayList<>();

        try(InputStream is = file.getInputStream();
            Workbook workbook = WorkbookFactory.create(is)){
            Sheet sheet = workbook.getSheet(template.getSheetName());
            // 循环每一个红框区域
            for(String areaStr : areaList){
                CellRangeAddress range = CellRangeAddress.valueOf(areaStr);
                // 遍历区域内所有单元格
                for(int r = range.getFirstRow(); r <= range.getLastRow(); r++){
                    Row row = sheet.getRow(r);
                    if(row == null) continue;
                    for(int c = range.getFirstColumn(); c <= range.getLastColumn(); c++){
                        Cell cell = row.getCell(c, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
                        String cellVal = cell.getStringCellValue().trim();
                        Map<String,Object> cellInfo = new HashMap<>();
                        cellInfo.put("excelRange",areaStr);
                        cellInfo.put("cellAddr",getColName(c)+(r+1));
                        cellInfo.put("value",cellVal);
                        resultData.add(cellInfo);
                    }
                }
            }
        }
        return Map.of("code",200,"data",resultData,"msg","解析完成,仅返回红框采集数据");
    }

    // 列下标转A/B/C (0→A,1→B,2→C)
    private String getColName(int colIndex){
        StringBuilder sb = new StringBuilder();
        int n = colIndex;
        while(n >=0){
            sb.insert(0,(char)('A'+(n%26)));
            n = n/26 -1;
        }
        return sb.toString();
    }
}

二、Vue3 前端(Vite+Vue3+Luckysheet)

1. 安装依赖

bash

复制代码
npm install luckysheet axios

2. 单页面代码 TemplateG4B.vue

vue

复制代码
<template>
  <div>
    <div style="margin:10px">
      <el-button @click="saveCollectRange">保存选中红框采集区域</el-button>
      <el-button @click="loadTemplate">加载已保存模板(自动选中红框)</el-button>
      <el-upload :before-upload="uploadExcel">
        <el-button type="primary">上传Excel解析红框数据</el-button>
      </el-upload>
    </div>
    <!-- luckysheet渲染容器 -->
    <div id="luckysheet" style="width:100%;height:700px;border:1px solid #ccc"></div>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import axios from 'axios'
import luckysheet from 'luckysheet'
import 'luckysheet/dist/plugins/css/index.css'

const TEMPLATE_ID = 'G4B_III'
let luckysheetIns = null

// 列数字转ABC
const numToABC = (n)=>{
  let s = ''
  while(n>=0){
    s = String.fromCharCode(n%26+65)+s
    n = Math.floor(n/26)-1
  }
  return s
}
// ABC转列下标
const ABCtoNum = (str)=>{
  let res = -1
  for(let s of str){
    res = res*26 + s.charCodeAt()-'A'+1
  }
  return res-1
}
// A1:D5 拆分成luckysheet range格式 {row:[s,e],column:[s,e]}
const areaToRange = (areaStr)=>{
  const [start,end] = areaStr.split(':')
  // 拆分行列
  const parsePos = (pos)=>{
    let colStr = pos.replace(/[0-9]/g,'')
    let row = Number(pos.replace(/[A-Z]/g,''))-1
    let col = ABCtoNum(colStr)
    return {row,col}
  }
  const s = parsePos(start)
  const e = parsePos(end)
  return {
    row:[s.row,e.row],
    column:[s.col,e.col]
  }
}

// 初始化luckysheet
onMounted(()=>{
  luckysheetIns = luckysheet.create({
    container:'luckysheet',
    showSheetBar:true,
    // 导入你的G4B模板数据(可从excel导入,这里省略)
    data:[[]]
  })
})

// 1. 保存当前选中红框选区到后端
const saveCollectRange = async()=>{
  // 获取用户框选的多个区域
  const selectRanges = luckysheet.getRange()
  // 转成 C2:D2 格式字符串
  const excelAreas = selectRanges.map(item=>{
    const sc = numToABC(item.column[0])
    const ec = numToABC(item.column[1])
    const sr = item.row[0]+1
    const er = item.row[1]+1
    return `${sc}${sr}:${ec}${er}`
  })
  // 请求后端保存
  await axios.post('http://localhost:8080/api/template/saveRange',{
    templateId:TEMPLATE_ID,
    sheetName:'Sheet1',
    collectAreas:excelAreas
  })
  alert('红框采集区域保存成功!')
}

// 2. 读取后端模板,自动选中红框
const loadTemplate = async()=>{
  const res = await axios.get(`http://localhost:8080/api/template/get/${TEMPLATE_ID}`)
  const {collectAreas} = res.data
  // 转为luckysheet选中格式
  const luckRanges = collectAreas.map(item=>areaToRange(item))
  // 自动选中所有红框
  luckysheet.setRange(luckRanges)
}

//3. 上传Excel文件解析
const uploadExcel = async(file)=>{
  const form = new FormData()
  form.append('templateId',TEMPLATE_ID)
  form.append('file',file)
  const res = await axios.post('http://localhost:8080/api/template/parseExcel',form)
  console.log('解析结果(仅红框数据)',res.data.data)
  alert('解析成功,控制台查看红框采集数据')
  return false
}
</script>

三、使用操作步骤

  1. 后端启动 SpringBoot(端口 8080)
  2. 前端启动 vue 项目
  3. 操作流程: ① 在 Luckysheet 表格里,鼠标Ctrl+多选红框单元格(你截图里所有红色采集区)② 点击【保存选中红框采集区域】→ 后端存入该模板的采集坐标③ 下次打开页面点击【加载已保存模板】,程序自动框选所有红框④ 上传用户填报好的 Excel 文件 → 后端只解析预存红框区域单元格,灰色单元格直接跳过不读取,返回所有红框的数据

四、关键业务扩展

  1. 正式环境替换内存存储:TemplateCache换成 MySQL+Mybatis,templateId主键存储每个报表的采集区域。
  2. 灰色禁用单元格: 在模板配置时,灰色单元格设置luckysheet.setCellReadOnly(row,col,true),前端不可编辑,解析天然忽略。
  3. **数据结构化:**解析后根据 B 列项目名称 + C/D 列字段,组装成业务 JSON(项目 - 名义本金 - 风险加权资产)。
相关推荐
~朴:shu5 个月前
Luckysheet 远程搜索下拉 控件开发 : 揭秘二开全流程
luckysheet·远程搜索下拉控件·luckysheet二开·增强luckysheet·拓展lucky sheet·接口获取数据填充·接口获取数据下拉控件
~朴:shu1 年前
Luckysheet 实现 excel 多人在线协同编辑(全功能实现增强版)
luckysheet·luckysheet两个实例·luckysheet协同编辑·luckysheet协同演示·excel 协同编辑·在线 excel 协同编辑·在线电子表格协同编辑
vener_2 年前
LuckySheet协同编辑后端示例(Django+Channel,Websocket通信)
javascript·后端·python·websocket·django·luckysheet
~朴:shu3 年前
Luckysheet 实现excel多人在线协同编辑
luckysheet·luckysheet协同·文件导入、导出·vue在线表格·vue-excel·协同编辑excel·vue在线电子表格