跳转到内容

$excel - Excel 表格

更新: 2026/8/9 字数: 0 字 时长: 0 分钟

$excel 模块提供了完整的 Excel 文件操作能力,支持在 Android 环境中创建、编辑、查询和处理 Excel 文件。无论是简单的数据读写,还是复杂的样式设置、数据验证、图表生成,都可以通过简洁的 API 轻松完成。

  • 文件操作:打开、创建、保存、另存为 Excel 文件,支持密码保护
  • 工作表管理:创建、删除、重命名、复制工作表,支持冻结窗格和筛选
  • 数据读写:单元格级别的数据读写,支持批量写入和读取
  • 数据查询:类似 SQL 的条件查询,支持 >, <, >=, <=, =, !=, LIKE, AND, OR
  • 样式设置:字体、颜色、背景、边框、对齐、数字格式等完整样式支持
  • 数据验证:下拉列表、整数、小数、日期、时间、文本长度等验证规则
  • 合并单元格:合并、取消合并、重叠检测
  • 图片插入:支持从文件路径、字节数组、Bitmap 插入图片
  • 批注和超链接:单元格批注和超链接支持
  • 行/列操作:插入、删除、隐藏、行高列宽设置

本模块基于 Apache POI 实现,支持两种 Excel 文件格式:

格式扩展名说明
XLSX.xlsx推荐使用。基于 XML 的开放格式,支持更多功能(如更多行/列、更好的样式支持、数据验证、图表等),文件体积更小,兼容性更好。
XLS.xls旧版二进制格式,功能有限(最大 65536 行、256 列),样式和验证支持较弱。

推荐

强烈建议使用 .xlsx 格式,以获得更好的功能支持和兼容性。本模块的密码保护、高级样式、数据验证等高级功能均对 .xlsx 提供完整支持。

$excel.open(options[, callback])

  • options {Object} 文件配置
    • path {string} 文件路径(绝对路径,如 /sdcard/example.xlsx
    • readOnly {boolean} 是否以只读模式打开,默认 false
    • password {string} 文件密码(用于打开或保存密码保护的 Excel 文件,目前仅支持 .xlsx 格式)
  • callback {Object} 回调对象(可选)
    • onOpen(excel) 打开成功后回调,excelExcel 对象
  • 返回 Excel

打开或创建一个 Excel 文件。如果文件不存在,则会自动创建一个新的 Excel 文件。

注意

只读模式下无法调用 excel.save()

js
var excel = $excel.open({
    path: "/sdcard/data.xlsx"
});

// 修改数据(示例:添加一个 Sheet 并写入内容)
var sheet = excel.sheet("数据");
if (!sheet) {
    sheet = excel.createSheet("数据");
}
sheet.cell(0, 0).setValue("姓名");
sheet.cell(0, 1).setValue("年龄");
sheet.cell(1, 0).setValue("张三");
sheet.cell(1, 1).setValue(25);

// 保存更改到磁盘
excel.save();
console.log("✅ 数据已保存到 data.xlsx");
excel.close();
js
var path = "/sdcard/report.xlsx";

// 准备示例数据(仅在文件不存在时创建)
if (!files.exists(path)) {
    var excel = $excel.open({ path: path });

    var sheet = excel.sheet("报表");
    if (!sheet) {
        sheet = excel.createSheet("报表");
    }

    sheet.cell(0, 0).setValue("姓名");
    sheet.cell(0, 1).setValue("年龄");
    sheet.cell(1, 0).setValue("张三");
    sheet.cell(1, 1).setValue(25);

    excel.save();
    excel.close();
}

// 以只读模式打开
var excel = $excel.open({
    path: path,
    readOnly: true
});

// 读取数据
var sheet = excel.sheet("报表");
if (sheet) {
    console.log("姓名:", sheet.cell(1, 0).value());
    console.log("年龄:", sheet.cell(1, 1).value());
}

excel.close();
js
var path = "/sdcard/secret.xlsx";
var password = "123456";

// 准备示例文件(仅首次运行时执行)
if (!files.exists(path)) {
    var excel = $excel.open({
        path: path,
        password: password
    });

    var sheet = excel.createSheet("机密数据");
    sheet.cell(0, 0).setValue("姓名");
    sheet.cell(0, 1).setValue("张三");

    excel.save();
    excel.close();
}

// 使用密码打开文件
var excel = $excel.open({
    path: path,
    password: "123456"
});

var sheet = excel.sheet("机密数据");
if (sheet) {
    console.log("姓名:", sheet.cell(0, 1).value());
}

excel.close();
js
var excel = $excel.open({
    path: "/sdcard/data.xlsx"
}, {
    onOpen: function (excel) {
        console.log("✅ 文件打开成功");
        console.log("📊 Sheet 数量:", excel.sheetCount());

        // 在回调中操作并保存
        var sheet = excel.sheet("新增Sheet");
        if (!sheet) sheet = excel.createSheet("新增Sheet");
        sheet.cell(0, 0).setValue("测试数据");

        excel.save();
        console.log("✅ 数据已保存");

    }
});

excel.close();

Excel

$excel.open() 返回,代表一个 Excel 工作簿。

Excel.sheetCount()

  • 返回 {number}

获取工作簿中的工作表总数。

js
console.log("Sheet 总数:", excel.sheetCount());

Excel.sheetNames()

  • 返回 {Array<string>}

获取所有工作表的名称列表。

js
var names = excel.sheetNames();
for (var i = 0; i < names.length; i++) {
    console.log("📄", names[i]);
}

Excel.sheet(name)

  • name {string} 工作表名称
  • 返回 ExcelSheet | null

根据名称获取工作表。如果不存在则返回 null

js
var sheet = excel.sheet("数据表");
if (sheet) {
    console.log("✅ 找到工作表");
} else {
    console.log("❌ 工作表不存在");
}

Excel.sheet(index)

  • index {number} 工作表索引(从 0 开始)
  • 返回 ExcelSheet | null

根据索引获取工作表。如果越界则返回 null

js
// 获取第一个工作表
var firstSheet = excel.sheet(0);
console.log("第一个工作表:", firstSheet.name());

Excel.activeSheet()

获取当前激活的工作表。如果没有激活的 Sheet,则返回第一个 Sheet。

js
var active = excel.activeSheet();
console.log("当前激活的 Sheet:", active.name());

Excel.setActiveSheet(index)

  • index {number} 工作表索引

通过索引设置激活的工作表。

js
excel.setActiveSheet(1);

Excel.setActiveSheet(name)

  • name {string} 工作表名称

通过名称设置激活的工作表。

js
excel.setActiveSheet("数据表");

Excel.createSheet(name)

  • name {string} 新工作表的名称
  • 返回 ExcelSheet

创建一个新的工作表。

js
var newSheet = excel.createSheet("新表格");
console.log("✅ 已创建:", newSheet.name());

Excel.hasSheet(name)

  • name {string} 工作表名称
  • 返回 {boolean}

判断指定名称的工作表是否存在。

js
if (excel.hasSheet("数据表")) {
    console.log("✅ 数据表已存在");
}

Excel.removeSheet(index)

  • index {number} 工作表索引

根据索引删除工作表。

js
excel.removeSheet(0);

Excel.removeSheet(name)

  • name {string} 工作表名称

根据名称删除工作表。

js
excel.removeSheet("临时表");

Excel.renameSheet(index, newName)

  • index {number} 工作表索引
  • newName {string} 新名称

重命名工作表。

js
excel.renameSheet(0, "主数据");

Excel.renameSheet(oldName, newName)

  • oldName {string} 原名称
  • newName {string} 新名称

根据名称重命名工作表。

js
excel.renameSheet("Sheet1", "销售数据");

Excel.copySheet(sourceIndex, newName)

  • sourceIndex {number} 源工作表索引
  • newName {string} 新工作表名称

复制工作表(含内容和格式)。

js
excel.copySheet(0, "备份");

Excel.copySheet(sourceName, newName)

  • sourceName {string} 源工作表名称
  • newName {string} 新工作表名称

根据名称复制工作表。

js
excel.copySheet("数据表", "数据表_副本");

Excel.save([options])

  • options {Object} 保存配置(可选)
    • path {string} 保存文件路径(可选,默认使用 $excel.open() 时指定的文件路径)
    • password {string} 文件密码(可选,默认使用 $excel.open() 时指定的密码;仅支持 .xlsx 格式)
  • 异常 {IOException}
  • 返回 {void}

保存工作簿到文件。

如果不传 options,则使用打开 Excel 时的文件路径和密码保存。

如果传入 options,可以指定新的保存路径或修改文件密码:

  • 只传 path:另存为新文件,保持原密码
  • 只传 password:保存到原文件,并修改密码
  • 同时传 pathpassword:保存到指定文件,并使用新的密码

如果文件以只读模式打开,调用此方法会抛出异常。

注意

密码保护功能目前仅支持 .xlsx 文件格式。

js
var excel = $excel.open({
    path: "/sdcard/data.xlsx"
});

var sheet = excel.sheet("Sheet1");
if (!sheet) sheet = excel.createSheet("Sheet1");
sheet.cell(0, 0).setValue("Hello Excel");


// 保存到原文件
excel.save();

console.log("💾 保存成功");
js
var excel = $excel.open({
    path: "/sdcard/data.xlsx"
});


var sheet = excel.sheet("Sheet1");
if (!sheet) sheet = excel.createSheet("Sheet1");
sheet.cell(0, 0).setValue("新的文件");

// 保存到新的路径
excel.save({
    path: "/sdcard/data_copy.xlsx"
});


console.log("📄 已另存为 data_copy.xlsx");
js
var excel = $excel.open({
    path: "/sdcard/secret.xlsx",
    password: "123456"
});

// 修改数据
var sheet = excel.sheet("数据");
if (!sheet) sheet = excel.createSheet("Sheet1");
sheet.cell(0, 0).setValue("修改后的内容");

// 使用新密码保存
excel.save({
    password: "888888"
});


console.log("🔐 密码已修改");
js
var excel = $excel.open({
    path: "/sdcard/source.xlsx",
    password: "123456"
});


// 修改内容
var sheet = excel.sheet("Sheet1");
if (!sheet) sheet = excel.createSheet("Sheet1");
sheet.cell(0, 0).setValue("新文件");



// 保存到新文件,并设置新密码
excel.save({
    path: "/sdcard/new.xlsx",
    password: "888888"
});


console.log("✅ 已生成新的密码文件");
js
var excel = $excel.open({
    path: "/sdcard/secret.xlsx",
    password: "123456"
});

// 保存为普通 Excel 文件
excel.save({
    path: "/sdcard/plain.xlsx",
    password: null
});


console.log("🔓 已取消密码保护");

Excel.readOnly()

  • 返回 {boolean}

判断当前工作簿是否为只读模式。

js
if (excel.readOnly()) {
    console.log("🔒 只读模式");
}

Excel.xlsx()

  • 返回 {boolean}

判断当前工作簿是否为 .xlsx 格式(XSSF)。

js
if (excel.xlsx()) {
    console.log("📄 文件格式: .xlsx");
}

Excel.xls()

  • 返回 {boolean}

判断当前工作簿是否为 .xls 格式(HSSF)。

js
if (excel.xls()) {
    console.log("📄 文件格式: .xls");
}

Excel.close()

关闭工作簿,释放资源。

js
excel.close();
console.log("🔒 已关闭");

ExcelSheet

Excel.sheet(name)Excel.createSheet(name)Excel.activeSheet() 等方法返回,代表一个工作表。

ExcelSheet.name()

  • 返回 {string}

获取工作表名称。

js
console.log("名称:", sheet.name());

ExcelSheet.setName(name)

  • name {string} 新名称

设置工作表名称。

js
sheet.setName("新名称");

ExcelSheet.lastRow()

  • 返回 {number}

获取最后一行的索引(从 0 开始)。

js
console.log("最后一行:", sheet.lastRow());

ExcelSheet.lastColumn()

  • 返回 {number}

获取最后一列的索引(从 0 开始)。

js
console.log("最后一列:", sheet.lastColumn());

ExcelSheet.cell(address)

  • address {string} 单元格地址,如 "A1""B2"
  • 返回 ExcelCell

通过地址获取单元格。

js
var cell = sheet.cell("A1");
cell.setValue("Hello");

ExcelSheet.cell(row, col)

  • row {number} 行索引(从 0 开始)
  • col {number} 列索引(从 0 开始)
  • 返回 ExcelCell

通过行列索引获取单元格。

js
var cell = sheet.cell(0, 0);
cell.setValue("Hello");

ExcelSheet.range(address)

  • address {string} 范围地址,如 "A1:B10""C5"
  • 返回 ExcelRange

通过地址获取范围。

js
var range = sheet.range("A1:C10");

ExcelSheet.range(startRow, startCol, endRow, endCol)

  • startRow {number} 起始行索引
  • startCol {number} 起始列索引
  • endRow {number} 结束行索引
  • endCol {number} 结束列索引
  • 返回 ExcelRange

通过行列索引获取范围。

js
var range = sheet.range(0, 0, 10, 2);

ExcelSheet.range(row, col)

  • row {number} 行索引
  • col {number} 列索引
  • 返回 ExcelRange

获取单个单元格的范围。

js
var cellRange = sheet.range(0, 0);

ExcelSheet.row(index)

获取行对象。

js
var row = sheet.row(0);
console.log("行:", row.index());

ExcelSheet.insertRow(rowIndex)

  • rowIndex {number} 插入位置(从 0 开始)
  • 返回 {boolean}

插入一行。

js
sheet.insertRow(1);

ExcelSheet.deleteRow(rowIndex)

  • rowIndex {number} 要删除的行索引
  • 返回 {boolean}

删除一行。

js
sheet.deleteRow(1);

ExcelSheet.insertColumn(colIndex)

  • colIndex {number} 插入位置(从 0 开始)
  • 返回 {ExcelSheet}

插入一列(链式调用)。

js
sheet.insertColumn(1);

ExcelSheet.deleteColumn(colIndex)

  • colIndex {number} 要删除的列索引
  • 返回 {ExcelSheet}

删除一列(链式调用)。

js
sheet.deleteColumn(1);

ExcelSheet.freeze(row, col)

  • row {number} 冻结的行数
  • col {number} 冻结的列数
  • 返回 {ExcelSheet}

冻结窗格。

js
sheet.freeze(1, 0);  // 冻结第1行
sheet.freeze(0, 1);  // 冻结第A列
sheet.freeze(2, 1);  // 冻结前2行和前1列

ExcelSheet.unfreeze()

取消冻结窗格。

js
sheet.unfreeze();

ExcelSheet.autoFilter(range)

  • range {string} 筛选范围地址,如 "A1:D100"
  • 返回 {ExcelSheet}

设置自动筛选。

js
sheet.autoFilter("A1:D100");

ExcelSheet.hasAutoFilter()

  • 返回 {boolean}

判断是否设置了自动筛选。

js
if (sheet.hasAutoFilter()) {
    console.log("有自动筛选");
}

ExcelSheet.autoFilterRange()

  • 返回 {string} | null

获取自动筛选的范围。

js
var range = sheet.autoFilterRange();
console.log("筛选范围:", range);

ExcelSheet.removeFilter()

  • 返回 {boolean}

移除自动筛选。

js
sheet.removeFilter();

ExcelSheet.setProtect([password])

  • password {string} 可选,保护密码
    • 传入密码字符串:保护工作表(使用该密码)
    • 不传参数或传入 null:取消保护
  • 返回 {ExcelSheet}
js
// 使用密码保护
sheet.setProtect("password123");

// 取消保护
sheet.setProtect();
// 或
sheet.setProtect(null);

ExcelSheet.protect()

  • 返回 {boolean}

判断工作表是否被保护。

js
if (sheet.protect()) {
    console.log("🔒 工作表已保护");
} else {
    console.log("🔓 工作表未保护");
}
js
// 检查是否已保护
if (sheet.protect()) {
    console.log("已保护,先取消");
    sheet.setProtect(); // 取消保护
}

// 设置密码保护
sheet.setProtect("password123");
console.log("是否保护:", sheet.protect()); // true

// 取消保护(两种方式等效)
sheet.setProtect(); // 取消保护
console.log("是否保护:", sheet.protect()); // false

// 再次设置密码保护
sheet.setProtect("abc");
console.log("是否保护:", sheet.protect()); // true

// 传入 null 取消保护
sheet.setProtect(null);
console.log("是否保护:", sheet.protect()); // false

ExcelSheet.write(data[, startRow])

  • data {Array} 二维数组数据(行数组)
  • startRow {number} 可选,起始行索引(从 0 开始)。若省略,则追加到工作表末尾(从最后一行之后开始写入)
  • 返回 {ExcelRange | null} 写入的数据区域,如果 data 为空则返回 null

写入数据到工作表。

  • 省略 startRow 时,数据追加到当前最后一行之后(新表从第 0 行开始)。
  • 指定 startRow 时,从该行开始覆盖写入(不会清除超出范围的行)。
js

var range = sheet.write([
    ["姓名", "年龄", "分数"],
    ["张三", 25, 90],
    ["李四", 30, 85]
]);
if (range) {
    // 🔍 查看 range 信息
    console.log("📍 地址:", range.address());           // A1:C2
    console.log("📊 行数:", range.height());              // 2
    console.log("📊 列数:", range.width());           // 3
    console.log("📊 单元格数:", range.cellCount());         // 6
    console.log("📋 值:", JSON.stringify(range.values(), null, 2));
    
    // 🎨 设置样式
    range.style().bold(true).background("#FFFF00");
}
js
var range = sheet.write([
    ["产品", "价格"],
    ["苹果", 5.5]
], 3);
if (range) {
    console.log("📍 写入区域:", range.address());       // A4:B5
    console.log("📊 行数:", range.height());              // 2
    console.log("📊 列数:", range.width());           // 2
    console.log("📋 值:", JSON.stringify(range.values(), null, 2));
}

ExcelSheet.read()

  • 返回 {Array}

读取所有数据(二维数组)。

js
var data = sheet.read();
console.log(data);

ExcelSheet.readRows()

  • 返回 {Array<Object>}

将工作表数据读取为对象数组,每一行是一个对象,对象的键为列名。

读取范围从 ExcelSheet.dataRow() 开始(有表头时自动跳过表头行,无表头时从第 0 行开始)。

js
sheet.setHeaderRow(0);//设置表头行
var rows = sheet.readRows();
for (var i = 0; i < rows.length; i++) {
    console.log(rows[i].姓名, rows[i].年龄);      // 有表头时,使用表头名称
    // console.log(rows[i].A, rows[i].B);   // 无表头时,第一行第一列
}

ExcelSheet.createHeader(columns[, row])

  • columns {Array} 列名数组
  • row {number} 可选,表头行索引(从 0 开始)。若省略,则使用当前表头行(默认第 0 行)
  • 返回 {ExcelSheet}

创建表头,并将工作表切换为“有表头”模式。

  • 如果指定了 row,则在指定行创建表头,并将表头行设为该行。
  • 如果省略 row,则在当前表头行创建表头。若从未设置表头行,则默认使用第 0 行。
js
// 使用当前表头行(默认为第 0 行)
sheet.createHeader(["姓名", "年龄", "分数"]);

// 在指定行(第 3 行)创建表头
sheet.createHeader(["产品", "价格", "库存"], 3);

ExcelSheet.noHeader()

切换为无表头模式。

js
sheet.noHeader();

ExcelSheet.setHeaderRow(row)

  • row {number} 表头行索引(从 0 开始)。若传入 -1,则切换为无表头模式
  • 返回

设置表头所在的行,并自动启用表头模式。

row-1 时,将取消表头并切换为无表头模式,效果等同于调用 ExcelSheet.noHeader()

js
// 设置表头在第 1 行
sheet.setHeaderRow(1);

// 取消表头(无表头模式)
sheet.setHeaderRow(-1);

ExcelSheet.hasHeader()

  • 返回 {boolean}

判断是否有表头。

js
if (sheet.hasHeader()) {
    console.log("✅ 有表头");
}

ExcelSheet.headerRow()

  • 返回 {number}

获取表头行索引。

js
console.log("表头行:", sheet.headerRow());

ExcelSheet.dataRow()

  • 返回 {number}

获取数据区域的起始行索引。

  • 有表头时:返回 headerRow + 1
  • 无表头时:返回 0
js
// 第一行为表头
sheet.setHeaderRow(0);

console.log(sheet.dataRow()); // 1

// 无表头
sheet.noHeader();

console.log(sheet.dataRow()); // 0

ExcelSheet.insert(values[, afterRow])

  • values {Object} 单行数据(Map)或多行数据(Array)
  • afterRow {number} 可选,在哪个行号之后插入(从 0 开始)。若省略,则追加到工作表末尾
  • 返回 {ExcelRange | null} 插入的数据区域,如果未插入任何行则返回 null

在指定行之后插入数据,并返回新插入的数据区域。

  • 省略 afterRow 时,数据追加到当前最后一行之后(新表从第 0 行开始)。
  • 指定 afterRow 时,在该行之后插入数据(原有行自动下移)。
js
// 首次创建表头(位于第 3 行)
sheet.createHeader(["姓名", "年龄"], 3);

// 已有表头的 Excel,重新打开后只需指定表头行即可
// sheet.setHeaderRow(3);

var range = sheet.insert({ "姓名": "张三", "年龄": 18 });
if (range) {
    // 🔍 查看 range 信息
    console.log("📍 插入区域:", range.address());        // A1:B1
    console.log("📊 行数:", range.height());               // 1
    console.log("📊 列数:", range.width());            // 2
    console.log("📋 值:", JSON.stringify(range.values(), null, 2));
    
    // 🎨 设置样式
    range.style().bold(true).background("#FFFF00");
}
js
// 首次创建表头(位于第 3 行)
sheet.createHeader(["姓名", "年龄"], 3);

// 已有表头的 Excel,重新打开后只需指定表头行即可
// sheet.setHeaderRow(3);

var range = sheet.insert([
    { "姓名": "李四", "年龄": 19 },
    { "姓名": "王五", "年龄": 20 }
], 4);
if (range) {
    console.log("📍 插入区域:", range.address());        // A2:B3
    console.log("📊 行数:", range.height());               // 2
    console.log("📊 列数:", range.width());            // 2
    console.log("📊 单元格数:", range.cellCount());          // 4
    console.log("📋 值:", JSON.stringify(range.values(), null, 2));
}
js
// 切换为无表头模式(或直接不创建表头)
sheet.noHeader();

// 插入数据时,键名必须是 Excel 列字母(A, B, C...)
var range = sheet.insert({ "A": "产品", "B": "价格" });
if (range) {
    console.log("📍 插入区域:", range.address());        // A1:B1
    console.log("📋 值:", JSON.stringify(range.values(), null, 2));
}

// 批量插入多条
var range2 = sheet.insert([
    { "A": "苹果", "B": 5.5 },
    { "A": "香蕉", "B": 3.2 }
]);
if (range2) {
    console.log("📍 插入区域:", range2.address());       // A2:B3(自动追加)
    console.log("📊 行数:", range2.height());              // 2
}

ExcelSheet.query([where])

  • where {string | Object} 可选 查询条件。若省略,则查询所有数据
  • 返回 {ExcelCursor}

执行查询,返回游标对象。游标提供了 count()all()single()update()delete() 等方法,用于操作结果集。

where 支持两种写法:字符串表达式对象条件,均可组合多个条件。

字符串表达式(推荐用于简单/动态查询)

字符串条件使用类似 SQL 的语法,支持:

  • 比较运算符:>, <, >=, <=, =, ==, !=, <>, LIKE
  • 逻辑运算符:AND, OR(支持括号分组)
  • 字符串值需用单引号 ' 或双引号 " 包围
  • 数字值无需引号
示例说明
"年龄 > 20"年龄大于 20
"姓名 = '张三'"姓名等于“张三”
"分数 >= 80 AND 班级 = '三班'"分数≥80 且 班级为“三班”
"姓名 = '张三' OR 姓名 = '李四'"姓名等于“张三”或“李四”
"(年龄 > 18 AND 班级 = '一班') OR (年龄 < 20 AND 班级 = '二班')"复合括号分组
"姓名 LIKE '张'"姓名包含“张”(不区分大小写)
"分数 != 90"分数不等于 90

注意LIKE 执行的是 contains 匹配(不区分大小写),而非通配符匹配。

对象条件(推荐用于精确/结构化查询)

对象条件以 JavaScript 对象形式编写,键为列名,值为比较值或运算符对象。

写法说明
{ 姓名: "张三" }简写:默认 =,即姓名等于“张三”
{ 年龄: { ">": 20 } }年龄大于 20
{ 分数: { ">=": 80 }, 班级: "三班" }多条件(默认 AND),分数≥80 且 班级为“三班”
{ 姓名: { "!=": "张三" } }姓名不等于“张三”
{ 年龄: { ">": 18, "<": 30 } }年龄在 18 到 30 之间(AND 关系)

支持的操作符>, <, >=, <=, =, ==, !=, <>, LIKE

注意:对象条件中的多个键之间是 AND 关系,不支持直接 OR,但可以通过字符串表达式实现 OR 逻辑。

操作符支持对照表

操作符字符串写法对象写法说明
等于=, =="=" 或省略(默认)值相等
不等于!=, <>"!=""<>"值不等
大于>">"大于
大于等于>=">="大于等于
小于<"<"小于
小于等于<="<="小于等于
模糊匹配LIKE(不区分大小写)"LIKE"字符串包含(不区分大小写)

混合使用场景

  • 忽略 wheresheet.query() 查询所有数据。
  • 字符串 + 逻辑组合sheet.query("年龄 > 20 AND 班级 = '三班'")
  • 对象条件sheet.query({ 年龄: { ">": 20 }, 班级: "三班" })
  • 对象条件 + LIKEsheet.query({ 姓名: { "LIKE": "张" } })

💡 提示:当列名包含特殊字符或空格时,建议使用对象条件,避免字符串解析歧义。

js
var excel = $excel.open({ path: "/sdcard/query_demo.xlsx" });
var sheet = excel.sheet("数据");
if (!sheet) sheet = excel.createSheet("数据");

// 1. 创建表头并插入测试数据
sheet.createHeader(["姓名", "年龄", "分数", "班级"]);
var insertRange = sheet.insert([
    { "姓名": "张三", "年龄": 18, "分数": 90, "班级": "一班" },
    { "姓名": "李四", "年龄": 19, "分数": 85, "班级": "二班" },
    { "姓名": "王五", "年龄": 20, "分数": 78, "班级": "一班" },
    { "姓名": "赵六", "年龄": 21, "分数": 92, "班级": "三班" },
    { "姓名": "孙七", "年龄": 19, "分数": 66, "班级": "二班" },
    { "姓名": "周八", "年龄": 22, "分数": 88, "班级": "三班" }
]);
if (insertRange) {
    console.log("✅ 插入数据区域:", insertRange.address());
    insertRange.style().border("thin").background("#F2F2F2");
}

// ========== 各种查询示例 ==========

// 2. 无条件查询(展示 range() 与 ranges() 的区别)
var cursor0 = sheet.query();
console.log("\n【无条件查询】总行数:", cursor0.count());
console.log("所有数据:", JSON.stringify(cursor0.all(), null, 2));
var range0 = cursor0.range();
console.log("📌 range() 连续范围:", range0.address(), "行数:", range0.height(), "列数:", range0.width());
var ranges0 = cursor0.ranges();
console.log("📌 ranges() 分组范围(共", ranges0.size(), "组):");
for (var i = 0; i < ranges0.size(); i++) {
    var r = ranges0.get(i);
    console.log("  组" + (i+1) + ":", r.address(), "行数:", r.height(), "列数:", r.width());
}

// 3. 字符串条件:年龄 > 20
var cursor1 = sheet.query("年龄 > 20");
console.log("\n【字符串条件:年龄 > 20】行数:", cursor1.count());
console.log("结果:", JSON.stringify(cursor1.all(), null, 2));

// 4. 字符串条件:AND 组合
var cursor2 = sheet.query("年龄 > 20 AND 班级 = '三班'");
console.log("\n【字符串条件:年龄 > 20 AND 班级 = '三班'】行数:", cursor2.count());
console.log("结果:", JSON.stringify(cursor2.all(), null, 2));

// 5. 字符串条件:OR 组合(演示 ranges().style() 对不连续区域应用样式)
var cursor3 = sheet.query("班级 = '一班' OR 班级 = '三班'");
console.log("\n【字符串条件:班级 = '一班' OR 班级 = '三班'】行数:", cursor3.count());
console.log("结果:", JSON.stringify(cursor3.all(), null, 2));
var ranges3 = cursor3.ranges();
console.log("📌 ranges() 分组范围(共", ranges3.size(), "组):");
for (var j = 0; j < ranges3.size(); j++) {
    var r = ranges3.get(j);
    console.log("  组" + (j+1) + ":", r.address());
}
// 对每个连续组分别设置样式(背景色+边框)
ranges3.style().border("thin").background("#E6F0FA");

// 6. 字符串条件:LIKE 模糊匹配
var cursor4 = sheet.query("姓名 LIKE '张'");
console.log("\n【字符串条件:姓名 LIKE '张'】行数:", cursor4.count());
console.log("结果:", JSON.stringify(cursor4.all(), null, 2));

// 7. 对象条件:分数 >= 80 且班级 = '三班'
var cursor5 = sheet.query({
    分数: { ">=": 80 },
    班级: "三班"
});
console.log("\n【对象条件:分数>=80 且 班级='三班'】行数:", cursor5.count());
console.log("结果:", JSON.stringify(cursor5.all(), null, 2));

// 8. 对象条件简写(默认 =)
var cursor6 = sheet.query({ 姓名: "张三" });
console.log("\n【对象条件简写:姓名='张三'】行数:", cursor6.count());
var single = cursor6.single();
console.log("张三的信息:", JSON.stringify(single, null, 2));

// 9. 对象条件:不等于
var cursor7 = sheet.query({ 班级: { "!=": "一班" } });
console.log("\n【对象条件:班级 != '一班'】行数:", cursor7.count());
console.log("结果:", JSON.stringify(cursor7.all(), null, 2));

// 10. 对象条件:范围(大于且小于)
var cursor8 = sheet.query({ 年龄: { ">": 18, "<": 22 } });
console.log("\n【对象条件:年龄 > 18 且 < 22】行数:", cursor8.count());
console.log("结果:", JSON.stringify(cursor8.all(), null, 2));

// ========== 更新与删除演示 ==========
var affected = sheet.query("班级 = '二班'").update({ "分数": 95 });
console.log("\n【更新】更新了", affected, "行");
var deleted = sheet.query("分数 < 70").delete();
console.log("【删除】删除了", deleted, "行");

// 游标遍历(查询年龄>=20)
var cursor9 = sheet.query("年龄 >= 20");
console.log("\n【游标遍历】年龄>=20 的行:");
while (cursor9.moveToNext()) {
    var row = cursor9.pick();
    console.log("  ", JSON.stringify(row, null, 2));
}
cursor9.close();

excel.save();
excel.close();
js
var excel = $excel.open({ path: "/sdcard/query_noheader.xlsx" });
var sheet = excel.sheet("数据");
if (!sheet) sheet = excel.createSheet("数据");

// 1. 切换为无表头模式,插入数据(键名使用列字母 A, B, C...)
sheet.noHeader();
var insertRange1 = sheet.insert({ "A": "姓名", "B": "年龄", "C": "分数", "D": "班级" });
var insertRange2 = sheet.insert([
    { "A": "张三", "B": 18, "C": 90, "D": "一班" },
    { "A": "李四", "B": 19, "C": 85, "D": "二班" },
    { "A": "王五", "B": 20, "C": 78, "D": "一班" },
    { "A": "赵六", "B": 21, "C": 92, "D": "三班" },
    { "A": "孙七", "B": 19, "C": 66, "D": "二班" },
    { "A": "周八", "B": 22, "C": 88, "D": "三班" }
]);
if (insertRange2) {
    console.log("✅ 插入数据区域:", insertRange2.address());
    insertRange2.style().border("thin").background("#F2F2F2");
}

// ========== 各种查询示例(列名使用字母) ==========

// 2. 无条件查询(展示 range() 与 ranges() 的区别)
var cursor0 = sheet.query();
console.log("\n【无条件查询】总行数:", cursor0.count());
console.log("所有数据:", JSON.stringify(cursor0.all(), null, 2));
var range0 = cursor0.range();
console.log("📌 range() 连续范围:", range0.address(), "行数:", range0.height(), "列数:", range0.width());
var ranges0 = cursor0.ranges();
console.log("📌 ranges() 分组范围(共", ranges0.size(), "组):");
for (var i = 0; i < ranges0.size(); i++) {
    var r = ranges0.get(i);
    console.log("  组" + (i+1) + ":", r.address(), "行数:", r.height(), "列数:", r.width());
}

// 3. 字符串条件:B > 20
var cursor1 = sheet.query("B > 20");
console.log("\n【字符串条件:B > 20】行数:", cursor1.count());
console.log("结果:", JSON.stringify(cursor1.all(), null, 2));

// 4. 字符串条件:AND 组合
var cursor2 = sheet.query("B > 20 AND D = '三班'");
console.log("\n【字符串条件:B > 20 AND D = '三班'】行数:", cursor2.count());
console.log("结果:", JSON.stringify(cursor2.all(), null, 2));

// 5. 字符串条件:OR 组合(演示 ranges().style() 对不连续区域应用样式)
var cursor3 = sheet.query("D = '一班' OR D = '三班'");
console.log("\n【字符串条件:D = '一班' OR D = '三班'】行数:", cursor3.count());
console.log("结果:", JSON.stringify(cursor3.all(), null, 2));
var ranges3 = cursor3.ranges();
console.log("📌 ranges() 分组范围(共", ranges3.size(), "组):");
for (var j = 0; j < ranges3.size(); j++) {
    var r = ranges3.get(j);
    console.log("  组" + (j+1) + ":", r.address());
}
ranges3.style().border("thin").background("#E6F0FA");

// 6. LIKE 模糊匹配
var cursor4 = sheet.query("A LIKE '张'");
console.log("\n【字符串条件:A LIKE '张'】行数:", cursor4.count());
console.log("结果:", JSON.stringify(cursor4.all(), null, 2));

// 7. 对象条件:C>=80 且 D='三班'
var cursor5 = sheet.query({
    C: { ">=": 80 },
    D: "三班"
});
console.log("\n【对象条件:C>=80 且 D='三班'】行数:", cursor5.count());
console.log("结果:", JSON.stringify(cursor5.all(), null, 2));

// 8. 对象条件简写
var cursor6 = sheet.query({ A: "张三" });
console.log("\n【对象条件简写:A='张三'】行数:", cursor6.count());
var single = cursor6.single();
console.log("张三的信息:", JSON.stringify(single, null, 2));

// 9. 对象条件:不等于
var cursor7 = sheet.query({ D: { "!=": "一班" } });
console.log("\n【对象条件:D != '一班'】行数:", cursor7.count());
console.log("结果:", JSON.stringify(cursor7.all(), null, 2));

// 10. 对象条件:范围
var cursor8 = sheet.query({ B: { ">": 18, "<": 22 } });
console.log("\n【对象条件:B > 18 且 B < 22】行数:", cursor8.count());
console.log("结果:", JSON.stringify(cursor8.all(), null, 2));

// ========== 更新与删除 ==========
var affected = sheet.query("D = '二班'").update({ C: 95 });
console.log("\n【更新】更新了", affected, "行");
var deleted = sheet.query("C < 70").delete();
console.log("【删除】删除了", deleted, "行");

excel.save();
excel.close();

ExcelSheet.mergedRegions()

获取所有合并区域。

js
// 1. 创建一些合并区域
sheet.range("A1:B2").merge();
sheet.range("D4:E5").merge();

// 2. 获取并打印所有合并区域
var merged = sheet.mergedRegions();
console.log("合并区域数:", merged.size());
for (var i = 0; i < merged.size(); i++) {
    var range = merged.get(i);
    console.log("  区域" + (i+1) + ":", range.formatAsString());
}

ExcelSheet.validations()

获取所有数据验证。

js
var validations = sheet.validations();
console.log("数据验证数:", validations.size());

ExcelSheet.setColumnWidth(column, charWidth)

  • column {number | string} 列索引(从 0 开始)或列字母(如 "A""B"
  • charWidth {number} 字符宽度
  • 返回 {ExcelSheet}

设置列宽。支持通过列索引(数字)或列字母(字符串)指定列。

js
// 通过列索引设置
sheet.setColumnWidth(0, 15);

// 通过列字母设置
sheet.setColumnWidth("A", 15);

ExcelSheet.setColumnWidth(columns, charWidth)

  • columns {Array} 列标识数组,元素可以是数字索引或列字母(如 ["A","C","E"][0,2,4]
  • charWidth {number} 字符宽度
  • 返回 {ExcelSheet}

为多个指定列统一设置相同的宽度。

js
sheet.setColumnWidth(["A", "C", "E"], 15);
sheet.setColumnWidth([0, 2, 4], 15);

ExcelSheet.columnWidth(column)

  • column {number | string} 列索引或列字母
  • 返回 {number} 列宽(字符宽度)

获取指定列的宽度。

js
var w1 = sheet.columnWidth(0);
var w2 = sheet.columnWidth("A");
console.log("A列宽度:", w1);

ExcelSheet.columnWidthExact(column)

  • column {number | string} 列索引或列字母
  • 返回 {number} 列宽(1/256 字符宽度单位,POI 内部单位)

获取指定列的精确宽度(以 1/256 字符为单位)。

js
var exact = sheet.columnWidthExact("A");

ExcelSheet.setColumnHidden(column, hidden)

  • column {number | string} 列索引或列字母
  • hidden {boolean} true 隐藏,false 显示
  • 返回 {ExcelSheet}

设置列的隐藏状态。

js
sheet.setColumnHidden(2, true);   // 隐藏 C 列
sheet.setColumnHidden("C", true); // 同上

ExcelSheet.columnHidden(column)

  • column {number | string} 列索引或列字母
  • 返回 {boolean} true 表示隐藏,false 表示显示

判断指定列是否隐藏。

js
if (sheet.columnHidden(2)) {
    console.log("C 列已隐藏");
}

ExcelSheet.columns()

  • 返回 {Array<string>}

获取列名列表。

js
var colNames = sheet.columns();
console.log("列名:", colNames);

ExcelSheet.clear()

-->

清空整个 Sheet(删除所有内容、样式、合并区域、图片等)。

js
sheet.clear();

ExcelSheet.clearContents()

-->

仅清空内容(保留样式)。

js
sheet.clearContents();

ExcelSheet.clearStyles()

-->

仅清空样式(保留内容)。

js
sheet.clearStyles();

ExcelSheet.clearMerges()

-->

清空所有合并区域。

js
sheet.clearMerges();

ExcelSheet.clearComments()

-->

清空所有批注。

js
sheet.clearComments();

ExcelSheet.clearImages()

-->

清空所有图片。

js
sheet.clearImages();

ExcelSheet.poiSheet()

获取底层 POI Sheet 对象,用于执行 POI 原生操作(如高级格式设置、公式、图形等)。

js
var poiSheet = sheet.poiSheet();
// 例如:获取 Sheet 的保护状态
var isProtected = poiSheet.getProtect();

ExcelCursor

ExcelSheet.query([where]) 返回,代表查询结果集。

完整测试示例
js
// ==================== ExcelCursor 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelCursorTest/

var TEST_DIR = "/sdcard/ExcelCursorTest/";
files.ensureDir(TEST_DIR);

// 打开(或创建)工作簿,并获取 Sheet
var excel = $excel.open({ path: TEST_DIR + "cursor_test.xlsx" });
var sheetName = "Cursor测试";
if (excel.hasSheet(sheetName)) {
    excel.removeSheet(sheetName);
}
var sheet = excel.createSheet(sheetName);

console.log("\n📦 准备测试数据...");

// 1. 创建表头并插入数据(有表头模式)
sheet.createHeader(["姓名", "年龄", "分数", "班级"]);
sheet.insert([
    { "姓名": "张三", "年龄": 18, "分数": 90, "班级": "一班" },
    { "姓名": "李四", "年龄": 19, "分数": 85, "班级": "二班" },
    { "姓名": "王五", "年龄": 20, "分数": 78, "班级": "一班" },
    { "姓名": "赵六", "年龄": 21, "分数": 92, "班级": "三班" },
    { "姓名": "孙七", "年龄": 19, "分数": 66, "班级": "二班" },
    { "姓名": "周八", "年龄": 22, "分数": 88, "班级": "三班" }
]);
excel.save({ path: TEST_DIR + "cursor_base.xlsx" });
console.log("基础数据已保存 -> cursor_base.xlsx");

// 辅助函数:打印分隔线
function sep(title) {
    console.log("\n========== " + title + " ==========");
}

// ==================== 1. 无条件查询 ====================
sep("1. 无条件查询 (query())");
var cursor = sheet.query();
console.log("count():", cursor.count());
console.log("empty():", cursor.empty());
console.log("nonEmpty():", cursor.nonEmpty());
console.log("all():", JSON.stringify(cursor.all(), null, 2));

// 测试 single()
var singleRow = cursor.single();
console.log("single():", JSON.stringify(singleRow, null, 2));

// 测试 rows() / rowIndexes() / columns()
var rows = cursor.rows();
console.log("rows().size():", rows.size());
var indexes = cursor.rowIndexes();
console.log("rowIndexes():", JSON.stringify(indexes));
var cols = cursor.columns();
console.log("columns():", JSON.stringify(cols));

// 测试 range() 和 ranges()
var rangeAll = cursor.range();
console.log("range().address():", rangeAll.address());
var ranges = cursor.ranges();
console.log("ranges().size():", ranges.size());

// ==================== 2. 条件查询(字符串) ====================
sep("2. 条件查询 (字符串)");
var cursor2 = sheet.query("年龄 > 20");
console.log("年龄 > 20 的行数:", cursor2.count());
console.log("结果:", JSON.stringify(cursor2.all(), null, 2));

// 测试游标遍历
console.log("遍历结果:");
while (cursor2.moveToNext()) {
    var row = cursor2.pick();
    console.log("  ", JSON.stringify(row));
    // 测试 row() / cells()
    var excelRow = cursor2.row();
    console.log("    ExcelRow.index():", excelRow.index());
    var cells = cursor2.cells();
    console.log("    cells().size():", cells.size());
}
cursor2.reset();
console.log("reset() 后 position():", cursor2.position());

// ==================== 3. 条件查询(对象) ====================
sep("3. 条件查询 (对象)");
var cursor3 = sheet.query({
    分数: { ">=": 80 },
    班级: "三班"
});
console.log("分数>=80 且 班级='三班' 的行数:", cursor3.count());
console.log("结果:", JSON.stringify(cursor3.all(), null, 2));

// ==================== 4. 更新操作 ====================
sep("4. 更新操作 (update)");
var cursor4 = sheet.query("班级 = '二班'");
var updated = cursor4.update({ "班级": "火箭班" });
console.log("更新了", updated, "行");
// 验证更新结果
var cursor4_check = sheet.query("班级 = '火箭班'");
console.log("更新后火箭班人数:", cursor4_check.count());

// ==================== 5. 删除操作 ====================
sep("5. 删除操作 (delete)");
var cursor5 = sheet.query("分数 < 70");
var deleted = cursor5.delete(); // 不压缩
console.log("删除了", deleted, "行(不压缩空行)");
// 验证
var cursor5_check = sheet.query("分数 < 70");
console.log("剩余分数<70的行数:", cursor5_check.count());

// 删除后插入一条新数据,然后测试带压缩的删除
sheet.insert({ "姓名": "临时", "年龄": 30, "分数": 50, "班级": "测试" });
var cursor6 = sheet.query("姓名 = '临时'");
var deleted2 = cursor6.delete(true); // 压缩空行
console.log("删除了", deleted2, "行(压缩空行)");

// ==================== 6. 样式操作 ====================
sep("6. 样式操作 (style)");
var cursor7 = sheet.query("班级 = '一班'");
cursor7.style()
    .bold(true)
    .background("#FFFF00")
    .fontColor("#FF0000");
console.log("一班数据已应用样式(请打开文件查看)");

// ==================== 7. 空结果测试 ====================
sep("7. 空结果测试");
var cursor8 = sheet.query("姓名 = '不存在'");
console.log("empty():", cursor8.empty());
console.log("nonEmpty():", cursor8.nonEmpty());
console.log("count():", cursor8.count());
console.log("single():", cursor8.single());
console.log("range().address():", cursor8.range().address()); // 应返回空范围 A0:A0
console.log("ranges().size():", cursor8.ranges().size());

// ==================== 8. position() 测试 ====================
sep("8. position() 测试");
var cursor9 = sheet.query("年龄 >= 19");
console.log("初始 position():", cursor9.position());
var i = 0;
while (cursor9.moveToNext()) {
    console.log("第", i, "次 moveToNext 后 position():", cursor9.position());
    i++;
}
cursor9.reset();
console.log("reset() 后 position():", cursor9.position());
cursor9.close();
console.log("close() 后 position():", cursor9.position());

// ==================== 9. 无表头模式测试 ====================
sep("9. 无表头模式测试");
// 新建一个 Sheet 并切换到无表头模式
var sheetNoHeader = excel.createSheet("无表头测试");
sheetNoHeader.noHeader();
// 插入数据(键名为列字母)
sheetNoHeader.insert({ "A": "姓名", "B": "年龄", "C": "分数", "D": "班级" });
sheetNoHeader.insert([
    { "A": "张三", "B": 18, "C": 90, "D": "一班" },
    { "A": "李四", "B": 19, "C": 85, "D": "二班" }
]);
var cursorNo = sheetNoHeader.query("B > 18");
console.log("无表头查询结果:", JSON.stringify(cursorNo.all(), null, 2));
console.log("columns():", JSON.stringify(cursorNo.columns()));

// ==================== 10. 综合测试:rows() 和 range() 结合 ====================
sep("10. rows() 与 range() 结合");
var cursor10 = sheet.query("年龄 >= 20");
var rowColl = cursor10.rows();
console.log("rows().size():", rowColl.size());

rowColl.forEach(function(row) {
    console.log("行索引:", row.index());
});
var range10 = cursor10.range();
console.log("range().address():", range10.address());

// 保存最终结果
excel.save({ path: TEST_DIR + "cursor_final.xlsx" });
console.log("\n✅ 所有测试完成!");
console.log("📁 生成的文件位于:", TEST_DIR);
console.log("   基础文件: cursor_base.xlsx");
console.log("   最终文件: cursor_final.xlsx");
excel.close();

ExcelCursor.moveToNext()

  • 返回 {boolean}

移动到下一条记录。

js
while (cursor.moveToNext()) {
    var row = cursor.pick();
    console.log(row);
}

ExcelCursor.pick()

  • 返回 {Object | null}

获取当前行的 Object。

js
var row = cursor.pick();

ExcelCursor.row()

获取当前行的 ExcelRow 对象。

js
var row = cursor.row();

ExcelCursor.cells()

获取当前行的单元格集合。

js
var cells = cursor.cells();

ExcelCursor.all()

  • 返回 {Array}

获取所有数据。

js
var all = cursor.all();

ExcelCursor.single()

  • 返回 {Object | null}

获取第一条数据。

js
var first = cursor.single();

ExcelCursor.count()

  • 返回 {number}

获取行数。

js
console.log("行数:", cursor.count());

ExcelCursor.empty()

  • 返回 {boolean}

判断是否为空。

js
if (cursor.empty()) {
    console.log("没有匹配的数据");
}

ExcelCursor.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (cursor.nonEmpty()) {
    console.log("有匹配的数据");
}

ExcelCursor.update(values)

  • values {Object} 要更新的数据
  • 返回 {number} 更新的行数

更新匹配的行。

js
var updated = cursor.update({
    状态: "已处理",
    处理时间: new Date()
});
console.log("更新了", updated, "行");

ExcelCursor.delete([compress])

  • compress {boolean} 可选,是否压缩空行(删除后上移后续行),默认 false
  • 返回 {number} 删除的行数

删除所有匹配行。若 compresstrue,删除后会将后续行上移填补空位(注意:可能影响公式、合并区域等,请谨慎使用)。

js
// 不压缩空行(默认)
var deleted = cursor.delete();
console.log("删除了", deleted, "行");

// 删除并压缩空行
var deleted = cursor.delete(true);
console.log("删除了", deleted, "行");

ExcelCursor.ranges()

将匹配行按连续块分组,每个分组内不含非匹配行,返回 ExcelRangeCollection(集合中的每个 ExcelRange 对应一个连续块)。

js
var ranges = cursor.ranges();
for (var i = 0; i < ranges.size(); i++) {
    var range = ranges.get(i);
    console.log("块" + (i+1) + ":", range.address());
}

ExcelCursor.range()

返回一个连续的矩形范围,从第一个匹配行的起始列到最后一个匹配行的结束列,中间可能包含不符合查询条件的行

js
var range = cursor.range();
console.log("连续范围:", range.address());

ExcelCursor.rows()

获取匹配的行集合。

js
var rows = cursor.rows();

ExcelCursor.rowIndexes()

  • 返回 {Array}

获取行索引数组。

js
var indexes = cursor.rowIndexes();

ExcelCursor.columns()

  • 返回 {Array}

获取列名数组。

js
var columns = cursor.columns();

ExcelCursor.style()

为匹配行设置样式。

js
cursor.style()
    .bold(true)
    .background("#FFFF00");

ExcelCursor.position()

  • 返回 {number}

获取当前位置。

js
console.log("位置:", cursor.position());

ExcelCursor.close()

关闭游标。

js
cursor.close();

ExcelRange

ExcelSheet.range(address)ExcelSheet.range(startRow, startCol, endRow, endCol) 等方法返回,代表一个单元格区域。

完整测试示例
js
// ==================== ExcelRange 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelRangeTest/

var TEST_DIR = "/sdcard/ExcelRangeTest/";
files.ensureDir(TEST_DIR);

// 打开(或创建)工作簿,并获取 Sheet
var excel = $excel.open({ path: TEST_DIR + "range_test_base.xlsx" });
var sheet = excel.sheet("Range测试");
if (!sheet) sheet = excel.createSheet("Range测试");

// 清空可能存在的旧数据(保留 Sheet)
// sheet.clear();

// ==================== 准备基础数据 ====================
console.log("\n📦 准备基础数据...");
sheet.range("A1:E10").setValue([
    ["姓名", "语文", "数学", "英语", "总分"],
    ["张三", 85, 90, 88, 263],
    ["李四", 78, 82, 80, 240],
    ["王五", 92, 85, 90, 267],
    ["赵六", 70, 75, 72, 217],
    ["孙七", 88, 92, 85, 265],
    ["周八", 95, 88, 93, 276],
    ["吴九", 65, 70, 68, 203],
    ["郑十", 80, 85, 82, 247],
    ["合计", "=SUM(B2:B9)", "=SUM(C2:C9)", "=SUM(D2:D9)", "=SUM(E2:E9)"]
]);
excel.save({ path: TEST_DIR + "00_base.xlsx" });
console.log("基础数据已保存 -> 00_base.xlsx");

// ==================== 1. 基础属性 ====================
console.log("\n🔍 1. 基础属性测试");
var r1 = sheet.range("B2:D4");
console.log("  address()        :", r1.address());
console.log("  height()         :", r1.height());
console.log("  width()          :", r1.width());
console.log("  firstRow()       :", r1.firstRow());
console.log("  lastRow()        :", r1.lastRow());
console.log("  firstColumn()    :", r1.firstColumn());
console.log("  lastColumn()     :", r1.lastColumn());
console.log("  cellCount()      :", r1.cellCount());
console.log("  empty()          :", r1.empty());

// ==================== 2. 读写数据 ====================
console.log("\n✏️ 2. 读写数据测试");
var r2 = sheet.range("A12:C14");
r2.setValue([
    ["产品", "单价", "数量"],
    ["苹果", 5.5, 10],
    ["香蕉", 3.2, 15]
]);
console.log("  setValue(二维数组) 后,value() 返回:");
console.log("  " + JSON.stringify(r2.value()));
var r3 = sheet.range("E12");
r3.setValue("备注");
console.log("  setValue(单值) 后,value() =", r3.value());

// ==================== 3. 单元格访问 ====================
console.log("\n📍 3. 单元格访问测试");
var r4 = sheet.range("A15:D17");
r4.setValue([
    ["A", "B", "C", "D"],
    ["E", "F", "G", "H"],
    ["I", "J", "K", "L"]
]);
console.log("  firstCell()      :", r4.firstCell().value());
console.log("  lastCell()       :", r4.lastCell().value());
console.log("  cell(1,2)        :", r4.cell(1, 2).value());

// ==================== 4. 包含关系判断 ====================
console.log("\n🔎 4. 包含关系测试");
var r5 = sheet.range("B2:E9");
console.log("  range: " + r5.address());
console.log("  contains(2,2)    :", r5.contains(2, 2));
console.log("  contains('C3')   :", r5.contains("C3"));
var cell = sheet.cell("D4");
console.log("  contains(cell)   :", r5.contains(cell));
var sub = sheet.range("C3:D4");
console.log("  contains(sub)    :", r5.contains(sub));
var out = sheet.range("F1:G2");
console.log("  contains(out)    :", r5.contains(out));

// ==================== 5. 复制与粘贴 ====================
console.log("\n📋 5. 复制与粘贴测试");
var src = sheet.range("A1:E1");
var tgt = sheet.range("G1:K1");
console.log("  源地址:", src.address(), "目标地址:", tgt.address());
src.copy(tgt);
console.log("  copy() 后目标值:", JSON.stringify(tgt.value()));

var tgtVal = sheet.range("G3:K3");
src.paste(tgtVal);
console.log("  paste() 后目标值:", JSON.stringify(tgtVal.value()));

src.style().bold(true).background("#FFCC00");
src.pasteStyle(tgt);
console.log("  pasteStyle() 后样式已应用(请打开文件查看)");

// ==================== 6. 范围调整 ====================
console.log("\n🔄 6. 偏移与调整大小测试");
var r6 = sheet.range("A20:B21");
console.log("  原始地址:", r6.address());
var offset = r6.offset(2, 3);
console.log("  offset(2,3)  :", offset.address());
var resize = r6.resize(4, 5);
console.log("  resize(4,5)  :", resize.address());

// ==================== 7. 清空操作 ====================
console.log("\n🧹 7. 清空测试");
var r7a = sheet.range("A23:C25");
r7a.setValue([
    ["a", "b", "c"],
    ["d", "e", "f"],
    ["g", "h", "i"]
]);
console.log("  写入数据:", JSON.stringify(r7a.value()));
r7a.clearContents();
console.log("  clearContents() 后:", JSON.stringify(r7a.value()));

var r7b = sheet.range("E23:G25");
r7b.setValue([
    ["x", "y", "z"],
    ["1", "2", "3"],
    ["4", "5", "6"]
]);
r7b.style().bold(true).background("#FF0000").fontColor("#FFFFFF");
r7b.clearStyles();
console.log("  clearStyles() 后样式已重置(请打开文件查看)");

// ==================== 8. 合并操作 ====================
console.log("\n🧩 8. 合并测试");
var r8a = sheet.range("A28:B29");
console.log("  即将合并:", r8a.address());
var idx = r8a.merge();
console.log("  merge() 返回索引:", idx);
console.log("  isMerged():", r8a.isMerged());

var r8b = sheet.range("D28:E29");
r8b.merge();
console.log("  第二个合并区域:", r8b.address());

var r8c = sheet.range("C28:D28");
console.log("  检查重叠:", r8c.address(), "hasMergedOverlap() =", r8c.hasMergedOverlap());
if (r8c.hasMergedOverlap()) {
    r8c.clearMergedOverlap();
    console.log("  已清除重叠");
}

r8a.unmerge();
console.log("  unmerge() 后 isMerged():", r8a.isMerged());

// ==================== 9. 排序 ====================
console.log("\n📊 9. 排序测试");
var r9 = sheet.range("A30:E38");
r9.setValue([
    ["姓名", "班级", "分数", "排名", "备注"],
    ["张三", "一班", 85, "", ""],
    ["李四", "二班", 92, "", ""],
    ["王五", "一班", 78, "", ""],
    ["赵六", "三班", 95, "", ""],
    ["孙七", "二班", 88, "", ""],
    ["周八", "一班", 70, "", ""],
    ["吴九", "三班", 82, "", ""],
    ["郑十", "二班", 90, "", ""]
]);
console.log("  排序前数据:", JSON.stringify(r9.value()));
r9.sort(2, false);
console.log("  按分数降序后数据:", JSON.stringify(r9.value()));
r9.sort([1, 2], [true, false]);
console.log("  按班级升序+分数降序后数据:", JSON.stringify(r9.value()));

// ==================== 10. 集合操作 ====================
console.log("\n🔗 10. 交集与并集测试");
var r10a = sheet.range("A40:C45");
var r10b = sheet.range("B41:D42");
console.log("  范围1:", r10a.address());
console.log("  范围2:", r10b.address());
var inter = r10a.intersect(r10b);
console.log("  intersect():", inter ? inter.address() : "无交集");
var uni = r10a.union(r10b);
console.log("  union():", uni ? uni.address() : "不相邻,无法并集");

// ==================== 11. 样式 ====================
console.log("\n🎨 11. 样式测试");
var r11 = sheet.range("A47:C49");
r11.setValue([
    ["样式", "演示", "区域"],
    ["加粗", "斜体", "下划线"],
    ["颜色", "背景", "边框"]
]);
r11.style()
    .bold(true)
    .italic(true)
    .underline(true)
    .fontColor("#FF0000")
    .background("#FFFF00")
    .border("thin")
    .align("center")
    .vertical("middle");
console.log("  样式已应用(请打开文件查看效果)");

// ==================== 12. 批注 ====================
console.log("\n💬 12. 批注测试");
var r12 = sheet.range("A51:B52");
r12.setValue([
    ["批注1", "批注2"],
    ["注释", "说明"]
]);
r12.setComment("这是整个范围的批注", "测试员");
console.log("  setComment() 后,comments().size() =", r12.comments().size());
var cmt = r12.comment(0, 0);
if (cmt) {
    console.log("  comment(0,0) 内容:", cmt.text(), "作者:", cmt.author());
}
r12.removeComment();
console.log("  removeComment() 后,comments().size() =", r12.comments().size());

// ==================== 13. 超链接 ====================
console.log("\n🔗 13. 超链接测试");
var r13 = sheet.range("A54:C55");
r13.setValue([
    ["链接1", "链接2", "链接3"],
    ["邮件", "文件", "网址"]
]);
r13.setHyperlink("https://example.com");
r13.setEmailHyperlink("user@example.com");
r13.setFileHyperlink("/sdcard/test.txt");
console.log("  setHyperlink() 后 hyperlinks().size() =", r13.hyperlinks().size());
var links = r13.hyperlinks();
links.forEach(function(h) {
    console.log("链接: 地址=" + h.address() + ", 标签=" + h.label() + ", 类型=" + h.type());
});
r13.removeHyperlink();
console.log("  removeHyperlink() 后 hyperlinks().size() =", r13.hyperlinks().size());

// ==================== 14. 底层对象 ====================
console.log("\n⚙️ 14. 底层对象测试");
var r14 = sheet.range("A57:C58");
console.log("  poiSheet().getSheetName() =", r14.poiSheet().getSheetName());
var addr = r14.poiCellRangeAddress();
console.log("  poiCellRangeAddress().formatAsString() =", addr.formatAsString());
console.log("  poiCellRangeAddress().getFirstRow() =", addr.getFirstRow());

// ==================== 15. 额外测试 ====================
console.log("\n📌 15. 单格测试");
var r15 = sheet.range("A60");
r15.setValue("单格测试");
console.log("  单格 cellCount =", r15.cellCount());
console.log("  单格 empty =", r15.empty());
console.log("  单格 value =", r15.value());

// ==================== 完成 ====================
excel.save({ path: TEST_DIR + "99_final.xlsx" });
console.log("\n✅ 所有测试完成!");
console.log("📁 生成的文件位于:", TEST_DIR);
console.log("   基础文件: 00_base.xlsx");
console.log("   最终文件: 99_final.xlsx(包含所有修改)");
excel.close();

ExcelRange.height()

  • 返回 {number}

获取高度(行数)。

js
console.log("高度:", range.height());

ExcelRange.width()

  • 返回 {number}

获取宽度(列数)。

js
console.log("宽度:", range.width());

ExcelRange.firstRow()

  • 返回 {number}

获取首行索引。

js
console.log("首行:", range.firstRow());

ExcelRange.lastRow()

  • 返回 {number}

获取末行索引。

js
console.log("末行:", range.lastRow());

ExcelRange.firstColumn()

  • 返回 {number}

获取首列索引。

js
console.log("首列:", range.firstColumn());

ExcelRange.lastColumn()

  • 返回 {number}

获取末列索引。

js
console.log("末列:", range.lastColumn());

ExcelRange.cellCount()

  • 返回 {number}

获取单元格总数。

js
console.log("单元格数:", range.cellCount());

ExcelRange.empty()

  • 返回 {boolean}

判断是否为空(所有单元格均为空)。

js
if (range.empty()) {
    console.log("该范围为空");
}

ExcelRange.address()

  • 返回 {string}

获取范围地址。

js
console.log("地址:", range.address());

ExcelRange.setValue(value)

  • value {Object | Array} 要设置的值
  • 返回 {ExcelRange}

设置值。

js
// 所有单元格设为 100
range.setValue(100);

// 设置二维数组
range.setValue([
    ["A", "B", "C"],
    [1, 2, 3],
    [4, 5, 6]
]);

ExcelRange.value()

  • 返回 {Object | Array}

获取值(单格返回值,多格返回二维数组)。

js
var val = range.value();

ExcelRange.contains(row, col)

  • row {number} 行索引
  • col {number} 列索引
  • 返回 {boolean}

判断坐标是否在范围内。

js
console.log(range.contains(2, 2));

ExcelRange.contains(address)

  • address {string} 单元格地址
  • 返回 {boolean}

判断地址是否在范围内。

js
console.log(range.contains("D2"));

ExcelRange.contains(cell)

  • cell {ExcelCell} 单元格对象
  • 返回 {boolean}

判断单元格是否在范围内。

js
var cell = sheet.cell(2, 2);
console.log(range.contains(cell));

ExcelRange.contains(range)

判断另一个范围是否在范围内。

js
var subRange = sheet.range("B2:C3");
console.log(range.contains(subRange));

ExcelRange.firstCell()

获取第一个单元格。

js
var first = range.firstCell();

ExcelRange.lastCell()

获取最后一个单元格。

js
var last = range.lastCell();

ExcelRange.cell(rowOffset, colOffset)

  • rowOffset {number} 行偏移
  • colOffset {number} 列偏移
  • 返回 ExcelCell

通过偏移获取单元格(相对于范围左上角)。

js
var cell = range.cell(1, 0);  // 第2行,第1列

ExcelRange.copy(target)

完整复制(值 + 样式)。

js
var source = sheet.range("A1:C10");
var target = sheet.range("E1:G10");
source.copy(target);

ExcelRange.copy(targetAddress)

  • targetAddress {string} 目标地址
  • 返回 {ExcelRange}

复制到地址字符串。

js
source.copy("E1:G10");

ExcelRange.paste(target)

仅粘贴值到范围。

js
source.paste(target);

ExcelRange.paste(targetAddress)

  • targetAddress {string} 目标地址
  • 返回 {ExcelRange}

粘贴到地址字符串。

js
source.paste("E1:G10");

ExcelRange.pasteStyle(target)

仅粘贴样式。

js
source.pasteStyle(target);

ExcelRange.pasteStyle(targetAddress)

  • targetAddress {string} 目标地址
  • 返回 {ExcelRange}

粘贴样式到地址字符串。

js
source.pasteStyle("E1:G10");

ExcelRange.offset(rowOffset, colOffset)

  • rowOffset {number} 行偏移
  • colOffset {number} 列偏移
  • 返回 {ExcelRange}

偏移范围。

js
var newRange = range.offset(2, 1);

ExcelRange.resize(newRows, newCols)

  • newRows {number} 新行数
  • newCols {number} 新列数
  • 返回 {ExcelRange}

调整范围大小。

js
var resized = range.resize(3, 2);

ExcelRange.merge()

  • 返回 {number} 合并区域的索引
  • 异常 {IllegalStateException} 如果存在重叠合并区域

合并单元格。

js
range.merge();

ExcelRange.unmerge()

取消合并。

js
range.unmerge();

ExcelRange.hasMergedOverlap()

  • 返回 {boolean}

检查是否有重叠的合并区域。

js
if (range.hasMergedOverlap()) {
    range.clearMergedOverlap();
}

ExcelRange.clearMergedOverlap()

清除重叠的合并区域。

js
range.clearMergedOverlap();

ExcelRange.isMerged()

  • 返回 {boolean}

判断是否为合并区域。

js
if (range.isMerged()) {
    console.log("该范围是合并区域");
}

ExcelRange.sort(column, ascending)

  • column {number | string} 排序列索引(从 0 开始)或列字母(如 "B"
  • ascending {boolean} 是否升序
  • 返回 {ExcelRange}

单列排序(默认有标题行)。

js
// 按列索引排序
range.sort(1, true);   // 升序
range.sort(1, false);  // 降序

// 按列字母排序
range.sort("B", true);

ExcelRange.sort(column, ascending, hasHeader)

  • column {number} 排序列索引
  • ascending {boolean} 是否升序
  • hasHeader {boolean} 是否有标题行
  • 返回 {ExcelRange}

单列排序(指定标题行)。

js
range.sort(1, true, false);  // 无标题行

ExcelRange.sort(columns, ascending)

  • columns {number[]} 排序列索引数组
  • ascending {boolean[]} 升降序数组
  • 返回 {ExcelRange}

多列排序。

js
range.sort([1, 2], [true, false]);

ExcelRange.sort(columns, ascending, hasHeader)

  • columns {number[]} 排序列索引数组
  • ascending {boolean[]} 升降序数组
  • hasHeader {boolean} 是否有标题行
  • 返回 {ExcelRange}

多列排序(指定标题行)。

js
range.sort([1, 2], [true, false], true);

ExcelRange.intersect(other)

获取交集。

js
var range1 = sheet.range("A1:C10");
var range2 = sheet.range("B2:D5");
var intersect = range1.intersect(range2);

ExcelRange.union(other)

获取并集(需要相邻)。

js
var union = range1.union(range2);

ExcelRange.style()

获取样式构建器。

js
range.style()
    .bold(true)
    .fontColor("#FFFFFF")
    .background("#4472C4");

ExcelRange.setComment(text[, author])

  • text {string} 批注内容
  • author {string} 可选,作者名称
  • 返回 {ExcelRange}

添加批注。若指定 author,则同时设置作者信息。

注意

如果目标单元格已有批注,再次调用此方法会抛出 IllegalStateException(POI 不允许一个单元格有多个批注)。建议在设置前先调用 removeComment() 移除旧批注,或使用 comments() 检查是否存在。

js
// 安全做法:先移除旧批注,再设置新批注
range.removeComment();
console.log("已移除旧批注");
range.setComment("这是批注内容", "作者名");
console.log("已设置新批注,作者:", "作者名");

// 或者检查后再设置
if (range.comments().size() > 0) {
    range.removeComment();
    console.log("检测到已有批注,已移除");
}
range.setComment("这是批注内容");
console.log("已设置批注内容");

ExcelRange.removeComment()

删除批注。

js
range.removeComment();

ExcelRange.comments()

获取批注集合。

js
var comments = range.comments();

ExcelRange.comment(rowOffset, colOffset)

  • rowOffset {number} 行偏移
  • colOffset {number} 列偏移
  • 返回 {ExcelComment} | null

获取指定位置的批注。

js
var comment = range.comment(0, 0);

添加超链接。

js
range.setHyperlink("https://example.com");

添加邮箱超链接。

js
range.setEmailHyperlink("user@example.com");

添加文件超链接。

js
range.setFileHyperlink("/sdcard/file.pdf");

删除超链接。

js
range.removeHyperlink();

获取超链接集合。

js
var links = range.hyperlinks();
  • 返回 {boolean}

检查是否有超链接。

js
if (range.hasAnyHyperlink()) {
    console.log("存在超链接");
}

ExcelRange.clear()

清空所有(内容和样式)。

js
range.clear();

ExcelRange.clearContents()

仅清空内容。

js
range.clearContents();

ExcelRange.clearStyles()

仅清空样式。

js
range.clearStyles();

ExcelRange.poiSheet()

获取底层 POI Sheet 对象,用于执行 POI 原生工作表操作(如行/列操作、合并区域等)。

js
var poiSheet = range.poiSheet();

ExcelRange.poiCellRangeAddress()

获取底层 POI CellRangeAddress 对象,用于执行 POI 原生范围操作。

js
var address = range.poiCellRangeAddress();

ExcelRangeCollection

ExcelCursor.ranges() 返回,代表多个不连续范围的集合。

完整测试示例
js
// ==================== ExcelRangeCollection 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelRangeCollectionTest/

var TEST_DIR = "/sdcard/ExcelRangeCollectionTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({ path: TEST_DIR + "range_collection_test.xlsx" });
var sheet = excel.sheet("测试");
if (!sheet) sheet = excel.createSheet("测试");

// 1. 创建表头并插入测试数据(使匹配行不连续)
sheet.createHeader(["姓名", "年龄", "班级"]);
sheet.insert([
    { "姓名": "张三", "年龄": 18, "班级": "一班" },
    { "姓名": "李四", "年龄": 19, "班级": "二班" },
    { "姓名": "王五", "年龄": 20, "班级": "一班" },
    { "姓名": "赵六", "年龄": 21, "班级": "三班" },
    { "姓名": "孙七", "年龄": 22, "班级": "二班" },
    { "姓名": "周八", "年龄": 23, "班级": "三班" }
]);

// 2. 查询条件产生不连续的匹配行(一班和三班)
// 匹配行索引:0, 2, 3, 5 → 连续块:[0], [2-3], [5] 共3块
var cursor = sheet.query("班级 = '一班' OR 班级 = '三班'");
var ranges = cursor.ranges();

console.log("\n========== ExcelRangeCollection 测试 ==========");

// 3. 测试 size()
console.log("size():", ranges.size());   // 预期 3

// 4. 测试 empty() 和 nonEmpty()
console.log("empty():", ranges.empty());       // false
console.log("nonEmpty():", ranges.nonEmpty()); // true

// 5. 测试 cellCount()(每行3列,总行数 = 各块行数之和)
console.log("cellCount():", ranges.cellCount()); // 预期 12

// 6. 测试 get(index)
console.log("\n--- get(index) ---");
console.log("get(0).address():", ranges.get(0).address());
console.log("get(1).address():", ranges.get(1).address());
console.log("get(2).address():", ranges.get(2).address());

// 7. 测试 forEach(callback)
console.log("\n--- forEach 遍历 ---");
ranges.forEach(function(range) {
    console.log("范围:", range.address());
});

// 8. 测试 filter(predicate)
console.log("\n--- filter ---");
var filtered = ranges.filter(function(range) {
    return range.height() > 1;   // 使用 height() 获取行数
});
console.log("过滤后 size:", filtered.size());

// 使用 size() + get() 遍历过滤后的结果
console.log("过滤后范围(使用 size/get 遍历):");
for (var i = 0; i < filtered.size(); i++) {
    var range = filtered.get(i);
    console.log("  范围 " + i + ":", range.address());
}

// 9. 测试 find(predicate)
console.log("\n--- find ---");
var found = ranges.find(function(range) {
    // 查找第一个范围(直接返回 true 即可)
    return true;
});
console.log("找到的范围地址:", found ? found.address() : "未找到");

// 10. 测试 style()
console.log("\n--- style ---");
ranges.style()
    .bold(true)
    .background("#FFFF00")
    .border("thin");
console.log("样式已应用到所有范围(请打开文件查看效果)");

// 保存最终文件
excel.save({ path: TEST_DIR + "range_collection_final.xlsx" });
console.log("\n✅ 测试完成!");
console.log("📁 生成的文件:", TEST_DIR + "range_collection_final.xlsx");
excel.close();

ExcelRangeCollection.size()

  • 返回 {number}

获取范围数量。

js
console.log("范围数:", ranges.size());

ExcelRangeCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (ranges.empty()) {
    console.log("没有范围");
}

ExcelRangeCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (ranges.nonEmpty()) {
    console.log("有范围");
}

ExcelRangeCollection.cellCount()

  • 返回 {number}

获取单元格总数。

js
console.log("单元格总数:", ranges.cellCount());

ExcelRangeCollection.get(index)

获取指定索引的范围。

js
// 使用 size() 和 get() 遍历
for (var i = 0; i < ranges.size(); i++) {
    var r = ranges.get(i);
    console.log(r.address());
}

ExcelRangeCollection.forEach(callback)

遍历所有范围。

js
ranges.forEach(function(range) {
    console.log(range.address());
});

ExcelRangeCollection.filter(predicate)

过滤范围。

js
var largeRanges = ranges.filter(function(range) {
    return range.cellCount() > 10;
});

ExcelRangeCollection.find(predicate)

  • predicate {Function} 过滤函数 (range) => boolean
  • 返回 {ExcelRange} | null

查找第一个匹配的范围。

js
var found = ranges.find(function(range) {
    return range.address() === "A1:B2";
});

ExcelRangeCollection.style()

为所有范围设置样式。

js
ranges.style()
    .bold(true)
    .background("#FFFF00")
    .border("thin");

ExcelRow

ExcelSheet.row(index) 返回,代表一行。

完整测试示例
js
// ==================== ExcelRow 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelRowTest/

var TEST_DIR = "/sdcard/ExcelRowTest/";
files.ensureDir(TEST_DIR);

// 打开(或创建)工作簿,并获取 Sheet
var excel = $excel.open({ path: TEST_DIR + "row_test.xlsx" });
var sheetName = "Row测试";
if (excel.hasSheet(sheetName)) {
    excel.removeSheet(sheetName);
}
var sheet = excel.createSheet(sheetName);

// ==================== 准备基础数据 ====================
console.log("\n📦 准备基础数据...");
sheet.range("A1:E5").setValue([
    ["姓名", "语文", "数学", "英语", "总分"],
    ["张三", 85, 90, 88, 263],
    ["李四", 78, 82, 80, 240],
    ["王五", 92, 85, 90, 267],
    ["赵六", 70, 75, 72, 217]
]);
sheet.range("A7:E7").setValue(["", "", "", "", ""]);
excel.save({ path: TEST_DIR + "row_base.xlsx" });
console.log("基础数据已保存 -> row_base.xlsx");

// ==================== 获取行对象 ====================
var row0 = sheet.row(0);
var row1 = sheet.row(1);
var row2 = sheet.row(2);
var row3 = sheet.row(3);
var row4 = sheet.row(4);
var row7 = sheet.row(7);

console.log("\n========== 1. 基本属性测试 ==========");
console.log("row0.index():", row0.index());
console.log("row0.empty():", row0.empty());
console.log("row0.cellCount():", row0.cellCount());
console.log("row1.cellCount():", row1.cellCount());
console.log("row1.empty():", row1.empty());

// ==================== 2. 单元格操作 ====================
console.log("\n========== 2. 单元格操作测试 ==========");
console.log("row1.cell(0).value():", row1.cell(0).value());
console.log("row1.cell(4).value():", row1.cell(4).value());

var cells = row1.cells();
console.log("row1.cells().size():", cells.size());
console.log("row1.cells() 第一个值:", cells.size() > 0 ? cells.get(0).value() : "无");

var testRow = sheet.row(10);
testRow.setValue(["测试", 100, 200, 300, 400]);
console.log("testRow.value():", JSON.stringify(testRow.value()));

// ==================== 3. 行高操作 ====================
console.log("\n========== 3. 行高操作测试 ==========");
console.log("row1.height():", row1.height());
row1.setHeight(30);
console.log("设置后 row1.height():", row1.height());
row1.autoHeight();
console.log("autoHeight() 后 row1.height():", row1.height());

// ==================== 4. 样式操作(链式调用) ====================
console.log("\n========== 4. 样式操作测试(链式) ==========");
// 给 row1 设置样式:加粗、红色字体、黄色背景、边框、居中
row1.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00")
    .border("thin")
    .align("center")
    .vertical("middle");
console.log("row1 样式已应用(请打开文件查看)");

// ==================== 5. 复制与粘贴 ====================
console.log("\n========== 5. 复制与粘贴测试 ==========");
var targetRow = sheet.row(12);
console.log("复制前 targetRow 值:", JSON.stringify(targetRow.value()));
row1.copy(targetRow);
console.log("copy() 后 targetRow 值:", JSON.stringify(targetRow.value()));

var pasteTarget = sheet.row(14);
pasteTarget.setValue(["旧值", "旧值", "旧值", "旧值", "旧值"]);
console.log("粘贴前 pasteTarget 值:", JSON.stringify(pasteTarget.value()));
row1.paste(pasteTarget);
console.log("paste() 后 pasteTarget 值:", JSON.stringify(pasteTarget.value()));

// 粘贴样式到另一行
var styleTarget = sheet.row(16);
styleTarget.setValue(["样式", "粘贴", "测试"]);
row1.pasteStyle(styleTarget);
console.log("pasteStyle() 后样式已应用(请打开文件查看)");

// 测试索引版本
row1.copy(18);
console.log("copy(18) 后第18行值:", JSON.stringify(sheet.row(18).value()));

var pasteIndexTarget = sheet.row(20);
pasteIndexTarget.setValue(["P1", "P2", "P3", "P4", "P5"]);
row1.paste(20);
console.log("paste(20) 后第20行值:", JSON.stringify(sheet.row(20).value()));

var pasteStyleIndexTarget = sheet.row(22);
pasteStyleIndexTarget.setValue(["S1", "S2", "S3"]);
row1.pasteStyle(22);
console.log("pasteStyle(22) 后样式已应用");

// ==================== 6. 行删除与插入 ====================
console.log("\n========== 6. 行删除与插入测试 ==========");
var insertRow = sheet.row(24);
insertRow.setValue(["插入前", "数据"]);
console.log("插入前第24行值:", JSON.stringify(insertRow.value()));
insertRow.insert();
console.log("insert() 后第24行值:", JSON.stringify(sheet.row(24).value()));
console.log("第25行值:", JSON.stringify(sheet.row(25).value()));

var deleteRow = sheet.row(26);
deleteRow.setValue(["删除行", "测试"]);
console.log("删除前第26行值:", JSON.stringify(deleteRow.value()));
deleteRow.remove();
console.log("remove() 后第26行值:", JSON.stringify(sheet.row(26).value()));

// ==================== 7. 隐藏操作 ====================
console.log("\n========== 7. 隐藏操作测试 ==========");
var hideRow = sheet.row(28);
hideRow.setValue(["隐藏行", "测试"]);
console.log("隐藏前 hidden():", hideRow.hidden());
hideRow.setHidden(true);
console.log("setHidden(true) 后 hidden():", hideRow.hidden());

// ==================== 8. poiRow 和 getSheet ====================
console.log("\n========== 8. 底层对象测试 ==========");
console.log("poiRow().getRowNum():", row1.poiRow().getRowNum());


// ==================== 9. cells() 集合操作 ====================
console.log("\n========== 9. cells() 集合操作测试 ==========");
var cellColl = row1.cells();
console.log("cellColl.size():", cellColl.size());

var testRow2 = sheet.row(30);
testRow2.setValue(["A", "B", "C", "D", "E"]);
var cells2 = testRow2.cells();

cells2.forEach(function(cell) {
    console.log("单元格值:", cell.value());
});

cells2.style().bold(true).background("#CCCCCC");
console.log("批量样式已应用");

// ==================== 完成 ====================
excel.save({ path: TEST_DIR + "row_final.xlsx" });
console.log("\n✅ 所有测试完成!");
console.log("📁 生成的文件位于:", TEST_DIR);
console.log("   基础文件: row_base.xlsx");
console.log("   最终文件: row_final.xlsx(包含所有修改)");
excel.close();

ExcelRow.index()

  • 返回 {number}

获取行索引。

js
console.log("行:", row.index());

ExcelRow.empty()

  • 返回 {boolean}

判断行是否为空(所有单元格均为空)。

js
if (row.empty()) {
    console.log("该行为空");
}

ExcelRow.cellCount()

  • 返回 {number}

获取行中的单元格数量(包括空白单元格,即最大列索引 + 1)。若行中没有单元格,返回 -1。

js
console.log("单元格数:", row.cellCount());

ExcelRow.cell(index)

获取或创建指定列的单元格。如果单元格不存在,会自动创建。

js
var cell = row.cell(0);

ExcelRow.cells()

获取当前行所有已存在的单元格(不含空白)。

js
var cells = row.cells();
console.log("单元格数:", cells.size());

ExcelRow.setValue(values)

  • values {Array} 值数组
  • 返回 {ExcelRow}

设置行中所有单元格的值。如果数组长度超过现有列数,会自动创建新列。

js
row.setValue(["A", "B", "C"]);

ExcelRow.value()

  • 返回

获取行中所有单元格的值(按列顺序)。

js
var values = row.value();
console.log(JSON.stringify(values));

ExcelRow.setHeight(heightInPoints)

  • heightInPoints {number} 行高(单位:点)
  • 返回 {ExcelRow}

设置行高(单位:点)。

js
row.setHeight(20);

ExcelRow.height()

  • 返回 {number}

获取行高(单位:点)。

js
console.log("行高:", row.height());

ExcelRow.autoHeight()

自动调整行高(根据内容)。

js
row.autoHeight();

ExcelRow.style()

获取行样式构建器,支持链式设置字体、颜色、背景、边框、对齐等样式。

js
// 链式设置样式
row.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00")
    .border("thin")
    .align("center");

ExcelRow.copy(target)

将当前行的值和样式完整复制到目标行(包括行高、隐藏状态)。

js
row.copy(targetRow);

ExcelRow.copy(targetIndex)

  • targetIndex {number} 目标行索引
  • 返回 {ExcelRow}

将当前行的值和样式完整复制到指定索引的目标行(不存在则创建)。

js
row.copy(5);

ExcelRow.paste(target)

将当前行的值粘贴到目标行(仅值,不改变样式和行属性)。

js
row.paste(targetRow);

ExcelRow.paste(targetIndex)

  • targetIndex {number} 目标行索引
  • 返回 {ExcelRow}

将当前行的值粘贴到指定索引的目标行(不存在则创建)。

js
row.paste(5);

ExcelRow.pasteStyle(target)

将当前行的样式粘贴到目标行(仅样式,不改变值和行属性)。

js
row.pasteStyle(targetRow);

ExcelRow.pasteStyle(targetIndex)

  • targetIndex {number} 目标行索引
  • 返回 {ExcelRow}

将当前行的样式粘贴到指定索引的目标行(不存在则创建)。

js
row.pasteStyle(5);

ExcelRow.remove()

从工作表中删除当前行。

js
row.remove();

ExcelRow.insert()

在当前行前插入一个空白行,并将当前行及其下方所有行下移。插入后,当前 ExcelRow 对象会更新为指向新插入的行。

js
row.insert();

ExcelRow.setHidden(hidden)

  • hidden {boolean} 是否隐藏
  • 返回 {ExcelRow}

设置行的隐藏状态。

js
row.setHidden(true);

ExcelRow.hidden()

  • 返回 {boolean}

判断行是否隐藏。

js
if (row.hidden()) {
    console.log("行已隐藏");
}

ExcelRow.poiRow()

  • 返回 {org.apache.poi.ss.usermodel.Row}

获取底层 POI Row 对象,用于执行 POI 原生行操作。

js
var poiRow = row.poiRow();

ExcelRowCollection

ExcelCursor.rows() 等方法返回,代表行的集合。

ExcelRowCollection.size()

  • 返回 {number}

获取行数。

js
console.log("行数:", rows.size());

ExcelRowCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (rows.empty()) {
    console.log("没有行");
}

ExcelRowCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (rows.nonEmpty()) {
    console.log("有行");
}

ExcelRowCollection.get(index)

  • index {number} 索引
  • 返回 {ExcelRow | null}

获取指定索引的行。

js
var row = rows.get(0);
console.log(row.index());

ExcelRowCollection.forEach(callback)

遍历所有行。

js
rows.forEach(function(row) {
    console.log("行索引:", row.index());
});

// 使用 size() + get() 遍历
for (var i = 0; i < rows.size(); i++) {
    var row = rows.get(i);
    console.log("行索引:", row.index());
}

ExcelRowCollection.filter(filter)

过滤行。

js
var nonEmptyRows = rows.filter(function(row) {
    return !row.empty();
});

ExcelRowCollection.find(filter)

  • filter {Function} 过滤函数 (row) => boolean
  • 返回 {ExcelRow | null}

查找第一个匹配的行。

js
var found = rows.find(function(row) {
    return row.index() === 5;
});

ExcelCell

ExcelSheet.cell(address)ExcelRange.cell(rowOffset, colOffset) 等方法返回,代表一个单元格。

完整测试示例
js
// ==================== ExcelCell 完整 API 测试(严谨类型处理) ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelCellTest/

var TEST_DIR = "/sdcard/ExcelCellTest/";
files.ensureDir(TEST_DIR);

// 打开(或创建)工作簿,并获取 Sheet
var excel = $excel.open({ path: TEST_DIR + "cell_test.xlsx" });
var sheetName = "Cell测试";
if (excel.hasSheet(sheetName)) {
    excel.removeSheet(sheetName);
}
var sheet = excel.createSheet(sheetName);

// ==================== 准备基础数据 ====================
console.log("\n📦 准备基础数据...");
sheet.range("A1:E5").setValue([
    ["姓名", "年龄", "分数", "日期", "是否通过"],
    ["张三", 25, 90.5, "2024-01-15", true],
    ["李四", 30, 85.0, "2024-02-20", false],
    ["王五", 22, 76.5, "2024-03-10", true],
    ["赵六", 28, 92.0, "2024-04-05", false]
]);
excel.save({ path: TEST_DIR + "cell_base.xlsx" });
console.log("基础数据已保存 -> cell_base.xlsx");

// ==================== 获取单元格对象 ====================
var cellA1 = sheet.cell("A1"); // 字符串
var cellB2 = sheet.cell(1, 1); // 数字:25
var cellC3 = sheet.cell("C3"); // 数字:76.5
var cellD4 = sheet.cell(3, 3); // 文本日期
var cellE5 = sheet.cell("E5"); // 布尔值:false
var cellF1 = sheet.cell(0, 5); // 空单元格

console.log("\n========== 1. 值读写测试 ==========");
console.log("cellA1.value():", cellA1.value(), "类型:", cellA1.type());
console.log("cellB2.value():", cellB2.value(), "类型:", cellB2.type());
console.log("cellC3.value():", cellC3.value(), "类型:", cellC3.type());
console.log("cellD4.value():", cellD4.value(), "类型:", cellD4.type());
console.log("cellE5.value():", cellE5.value(), "类型:", cellE5.type());

// 修改值
cellA1.setValue("新表头");
console.log("setValue('新表头') 后 cellA1.value():", cellA1.value());

// 类型安全的取值方式
if (cellA1.type() === "STRING") {
    console.log("cellA1.string():", cellA1.string());
} else {
    console.log("cellA1 不是字符串类型,无法调用 string()");
}
if (cellB2.type() === "NUMERIC") {
    console.log("cellB2.number():", cellB2.number());
} else {
    console.log("cellB2 不是数字类型,无法调用 number()");
}
if (cellE5.type() === "BOOLEAN") {
    console.log("cellE5.bool():", cellE5.bool());
} else {
    console.log("cellE5 不是布尔类型,无法调用 bool()");
}

// 如果类型不确定,使用 value() 更安全
console.log("cellC3.value():", cellC3.value());

// ==================== 2. 类型判断测试 ====================
console.log("\n========== 2. 类型判断测试 ==========");
console.log("cellA1.type():", cellA1.type());
console.log("cellB2.type():", cellB2.type());
console.log("cellF1.empty():", cellF1.empty());
console.log("cellA1.empty():", cellA1.empty());
console.log("cellB2.isMerged():", cellB2.isMerged()); // 尚未合并

// ==================== 3. 样式测试 ====================
console.log("\n========== 3. 样式测试 ==========");
var styleCell = sheet.cell("A10");
styleCell.setValue("样式测试");
styleCell.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00")
    .border("thin")
    .align("center");
console.log("样式已应用到 A10");

// ==================== 4. 复制与粘贴测试 ====================
console.log("\n========== 4. 复制与粘贴测试 ==========");
var srcCell = sheet.cell("B2"); // 25
var dstCell = sheet.cell("G1");
console.log("源值:", srcCell.value(), "目标值:", dstCell.value());
srcCell.copy(dstCell);
console.log("copy() 后目标值:", dstCell.value());

var pasteTarget = sheet.cell("H1");
pasteTarget.setValue("旧值");
srcCell.paste(pasteTarget);
console.log("paste() 后目标值:", pasteTarget.value());

var styleTarget = sheet.cell("I1");
styleTarget.setValue("样式粘贴测试");
srcCell.pasteStyle(styleTarget);
console.log("pasteStyle() 后样式已应用(请打开文件查看)");

// 地址版本
srcCell.copy("G2");
console.log("copy('G2') 后 G2 值:", sheet.cell("G2").value());
srcCell.paste("H2");
console.log("paste('H2') 后 H2 值:", sheet.cell("H2").value());
srcCell.pasteStyle("I2");
console.log("pasteStyle('I2') 后样式已应用");

// ==================== 5. 超链接测试 ====================
console.log("\n========== 5. 超链接测试 ==========");
var linkCell = sheet.cell("A20");
linkCell.setValue("点击访问");
linkCell.setHyperlink("https://example.com");
console.log("hasHyperlink():", linkCell.hasHyperlink());
var link = linkCell.hyperlink();
if (link) {
    console.log("超链接地址:", link.address());
    console.log("超链接类型:", link.type());
}
linkCell.setEmailHyperlink("user@example.com");
console.log("setEmailHyperlink 后地址:", linkCell.hyperlink().address());
linkCell.removeHyperlink();
console.log("removeHyperlink() 后 hasHyperlink():", linkCell.hasHyperlink());

// ==================== 6. 批注测试 ====================
console.log("\n========== 6. 批注测试 ==========");
var commentCell = sheet.cell("B20");
commentCell.setValue("批注单元格");
commentCell.setComment("这是一个批注");
console.log("hasComment():", commentCell.hasComment());
var cmt = commentCell.comment();
if (cmt) {
    console.log("批注内容:", cmt.text());
    console.log("批注作者:", cmt.author());
}
commentCell.removeComment();
console.log("removeComment() 后 hasComment():", commentCell.hasComment());

// ==================== 7. 数据验证测试 ====================
console.log("\n========== 7. 数据验证测试 ==========");
var dropCell = sheet.cell("C20");
dropCell.setValue("性别");
dropCell.setDropdown(["男", "女", "其他"]);
console.log("下拉列表验证已添加");

var intCell = sheet.cell("D20");
intCell.setValue("年龄");
intCell.setInteger({ min: 18, max: 60, promptTitle: "年龄输入", promptText: "请输入18-60", errorTitle: "错误", errorText: "年龄范围18-60" });
console.log("整数验证已添加");

var decCell = sheet.cell("E20");
decCell.setValue("分数");
decCell.setDecimal({ min: 0, max: 100 });
console.log("小数验证已添加");

var dateCell = sheet.cell("F20");
dateCell.setValue("日期");
dateCell.setDate({ min: "2020-01-01", max: "2025-12-31" });
console.log("日期验证已添加");

var timeCell = sheet.cell("G20");
timeCell.setValue("时间");
timeCell.setTime({ min: "08:00", max: "18:00" });
console.log("时间验证已添加");

var textCell = sheet.cell("H20");
textCell.setValue("文本长度");
textCell.setTextLength({ minLength: 5, maxLength: 10 });
console.log("文本长度验证已添加");

// 删除验证
textCell.removeValidation();
console.log("removeValidation() 后验证已删除");

// ==================== 8. 公式测试 ====================
console.log("\n========== 8. 公式测试 ==========");
var formulaCell = sheet.cell("A30");
formulaCell.setFormula("SUM(B2:B5)");
console.log("formula():", formulaCell.formula());
console.log("hasFormula():", formulaCell.hasFormula());
console.log("calculatedValue():", formulaCell.calculatedValue());

// ==================== 9. 格式化文本测试 ====================
console.log("\n========== 9. 格式化文本测试 ==========");
var dateCell2 = sheet.cell("D4");
dateCell2.style().dateFormat("yyyy-MM-dd");
console.log("text():", dateCell2.text());
var evaluator = excel.poiWorkbook().getCreationHelper().createFormulaEvaluator();
console.log("text(evaluator):", dateCell2.text(evaluator));

// ==================== 10. 图片插入测试 ====================
console.log("\n========== 10. 图片插入测试 ==========");
var imgPath = "/sdcard/drawnImg.png";
if (files.isFile(imgPath)) {
    var imgCell = sheet.cell("A40");
    imgCell.setValue("图片");
    imgCell.setImage(imgPath)
        .size(200, 150)
        .insert();
    console.log("图片已插入到 A40");
} else {
    console.log("⚠️ 测试图片不存在,跳过图片插入测试");
}

// ==================== 11. poiCell 测试 ====================
console.log("\n========== 11. poiCell 底层对象测试 ==========");
var poiCell = cellB2.poiCell();
console.log("poiCell.getColumnIndex():", poiCell.getColumnIndex());
console.log("poiCell.getRowIndex():", poiCell.getRowIndex());

// ==================== 12. 其他方法:type、empty、isMerged 已在前面覆盖 ====================
console.log("\n✅ 所有测试完成!");

// ==================== 保存 ====================
excel.save({ path: TEST_DIR + "cell_final.xlsx" });
console.log("📁 生成的文件位于:", TEST_DIR);
console.log("   基础文件: cell_base.xlsx");
console.log("   最终文件: cell_final.xlsx(包含所有修改)");
excel.close();

ExcelCell.poiCell()

获取底层 POI Cell 对象。

js
var poiCell = cell.poiCell();

ExcelCell.value()

  • 返回 {Object}

获取单元格值。

js
console.log("值:", cell.value());

ExcelCell.setValue(value)

  • value {Object} 要设置的值
  • 返回 {ExcelCell}

设置单元格值。

js
cell.setValue("Hello");

ExcelCell.type()

  • 返回 {string}

获取单元格类型(如 STRINGNUMERICBOOLEANFORMULABLANK 等)。

js
console.log("类型:", cell.type());

ExcelCell.string()

  • 返回 {string}

仅当单元格类型为 STRING 时有效,否则抛出 IllegalStateException
建议先使用 type() 判断类型,或使用通用的 value() 方法。

js
// 安全调用
if (cell.type() === "STRING") {
    console.log("字符串:", cell.string());
} else {
    console.log("单元格不是字符串类型,当前类型:", cell.type());
}

// 或者使用通用方法
console.log("值:", cell.value());

ExcelCell.number()

  • 返回 {number}

仅当单元格类型为 NUMERIC 时有效,否则抛出 IllegalStateException
建议先使用 type() 判断类型,或使用通用的 value() 方法。

js
// 安全调用
if (cell.type() === "NUMERIC") {
    console.log("数字:", cell.number());
} else {
    console.log("单元格不是数值类型,当前类型:", cell.type());
}

// 或者使用通用方法
console.log("值:", cell.value());

ExcelCell.bool()

  • 返回 {boolean}

仅当单元格类型为 BOOLEAN 时有效,否则抛出 IllegalStateException
建议先使用 type() 判断类型,或使用通用的 value() 方法。

js
// 安全调用
if (cell.type() === "BOOLEAN") {
    console.log("布尔值:", cell.bool());
} else {
    console.log("单元格不是布尔类型,当前类型:", cell.type());
}

// 或者使用通用方法
console.log("值:", cell.value());

ExcelCell.empty()

  • 返回 {boolean}

判断单元格是否为空(内容为空或单元格为空白)。

js
if (cell.empty()) {
    console.log("单元格为空");
}

ExcelCell.isMerged()

  • 返回 {boolean}

判断是否为合并区域的一部分。

js
if (cell.isMerged()) {
    console.log("该单元格位于合并区域");
}

ExcelCell.style()

获取样式构建器,支持链式设置字体、颜色、背景、边框、对齐等样式。

js
cell.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00");

ExcelCell.copy(target)

将当前单元格的值和样式完整复制到目标单元格。

js
cell.copy(targetCell);

ExcelCell.copy(targetAddress)

  • targetAddress {string} 目标单元格地址(如 "B5"
  • 返回 {ExcelCell}

将当前单元格的值和样式完整复制到指定地址。

js
cell.copy("B5");

ExcelCell.paste(target)

仅将当前单元格的值粘贴到目标单元格(不改变目标样式)。

js
cell.paste(targetCell);

ExcelCell.paste(targetAddress)

  • targetAddress {string} 目标单元格地址(如 "B5"
  • 返回 {ExcelCell}

仅将当前单元格的值粘贴到指定地址。

js
cell.paste("B5");

ExcelCell.pasteStyle(target)

仅将当前单元格的样式粘贴到目标单元格(不改变目标值)。

js
cell.pasteStyle(targetCell);

ExcelCell.pasteStyle(targetAddress)

  • targetAddress {string} 目标单元格地址(如 "B5"
  • 返回 {ExcelCell}

仅将当前单元格的样式粘贴到指定地址。

js
cell.pasteStyle("B5");

  • url {string} 超链接地址
  • 返回 {ExcelCell}

添加超链接。

js
cell.setHyperlink("https://example.com");
  • email {string} 邮箱地址
  • 返回 {ExcelCell}

添加邮箱超链接(自动添加 mailto: 前缀)。

js
cell.setEmailHyperlink("user@example.com");

添加文件超链接。

js
cell.setFileHyperlink("/sdcard/file.pdf");

删除超链接。

js
cell.removeHyperlink();
  • 返回 {boolean}

判断是否有超链接。

js
if (cell.hasHyperlink()) {
    console.log("有超链接");
}

获取超链接信息对象。

js
var link = cell.hyperlink();
if (link) {
    console.log("地址:", link.address());
    console.log("类型:", link.type());
}

ExcelCell.setComment(text)

添加批注(如果已存在批注,新批注会覆盖旧批注)。

js
cell.setComment("这是批注内容");

ExcelCell.removeComment()

删除批注。

js
cell.removeComment();

ExcelCell.hasComment()

  • 返回 {boolean}

判断是否有批注。

js
if (cell.hasComment()) {
    console.log("有批注");
}

ExcelCell.comment()

获取批注信息对象。

js
var comment = cell.comment();
if (comment) {
    console.log("内容:", comment.text());
    console.log("作者:", comment.author());
}

ExcelCell.setDropdown(options)

  • options {Array} 下拉选项列表
  • 返回 {ExcelCell}

添加下拉列表验证(自动处理选项长度超限问题)。

js
cell.setDropdown(["男", "女", "其他"]);

ExcelCell.setInteger(options)

  • options {Object} 配置对象,包含:
    • min {number} 最小值(默认 0)
    • max {number} 最大值(默认 100)
    • promptTitle {string} 提示标题(可选)
    • promptText {string} 提示内容(可选)
    • errorTitle {string} 错误标题(可选)
    • errorText {string} 错误内容(可选)
  • 返回 {ExcelCell}

添加整数范围验证。

js
// 仅设置范围
cell.setInteger({ min: 18, max: 60 });

// 带提示信息
cell.setInteger({
    min: 18,
    max: 60,
    promptTitle: "年龄输入",
    promptText: "请输入18-60之间的整数",
    errorTitle: "错误",
    errorText: "年龄必须在18-60之间"
});

ExcelCell.setDecimal(options)

  • options {Object} 配置对象,包含:
    • min {number} 最小值(默认 0.0)
    • max {number} 最大值(默认 100.0)
    • 其他同 setIntegerpromptTitlepromptTexterrorTitleerrorText
  • 返回 {ExcelCell}

添加小数范围验证。

js
cell.setDecimal({ min: 0.0, max: 100.0 });

ExcelCell.setDate(options)

  • options {Object} 配置对象,包含:
    • min {string} 最小日期(格式:"yyyy-MM-dd",必需)
    • max {string} 最大日期(格式:"yyyy-MM-dd",必需)
    • 其他同 setIntegerpromptTitlepromptTexterrorTitleerrorText
  • 返回 {ExcelCell}

添加日期范围验证。

js
cell.setDate({ min: "2020-01-01", max: "2025-12-31" });

ExcelCell.setTime(options)

  • options {Object} 配置对象,包含:
    • min {string} 最小时间(格式:"HH:mm",必需)
    • max {string} 最大时间(格式:"HH:mm",必需)
    • 其他同 setInteger
  • 返回 {ExcelCell}

添加时间范围验证。

js
cell.setTime({ min: "08:00", max: "18:00" });

ExcelCell.setTextLength(options)

  • options {Object} 配置对象,包含:
    • minLength {number} 最小长度(默认 0)
    • maxLength {number} 最大长度(默认 255)
    • 其他同 setInteger
  • 返回 {ExcelCell}

添加文本长度验证。

js
cell.setTextLength({ minLength: 5, maxLength: 10 });

ExcelCell.removeValidation()

删除当前单元格的所有数据验证。

js
cell.removeValidation();

ExcelCell.formula()

  • 返回 {string | null}

获取公式字符串(如果单元格是公式类型)。

js
console.log("公式:", cell.formula());

ExcelCell.setFormula(formula)

  • formula {string} 公式字符串(可带 = 前缀,将自动去除)
  • 返回 {ExcelCell}

设置公式。

js
cell.setFormula("SUM(A1:A10)");

ExcelCell.hasFormula()

  • 返回 {boolean}

判断是否为公式单元格。

js
if (cell.hasFormula()) {
    console.log("是公式");
}

ExcelCell.calculatedValue()

  • 返回 {Object | null}

获取公式的计算结果(若单元格是公式类型)。

js
console.log("计算结果:", cell.calculatedValue());

ExcelCell.text()

  • 返回 {string}

获取格式化后的显示文本(根据单元格格式)。

js
console.log("显示文本:", cell.text());

ExcelCell.text(evaluator)

  • evaluator {FormulaEvaluator} 公式求值器
  • 返回 {string}

使用指定求值器获取格式化文本(适用于公式单元格)。

js
var evaluator = excel.poiWorkbook().getCreationHelper().createFormulaEvaluator();
console.log("显示文本:", cell.text(evaluator));

ExcelCell.setImage(image)

  • image {string | byte[] | Bitmap} 图片数据:
    • 字符串:图片文件路径(如 "/sdcard/logo.png"
    • 字节数组:图片的原始数据(byte[]
    • Bitmap:Android 的 Bitmap 对象
  • 返回 {ExcelImage}

在当前单元格位置插入图片,返回图片构建器,可继续链式调用 .size().insert() 等方法。

js
// 从文件路径
cell.setImage("/sdcard/logo.png")
    .size(200, 150)
    .insert();

// 从字节数组
cell.setImage(imageData)
    .size(100, 100)
    .insert();

// 从 Bitmap
cell.setImage(bitmap)
    .size(150, 150)
    .insert();

ExcelCellCollection

ExcelRow.cells() 等方法返回,代表单元格的集合。

ExcelCellCollection.size()

  • 返回 {number}

获取单元格数量。

js
console.log("单元格数:", cells.size());

ExcelCellCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (cells.empty()) {
    console.log("没有单元格");
}

ExcelCellCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (cells.nonEmpty()) {
    console.log("有单元格");
}

ExcelCellCollection.get(index)

  • index {number} 索引
  • 返回 {ExcelCell | null}

获取指定索引的单元格。

js
var cell = cells.get(0);
console.log(cell.value());

ExcelCellCollection.forEach(callback)

遍历所有单元格。

js
cells.forEach(function(cell) {
    console.log(cell.value());
});

// 使用 size() + get() 遍历
for (var i = 0; i < cells.size(); i++) {
    var cell = cells.get(i);
    console.log(cell.value());
}

ExcelCellCollection.filter(filter)

过滤单元格。

js
var nonEmpty = cells.filter(function(cell) {
    return !cell.empty();
});

ExcelCellCollection.find(filter)

  • filter {Function} 过滤函数 (cell) => boolean
  • 返回 {ExcelCell | null}

查找第一个匹配的单元格。

js
var found = cells.find(function(cell) {
    return cell.value() === "目标值";
});

ExcelCellCollection.style()

为所有单元格设置统一样式。

js
cells.style()
    .bold(true)
    .background("#FFFF00");

ExcelStyle

ExcelRange.style()ExcelRow.style()ExcelCell.style() 等方法返回,用于链式设置单元格样式。

完整测试示例
js

// ==================== ExcelStyle 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelStyleTest/

var TEST_DIR = "/sdcard/ExcelStyleTest/";
files.ensureDir(TEST_DIR);

// 打开(或创建)工作簿,并获取 Sheet
var excel = $excel.open({ path: TEST_DIR + "style_test.xlsx" });
var sheetName = "样式测试";
if (excel.hasSheet(sheetName)) {
    excel.removeSheet(sheetName);
}
var sheet = excel.createSheet(sheetName);

// 辅助:写入一些基础数据
sheet.range("A1:E5").setValue([
    ["姓名", "年龄", "分数", "日期", "是否通过"],
    ["张三", 25, 90.5, "2024-01-15", true],
    ["李四", 30, 85.0, "2024-02-20", false],
    ["王五", 22, 76.5, "2024-03-10", true],
    ["赵六", 28, 92.0, "2024-04-05", false]
]);

console.log("\n========== ExcelStyle 样式测试 ==========");

// ==================== 1. 字体样式 ====================
console.log("\n--- 1. 字体样式 ---");
var rangeFont = sheet.range("A2:E2");
rangeFont.style()
    .fontName("微软雅黑")
    .fontSize(14)
    .bold(true)
    .italic(true)
    .underline(1)                // 单下划线
    .strikeout(false)
    .fontColor("#FF0000");       // 红色,字符串
console.log("字体样式已应用(A2:E2)");

// ==================== 2. 对齐与换行 ====================
console.log("\n--- 2. 对齐与换行 ---");
var rangeAlign = sheet.range("A3:E3");
rangeAlign.style()
    .align("center")
    .vertical("middle")
    .wrap(true);
console.log("对齐样式已应用(A3:E3)");

// ==================== 3. 背景色(字符串和整数) ====================
console.log("\n--- 3. 背景色 ---");
var rangeBg = sheet.range("A4:E4");
rangeBg.style()
    .background("#FFFF00");      // 字符串
console.log("背景色(字符串)已应用(A4:E4)");

var rangeBgInt = sheet.range("A5:E5");
rangeBgInt.style()
    .background(0xFF00FF);       // 整数(品红)
console.log("背景色(整数)已应用(A5:E5)");

// ==================== 4. 边框 ====================
console.log("\n--- 4. 边框 ---");
var rangeBorder = sheet.range("A7:E9");
rangeBorder.setValue([
    ["边框", "测试", "区域", "A", "B"],
    ["上", "下", "左", "右", "外"],
    ["内", "全", "顶", "底", "边"]
]);

// 外边框(字符串颜色)
rangeBorder.style()
    .border("thin", "#0000FF")   // 蓝色外边框
    .insideBorder("dotted", "#CCCCCC")  // 灰色虚线内边框
    .allBorder("double", "#FF0000");    // 红色双线所有边框(会覆盖外边框和内边框,为了演示只保留最后一个)
console.log("边框已应用(A7:E9)");

// 单独测试单个边框(使用整数颜色)
var rangeSingle = sheet.range("A11:E11");
rangeSingle.setValue(["上边框", "下边框", "左边框", "右边框", "组合"]);
rangeSingle.style()
    .borderTop("medium", 0xFF0000)
    .borderBottom("thick", 0x00FF00)
    .borderLeft("dashed", 0x0000FF)
    .borderRight("double", 0xFFFF00);
console.log("单边边框已应用(A11:E11)");

// ==================== 5. 数字格式 ====================
console.log("\n--- 5. 数字格式 ---");
var rangeFormat = sheet.range("C2:C5");
rangeFormat.style()
    .format("#,##0.00");         // 千分位小数
console.log("数字格式已应用(C2:C5)");

// 日期格式
var rangeDate = sheet.range("D2:D5");
rangeDate.style()
    .format("yyyy-MM-dd");
console.log("日期格式已应用(D2:D5)");

// 百分比
var rangePercent = sheet.range("B12");
rangePercent.setValue(0.85);
rangePercent.style()
    .format("0.00%");
console.log("百分比格式已应用(B12)");

// ==================== 6. 锁定与隐藏 ====================
console.log("\n--- 6. 锁定与隐藏 ---");
var rangeLock = sheet.range("A13:C13");
rangeLock.setValue(["锁定", "隐藏", "测试"]);
rangeLock.style()
    .lock(true)
    .hidden(true);
console.log("锁定与隐藏已应用(A13:C13)");

// ==================== 7. 样式复制 ====================
console.log("\n--- 7. 样式复制 ---");
var sourceStyle = sheet.range("A2:E2").style();  // 获取现有样式(但无法直接复制,需创建新样式)
// 创建一个新样式并复制
var targetRange = sheet.range("A15:E15");
targetRange.setValue(["复制", "样式", "测试", "A", "B"]);
var newStyle = targetRange.style()
    .bold(true)
    .fontColor("#00FF00");
// 复制源样式(从另一个样式对象复制)
// 由于无法直接获取已有样式对象,我们创建一个临时样式并复制
var tempStyle = sheet.range("A2:E2").style();
// 注意:copy 方法需要传入 ExcelStyle 对象,但 range.style() 返回的是新实例,不能直接获取现有样式。
// 为了测试 copy,我们创建一个样式并复制属性
var copyTarget = sheet.range("A17:E17");
copyTarget.setValue(["复制", "样式", "测试", "C", "D"]);
var styleToCopy = copyTarget.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00");
// 现在复制到另一个范围
var pasteRange = sheet.range("A19:E19");
pasteRange.setValue(["粘贴", "样式", "测试", "E", "F"]);
pasteRange.style().copy(styleToCopy);
console.log("样式复制完成(A19:E19 复制了 A17:E17 的样式)");

// ==================== 8. 颜色整数测试(多种格式) ====================
console.log("\n--- 8. 颜色整数测试 ---");
var rangeIntColor = sheet.range("A21:E21");
rangeIntColor.setValue(["整数颜色", "RGB", "ARGB", "混合", "测试"]);
rangeIntColor.style()
    .fontColor(0xFF8800)          // 橙色
    .background(0x88FF00)         // 亮绿色
    .border("thin", 0x0000FF);    // 蓝色边框
console.log("整数颜色已应用(A21:E21)");

// ==================== 9. 综合样式(链式调用) ====================
console.log("\n--- 9. 综合样式(链式调用) ---");
var rangeComplex = sheet.range("A23:E25");
rangeComplex.setValue([
    ["综合", "样式", "测试", "区域", "A"],
    ["行1", "行2", "行3", "行4", "行5"],
    ["结束", "测试", "完成", "OK", "!"]
]);
rangeComplex.style()
    .fontName("Arial")
    .fontSize(16)
    .bold(true)
    .italic(false)
    .underline(2)                 // 双下划线
    .strikeout(true)
    .fontColor("#FFFFFF")
    .align("center")
    .vertical("middle")
    .wrap(false)
    .background("#4472C4")
    .border("medium", "#FF0000")
    .insideBorder("thin", "#CCCCCC")
    .allBorder("thin", "#0000FF") // 覆盖之前的边框,为了演示可注释掉
    .format("0.00")
    .lock(true)
    .hidden(false);
console.log("综合样式已应用(A23:E25)");

// ==================== 10. 边框带颜色(字符串和整数混合) ====================
console.log("\n--- 10. 边框颜色混合 ---");
var rangeMix = sheet.range("A27:E27");
rangeMix.setValue(["混合", "边框", "颜色", "测试", "完成"]);
rangeMix.style()
    .borderTop("thin", "#FF00FF")
    .borderBottom("medium", 0x00FFFF)
    .borderLeft("thick", "#00FF00")
    .borderRight("double", 0xFF0000);
console.log("混合边框颜色已应用(A27:E27)");

// ==================== 保存文件 ====================
excel.save({ path: TEST_DIR + "style_final.xlsx" });
console.log("\n✅ 所有样式测试完成!");
console.log("📁 生成的文件位于:", TEST_DIR);
console.log("   文件: style_final.xlsx");
excel.close();
console.log("测试结束。");

ExcelStyle.fontName(name)

设置字体名称。

js
style.fontName("微软雅黑");

ExcelStyle.fontSize(size)

  • size {number} 字体大小(单位:点)
  • 返回 {ExcelStyle}

设置字体大小。

js
style.fontSize(12);

ExcelStyle.bold(bold)

设置加粗。

js
style.bold(true);

ExcelStyle.italic(italic)

设置斜体。

js
style.italic(true);

ExcelStyle.underline(type)

  • type {number} 下划线类型:
    • 0:无下划线
    • 1:单下划线
    • 2:双下划线
    • (其他值:33 为单会计下划线,34 为双会计下划线,较少使用)
  • 返回 {ExcelStyle}

设置下划线。

js
style.underline(1); // 单下划线
style.underline(2); // 双下划线
style.underline(0); // 取消下划线

ExcelStyle.strikeout(strikeout)

  • strikeout {boolean} 是否删除线
  • 返回 {ExcelStyle}

设置删除线。

js
style.strikeout(true);

ExcelStyle.fontColor(color)

  • color {string | number} 十六进制颜色字符串(如 "#FF0000")或 32 位整数颜色值(如 0xFF00000xFFFF0000
  • 返回 {ExcelStyle}

设置字体颜色。整数形式支持 ARGB(Alpha 通道会被忽略)。

js
style.fontColor("#FF0000");
style.fontColor(0xFF0000);
style.fontColor(0xFFFF0000); // Alpha 被忽略

ExcelStyle.align(align)

  • align {string} 水平对齐方式:"left""center""right"
  • 返回 {ExcelStyle}

设置水平对齐。

js
style.align("center");

ExcelStyle.vertical(align)

  • align {string} 垂直对齐方式:"top""middle""bottom"
  • 返回 {ExcelStyle}

设置垂直对齐。

js
style.vertical("middle");

ExcelStyle.wrap(wrap)

  • wrap {boolean} 是否自动换行
  • 返回 {ExcelStyle}

设置自动换行。

js
style.wrap(true);

ExcelStyle.background(color)

  • color {string | number} 十六进制颜色字符串或 32 位整数颜色值
  • 返回 {ExcelStyle}

设置背景色。整数形式支持 ARGB(Alpha 通道会被忽略)。

js
style.background("#FFFF00");
style.background(0xFFFF00);

ExcelStyle.border(styleStr[, color])

  • styleStr {string} 边框样式:"thin""medium""thick""double""dotted""dashed""hair""none"
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置外边框。

js
style.border("thin");
style.border("thin", "#000000");
style.border("thin", 0x000000);

ExcelStyle.insideBorder(styleStr[, color])

  • styleStr {string} 边框样式
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置内部边框(仅对多行多列区域有效)。

js
style.insideBorder("thin");
style.insideBorder("thin", "#CCCCCC");
style.insideBorder("thin", 0xCCCCCC);

ExcelStyle.allBorder(styleStr[, color])

  • styleStr {string} 边框样式
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置所有单元格的边框(每个单元格都有边框)。

js
style.allBorder("thin");
style.allBorder("thin", "#000000");
style.allBorder("thin", 0x000000);

ExcelStyle.borderTop(styleStr[, color])

  • styleStr {string} 边框样式
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置上边框。

js
style.borderTop("thin");
style.borderTop("thin", "#FF0000");
style.borderTop("thin", 0xFF0000);

ExcelStyle.borderBottom(styleStr[, color])

  • styleStr {string} 边框样式
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置下边框。

js
style.borderBottom("thin");
style.borderBottom("thin", "#FF0000");
style.borderBottom("thin", 0xFF0000);

ExcelStyle.borderLeft(styleStr[, color])

  • styleStr {string} 边框样式
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置左边框。

js
style.borderLeft("thin");
style.borderLeft("thin", "#FF0000");
style.borderLeft("thin", 0xFF0000);

ExcelStyle.borderRight(styleStr[, color])

  • styleStr {string} 边框样式
  • color {string | number} 可选,边框颜色(十六进制字符串或 32 位整数)
  • 返回 {ExcelStyle}

设置右边框。

js
style.borderRight("thin");
style.borderRight("thin", "#FF0000");
style.borderRight("thin", 0xFF0000);

ExcelStyle.format(format)

  • format {string} Excel 格式字符串
  • 返回 {ExcelStyle}

设置单元格数据格式。

js
style.format("#,##0.00");
style.format("0.00%");
style.format("yyyy-MM-dd");
style.format("@");
style.format("¥#,##0.00");
style.format("HH:mm:ss");

ExcelStyle.lock(locked)

设置单元格锁定状态(需配合工作表保护生效)。

js
style.lock(true);

ExcelStyle.hidden(hidden)

  • hidden {boolean} 是否隐藏公式
  • 返回 {ExcelStyle}

设置公式隐藏状态(需配合工作表保护生效)。

js
style.hidden(true);

ExcelStyle.copy(source)

  • source {ExcelStyle} 源样式对象
  • 返回 {ExcelStyle}

从另一个样式复制所有属性到当前样式。

js
var sourceStyle = range.style().bold(true).background("#FFFF00");
style.copy(sourceStyle);

ExcelImage

ExcelCell.setImage(image) 等方法返回,用于构建和插入图片。

完整测试示例
js
// ==================== ExcelImage 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelImageTest/
// 请将以下图片路径替换为实际存在的图片路径

var TEST_DIR = "/sdcard/ExcelImageTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({ path: TEST_DIR + "image_test.xlsx" });
var sheet = excel.sheet("图片测试");
if (!sheet) sheet = excel.createSheet("图片测试");

// 测试图片路径(请根据实际情况修改)
var img1 = "/sdcard/test.png";
var img2 = "/sdcard/logo.png";
var img3 = "/sdcard/photo.jpg";
var img4 = "/sdcard/icon.png";
var img5 = "/sdcard/photo2.jpg";

console.log("\n========== ExcelImage API 测试 ==========");

// 辅助函数:在指定单元格写入说明文字
function writeLabel(address, text) {
    var cell = sheet.cell(address);
    cell.setValue(text);
}

// ==================== 1. 使用 sheet.image() + at(string) + size ====================
if (files.isFile(img1)) {
    writeLabel("A1", "图片1 (200x150)");
    sheet.image(img1)
        .at("A1")
        .size(200, 150)
        .insert();
    console.log("✅ 图片1 插入到 A1,尺寸 200x150");
} else {
    console.log("⚠️ 图片1 不存在: " + img1);
}

// ==================== 2. 使用 sheet.image() + at(row, col) + width/height ====================
if (files.isFile(img2)) {
    writeLabel("D4", "图片2 (宽300, 高200)");
    sheet.image(img2)
        .at(3, 3)        // 第4行第4列 (D4)
        .width(300)
        .height(200)
        .insert();
    console.log("✅ 图片2 插入到 D4,宽度300,高度200");
} else {
    console.log("⚠️ 图片2 不存在: " + img2);
}

// ==================== 3. 使用 cell.setImage() ====================
if (files.isFile(img3)) {
    var cell = sheet.cell("G1");
    cell.setValue("图片3 (100x100)");
    cell.setImage(img3)
        .size(100, 100)
        .insert();
    console.log("✅ 图片3 插入到 G1,尺寸 100x100");
} else {
    console.log("⚠️ 图片3 不存在: " + img3);
}

// ==================== 4. 从字节数组插入 ====================
if (files.isFile(img4)) {
    var bytes = files.readBytes(img4);
    writeLabel("A5", "字节数组图片 (150x150)");
    sheet.image(bytes)
        .at("A5")
        .size(150, 150)
        .insert();
    console.log("✅ 字节数组图片插入到 A5");
} else {
    console.log("⚠️ 字节数组图片不存在: " + img4);
}

// ==================== 5. 仅设置宽度(高度使用默认或保持比例) ====================
if (files.isFile(img5)) {
    writeLabel("C10", "仅宽度300");
    sheet.image(img5)
        .at("C10")
        .width(300)
        .insert();
    console.log("✅ 图片5 仅设置宽度300,插入到 C10");
} else {
    console.log("⚠️ 图片5 不存在: " + img5);
}

// ==================== 6. 测试无效图片(异常处理) ====================
console.log("\n--- 异常测试 ---");
try {
    sheet.image("/sdcard/not_exist.png")
        .at("A10")
        .insert();
} catch (e) {
    console.log("✅ 捕获到预期异常(文件不存在):", e.message);
}

// 保存最终文件
excel.save({ path: TEST_DIR + "image_final.xlsx" });
console.log("\n✅ 所有测试完成!");
console.log("📁 生成的文件: image_final.xlsx");
excel.close();

ExcelImage.at(address)

  • address {string} 单元格地址,如 "A1"
  • 返回 {ExcelImage}

设置图片位置(单元格地址)。

js
sheet.image("/sdcard/logo.png")
    .at("A1");

ExcelImage.at(row, col)

  • row {number} 行索引
  • col {number} 列索引
  • 返回 {ExcelImage}

设置图片位置(行列索引)。

js
sheet.image("/sdcard/logo.png")
    .at(0, 0);

ExcelImage.size(width, height)

  • width {number} 宽度(像素)
  • height {number} 高度(像素)
  • 返回 {ExcelImage}

设置图片尺寸。

js
sheet.image("/sdcard/logo.png")
    .size(200, 150);

ExcelImage.width(width)

  • width {number} 宽度(像素)
  • 返回 {ExcelImage}

设置图片宽度。

js
sheet.image("/sdcard/logo.png")
    .width(300);

ExcelImage.height(height)

  • height {number} 高度(像素)
  • 返回 {ExcelImage}

设置图片高度。

js
sheet.image("/sdcard/logo.png")
    .height(200);

ExcelImage.insert()

  • 异常 {IOException}

插入图片到工作表。

js
sheet.image("/sdcard/logo.png")
    .at("A1")
    .size(200, 150)
    .insert();

ExcelValidation

ExcelValidationCollection.get(index) 等方法返回,代表一个数据验证。

完整测试示例
js
// ==================== ExcelValidation 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelValidationTest/

var TEST_DIR = "/sdcard/ExcelValidationTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({ path: TEST_DIR + "validation_detail_test.xlsx" });
var sheet = excel.sheet("验证详情");
if (!sheet) sheet = excel.createSheet("验证详情");

// 1. 创建表头
sheet.createHeader(["字段", "规则", "示例值"]);

// 2. 添加多种数据验证(每个验证设置不同的提示和错误信息,以便测试)
// 下拉列表 (A2) - 显式列表
sheet.cell(1, 0).setValue("性别");
sheet.cell(1, 1).setValue("下拉列表");
sheet.cell(1, 2).setDropdown(["男", "女", "其他"]);

// 整数验证 (A3) - 带提示和错误信息
sheet.cell(2, 0).setValue("年龄");
sheet.cell(2, 1).setValue("整数 18-60");
sheet.cell(2, 2).setInteger({
    min: 18,
    max: 60,
    promptTitle: "年龄输入",
    promptText: "请输入18-60之间的整数",
    errorTitle: "年龄错误",
    errorText: "年龄必须在18-60之间"
});

// 小数验证 (A4) - 只设置范围,无提示
sheet.cell(3, 0).setValue("分数");
sheet.cell(3, 1).setValue("小数 0-100");
sheet.cell(3, 2).setDecimal({ min: 0.0, max: 100.0 });

// 日期验证 (A5) - 带提示
sheet.cell(4, 0).setValue("日期");
sheet.cell(4, 1).setValue("日期范围");
sheet.cell(4, 2).setDate({
    min: "2020-01-01",
    max: "2025-12-31",
    promptTitle: "日期输入",
    promptText: "请输入2020-2025之间的日期"
});

// 文本长度验证 (A6) - 带错误信息
sheet.cell(5, 0).setValue("文本");
sheet.cell(5, 1).setValue("长度 5-10");
sheet.cell(5, 2).setTextLength({
    minLength: 5,
    maxLength: 10,
    errorTitle: "文本长度错误",
    errorText: "长度必须在5-10之间"
});

// 3. 获取所有验证
var validations = sheet.validations();

console.log("\n========== ExcelValidation 详细 API 测试 ==========");
console.log("验证总数:", validations.size());

// 4. 遍历每个验证,测试所有 API
validations.forEach(function(validation) {
    console.log("\n--- 验证 ---");

    // type()
    console.log("type():", validation.type());

    // operator() (对于 LIST 类型可能为 null)
    console.log("operator():", validation.operator());

    // formula1() / formula2() (通常为 null)
    console.log("formula1():", validation.formula1());
    console.log("formula2():", validation.formula2());

    // listValues() (仅 LIST 类型有值)
    var listVals = validation.listValues();
    console.log("listValues():", listVals ? JSON.stringify(listVals) : "null");

    // allowBlank()
    console.log("allowBlank():", validation.allowBlank());

    // showPrompt()
    console.log("showPrompt():", validation.showPrompt());

    // showError()
    console.log("showError():", validation.showError());

    // promptTitle() / promptText()
    console.log("promptTitle():", validation.promptTitle());
    console.log("promptText():", validation.promptText());

    // errorTitle() / errorText()
    console.log("errorTitle():", validation.errorTitle());
    console.log("errorText():", validation.errorText());

    // suppressDropDownArrow()
    console.log("suppressDropDownArrow():", validation.suppressDropDownArrow());

    // regions()
    var regions = validation.regions();
    console.log("regions():", JSON.stringify(regions));

    // explicitList()
    console.log("explicitList():", validation.explicitList());

    // poiDataValidation() (仅验证对象存在)
    var poiDV = validation.poiDataValidation();
    console.log("poiDataValidation():", poiDV ? "获取成功" : "获取失败");
});

// 5. 额外测试:单独获取一个验证并测试(例如第一个)
console.log("\n--- 单独测试第一个验证(下拉列表) ---");
var firstValidation = validations.get(0);
if (firstValidation) {
    console.log("type():", firstValidation.type());
    console.log("listValues():", JSON.stringify(firstValidation.listValues()));
    console.log("regions():", JSON.stringify(firstValidation.regions()));
    console.log("explicitList():", firstValidation.explicitList());
}

// 6. 空集合测试(新建空 Sheet)
var emptySheet = excel.createSheet("空Sheet");
var emptyValidations = emptySheet.validations();
console.log("\n--- 空集合测试 ---");
console.log("空集合 size():", emptyValidations.size());


// 保存最终文件
excel.save({ path: TEST_DIR + "validation_detail_final.xlsx" });
console.log("\n✅ 测试完成!");
console.log("📁 生成的文件:", TEST_DIR + "validation_detail_final.xlsx");
excel.close();

ExcelValidation.type()

  • 返回 {string}

获取验证类型("LIST""INTEGER""DECIMAL""DATE""TIME""TEXT_LENGTH""CUSTOM""ANY""UNKNOWN")。

js
console.log("类型:", validation.type());

ExcelValidation.operator()

  • 返回 {string} | null

获取运算符("BETWEEN""NOT_BETWEEN""EQUAL""NOT_EQUAL""GREATER_THAN""LESS_THAN""GREATER_OR_EQUAL""LESS_OR_EQUAL")。

js
console.log("运算符:", validation.operator());

ExcelValidation.formula1()

  • 返回 {string} | null

获取公式1。

js
console.log("公式1:", validation.formula1());

ExcelValidation.formula2()

  • 返回 {string} | null

获取公式2。

js
console.log("公式2:", validation.formula2());

ExcelValidation.listValues()

  • 返回 {Array<string> | null}

获取下拉列表内容(仅显式列表)。

js
var values = validation.listValues();
if (values) {
    console.log("下拉选项:", values);
}

ExcelValidation.allowBlank()

  • 返回 {boolean}

是否允许空值。

js
if (validation.allowBlank()) {
    console.log("允许空值");
}

ExcelValidation.showPrompt()

  • 返回 {boolean}

是否显示输入提示。

js
if (validation.showPrompt()) {
    console.log("显示输入提示");
}

ExcelValidation.showError()

  • 返回 {boolean}

是否显示错误提示。

js
if (validation.showError()) {
    console.log("显示错误提示");
}

ExcelValidation.promptTitle()

  • 返回 {string} | null

获取提示标题。

js
console.log("提示标题:", validation.promptTitle());

ExcelValidation.promptText()

  • 返回 {string} | null

获取提示内容。

js
console.log("提示内容:", validation.promptText());

ExcelValidation.errorTitle()

  • 返回 {string} | null

获取错误标题。

js
console.log("错误标题:", validation.errorTitle());

ExcelValidation.errorText()

  • 返回 {string} | null

获取错误内容。

js
console.log("错误内容:", validation.errorText());

ExcelValidation.suppressDropDownArrow()

  • 返回 {boolean}

是否隐藏下拉箭头。

js
if (validation.suppressDropDownArrow()) {
    console.log("隐藏下拉箭头");
}

ExcelValidation.regions()

  • 返回 {Array<string>}

获取验证区域列表。

js
var regions = validation.regions();
for (var i = 0; i < regions.length; i++) {
    console.log("区域:", regions[i]);
}

ExcelValidation.explicitList()

  • 返回 {boolean}

是否为显式下拉列表。

js
if (validation.explicitList()) {
    console.log("是显式下拉列表");
}

ExcelValidation.poiDataValidation()

获取底层 POI DataValidation 对象,用于执行 POI 原生的数据验证操作。

js
var poiValidation = validation.poiDataValidation();

ExcelValidationCollection

ExcelSheet.validations() 返回,代表数据验证的集合。

完整测试示例
js
// ==================== ExcelValidationCollection 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/ExcelValidationCollectionTest/

var TEST_DIR = "/sdcard/ExcelValidationCollectionTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({ path: TEST_DIR + "validation_test.xlsx" });
var sheet = excel.sheet("验证测试");
if (!sheet) sheet = excel.createSheet("验证测试");

// 1. 创建表头
sheet.createHeader(["字段", "规则", "示例值"]);

// 2. 添加多种数据验证到不同单元格
// 下拉列表 (A2)
sheet.cell(1, 0).setValue("性别");
sheet.cell(1, 1).setValue("下拉列表");
sheet.cell(1, 2).setDropdown(["男", "女", "其他"]);

// 整数验证 (A3)
sheet.cell(2, 0).setValue("年龄");
sheet.cell(2, 1).setValue("整数 18-60");
sheet.cell(2, 2).setInteger({ min: 18, max: 60 });

// 小数验证 (A4)
sheet.cell(3, 0).setValue("分数");
sheet.cell(3, 1).setValue("小数 0-100");
sheet.cell(3, 2).setDecimal({ min: 0.0, max: 100.0 });

// 日期验证 (A5)
sheet.cell(4, 0).setValue("日期");
sheet.cell(4, 1).setValue("日期范围");
sheet.cell(4, 2).setDate({ min: "2020-01-01", max: "2025-12-31" });

// 文本长度验证 (A6)
sheet.cell(5, 0).setValue("文本");
sheet.cell(5, 1).setValue("长度 5-10");
sheet.cell(5, 2).setTextLength({ minLength: 5, maxLength: 10 });

// 3. 获取验证集合
var validations = sheet.validations();

console.log("\n========== ExcelValidationCollection 测试 ==========");

// 4. 测试 size()
console.log("size():", validations.size());   // 预期 5

// 5. 测试 empty() 和 nonEmpty()
console.log("empty():", validations.empty());       // false
console.log("nonEmpty():", validations.nonEmpty()); // true

// 6. 测试 get(index)
console.log("\n--- get(index) ---");
for (var i = 0; i < validations.size(); i++) {
    var v = validations.get(i);
    console.log("get(" + i + ").type():", v.type());
    // regions() 返回 NativeArray,需要转为字符串
    console.log("  regions:", JSON.stringify(v.regions()));
    if (v.type() === "LIST") {
        // listValues() 返回 NativeArray
        console.log("  listValues:", JSON.stringify(v.listValues()));
    }
}

// 7. 测试 forEach(callback)
console.log("\n--- forEach 遍历 ---");
validations.forEach(function(validation) {
    console.log("类型:", validation.type(), "区域:", JSON.stringify(validation.regions()));
});

// 8. 测试 filter(filter)
console.log("\n--- filter ---");
var listValidations = validations.filter(function(validation) {
    return validation.type() === "LIST";
});
console.log("过滤后 (LIST) size:", listValidations.size());
// 使用 size/get 遍历过滤结果
console.log("过滤后验证(使用 size/get 遍历):");
for (var i = 0; i < listValidations.size(); i++) {
    var v = listValidations.get(i);
    console.log("  验证 " + i + ": 类型=" + v.type() + ", 区域=" + JSON.stringify(v.regions()));
    if (v.type() === "LIST") {
        console.log("    listValues:", JSON.stringify(v.listValues()));
    }
}

// 9. 测试 find(filter)
console.log("\n--- find ---");
var found = validations.find(function(validation) {
    return validation.type() === "INTEGER";
});
console.log("找到的第一个 INTEGER 验证:", found ? "类型=" + found.type() + ", 区域=" + JSON.stringify(found.regions()) : "未找到");

// 10. 额外:空集合测试(新建一个空 Sheet)
var emptySheet = excel.createSheet("空Sheet");
var emptyValidations = emptySheet.validations();
console.log("\n--- 空集合测试 ---");
console.log("空集合 size():", emptyValidations.size());
console.log("空集合 empty():", emptyValidations.empty());
console.log("空集合 nonEmpty():", emptyValidations.nonEmpty());
console.log("空集合 get(0):", emptyValidations.get(0));  // 应返回 null

// 保存最终文件
excel.save({ path: TEST_DIR + "validation_final.xlsx" });
console.log("\n✅ 测试完成!");
console.log("📁 生成的文件:", TEST_DIR + "validation_final.xlsx");
excel.close();

ExcelValidationCollection.size()

  • 返回 {number}

获取数据验证数量。

js
console.log("数据验证数:", validations.size());

ExcelValidationCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (validations.empty()) {
    console.log("没有数据验证");
}

ExcelValidationCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (validations.nonEmpty()) {
    console.log("有数据验证");
}

ExcelValidationCollection.get(index)

获取指定索引的数据验证。

js
var validation = validations.get(0);

ExcelValidationCollection.forEach(callback)

遍历所有数据验证。

js
validations.forEach(function(validation) {
    console.log("类型:", validation.type());
    console.log("区域:", validation.regions());
});

ExcelValidationCollection.filter(filter)

过滤数据验证。

js
var listValidations = validations.filter(function(validation) {
    return validation.type() === "LIST";
});

ExcelValidationCollection.find(filter)

  • filter {Function} 过滤函数 (validation) => boolean
  • 返回 {ExcelValidation} | null

查找第一个匹配的数据验证。

js
var found = validations.find(function(validation) {
    return validation.type() === "INTEGER";
});

ExcelComment

ExcelCell.comment() 等方法返回,代表一个批注。

ExcelComment.text()

  • 返回 {string}

获取批注文本。

js
console.log("批注:", comment.text());

ExcelComment.author()

  • 返回 {string}

获取批注作者。

js
console.log("作者:", comment.author());

ExcelComment.poiComment()

获取底层 POI Comment 对象,用于执行 POI 原生的批注操作(如设置作者、修改文本内容等)。

js
var poiComment = comment.poiComment();

ExcelCommentCollection

ExcelRange.comments() 等方法返回,代表批注的集合。

ExcelCommentCollection.size()

  • 返回 {number}

获取批注数量。

js
console.log("批注数:", comments.size());

ExcelCommentCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (comments.empty()) {
    console.log("没有批注");
}

ExcelCommentCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (comments.nonEmpty()) {
    console.log("有批注");
}

ExcelCommentCollection.get(index)

获取指定索引的批注。

js
var comment = comments.get(0);
console.log(comment.text());

ExcelCommentCollection.forEach(callback)

遍历所有批注。

js
comments.forEach(function(comment) {
    console.log("批注:", comment.text(), "作者:", comment.author());
});

// 使用 size() + get() 遍历
for (var i = 0; i < comments.size(); i++) {
    var c = comments.get(i);
    console.log(c.text());
}

ExcelCommentCollection.filter(filter)

过滤批注。

js
var authorComments = comments.filter(function(comment) {
    return comment.author() === "张三";
});

ExcelCommentCollection.find(filter)

  • filter {Function} 过滤函数 (comment) => boolean
  • 返回 {ExcelComment | null}

查找第一个匹配的批注。

js
var found = comments.find(function(comment) {
    return comment.text().indexOf("重要") !== -1;
});

ExcelCell.hyperlink() 等方法返回,代表一个超链接。

  • 返回 {string}

获取超链接地址。

js
console.log("地址:", link.address());
  • 返回 {string} | null

获取超链接标签(显示文本)。

js
console.log("标签:", link.label());
  • 返回 {string}

获取超链接类型("URL""EMAIL""FILE" 等)。

js
console.log("类型:", link.type());

获取底层 POI Hyperlink 对象,用于执行 POI 原生的超链接操作(如修改地址、类型等)。

js
var poiLink = link.poiHyperlink();

ExcelHyperlinkCollection

ExcelRange.hyperlinks() 等方法返回,代表超链接的集合。

ExcelHyperlinkCollection.size()

  • 返回 {number}

获取超链接数量。

js
console.log("超链接数:", links.size());

ExcelHyperlinkCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (links.empty()) {
    console.log("没有超链接");
}

ExcelHyperlinkCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (links.nonEmpty()) {
    console.log("有超链接");
}

ExcelHyperlinkCollection.get(index)

获取指定索引的超链接。

js
var link = links.get(0);
console.log(link.address());

ExcelHyperlinkCollection.forEach(callback)

遍历所有超链接。

js
links.forEach(function(link) {
    console.log("地址:", link.address(), "类型:", link.type());
});

// 使用 size() + get() 遍历
for (var i = 0; i < links.size(); i++) {
    var link = links.get(i);
    console.log(link.address());
}

ExcelHyperlinkCollection.filter(filter)

过滤超链接。

js
var urlLinks = links.filter(function(link) {
    return link.type() === "URL";
});

ExcelHyperlinkCollection.find(filter)

  • filter {Function} 过滤函数 (link) => boolean
  • 返回 {ExcelHyperlink | null}

查找第一个匹配的超链接。

js
var found = links.find(function(link) {
    return link.address() === "https://example.com";
});

CellRangeAddressCollection

ExcelSheet.mergedRegions() 返回,代表合并区域地址的集合。

完整测试示例
js
// ==================== CellRangeAddressCollection 完整 API 测试 ====================
// 运行前请确保存储权限已开启,测试文件将生成在 /sdcard/CellRangeAddressCollectionTest/

var TEST_DIR = "/sdcard/CellRangeAddressCollectionTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({ path: TEST_DIR + "merged_regions_test.xlsx" });
var sheet = excel.sheet("测试");
if (!sheet) sheet = excel.createSheet("测试");

// 1. 创建一些合并区域(不同位置)
sheet.range("A1:B2").merge();     // 区域1
sheet.range("D4:E6").merge();     // 区域2
sheet.range("G7:H8").merge();     // 区域3
sheet.range("J10:K11").merge();   // 区域4

// 2. 获取合并区域集合
var merged = sheet.mergedRegions();

console.log("\n========== CellRangeAddressCollection 测试 ==========");

// 3. 测试 size()
console.log("size():", merged.size());   // 预期 4

// 4. 测试 empty() 和 nonEmpty()
console.log("empty():", merged.empty());       // false
console.log("nonEmpty():", merged.nonEmpty()); // true

// 5. 测试 get(index)
console.log("\n--- get(index) ---");
for (var i = 0; i < merged.size(); i++) {
    var range = merged.get(i);
    console.log("get(" + i + ").formatAsString():", range.formatAsString());
}

// 6. 测试 forEach(callback)(不带索引)
console.log("\n--- forEach 遍历 ---");
merged.forEach(function(range) {
    console.log("合并区域:", range.formatAsString());
});

// 7. 测试 filter(filter)
console.log("\n--- filter ---");
// 过滤出起始行 > 3 的区域(即区域2、区域3、区域4)
var filtered = merged.filter(function(range) {
    return range.getFirstRow() > 3;
});
console.log("过滤后 size:", filtered.size());
// 使用 size() + get() 遍历过滤结果
console.log("过滤后区域(使用 size/get 遍历):");
for (var i = 0; i < filtered.size(); i++) {
    var r = filtered.get(i);
    console.log("  区域 " + i + ":", r.formatAsString());
}

// 8. 测试 find(filter)
console.log("\n--- find ---");
var found = merged.find(function(range) {
    // 查找第一个起始列 > 5 的区域(即区域3、区域4)
    return range.getFirstColumn() > 5;
});
console.log("找到的区域(第一个起始列>5):", found ? found.formatAsString() : "未找到");

// 9. 额外:测试空集合的情况(新建一个空 Sheet)
var emptySheet = excel.createSheet("空Sheet");
var emptyMerged = emptySheet.mergedRegions();
console.log("\n--- 空集合测试 ---");
console.log("空集合 size():", emptyMerged.size());
console.log("空集合 empty():", emptyMerged.empty());
console.log("空集合 nonEmpty():", emptyMerged.nonEmpty());


// 保存最终文件
excel.save({ path: TEST_DIR + "merged_final.xlsx" });
console.log("\n✅ 测试完成!");
console.log("📁 生成的文件:", TEST_DIR + "merged_final.xlsx");
excel.close();

CellRangeAddressCollection.size()

  • 返回 {number}

获取合并区域数量。

js
console.log("合并区域数:", merged.size());

CellRangeAddressCollection.empty()

  • 返回 {boolean}

判断是否为空。

js
if (merged.empty()) {
    console.log("没有合并区域");
}

CellRangeAddressCollection.nonEmpty()

  • 返回 {boolean}

判断是否非空。

js
if (merged.nonEmpty()) {
    console.log("有合并区域");
}

CellRangeAddressCollection.get(index)

获取指定索引的合并区域。

js
var range = merged.get(0);
console.log(range.formatAsString());

CellRangeAddressCollection.forEach(consumer)

遍历所有合并区域。

js
merged.forEach(function(range) {
    console.log(range.formatAsString());
});

CellRangeAddressCollection.filter(filter)

过滤合并区域。

js
var filtered = merged.filter(function(range) {
    return range.getFirstRow() > 5;
});

CellRangeAddressCollection.find(filter)

  • filter {Function} 过滤函数 (range) => boolean
  • 返回 {CellRangeAddress} | null

查找第一个匹配的合并区域。

js
var found = merged.find(function(range) {
    return range.formatAsString() === "A1:B2";
});

实战示例

所有示例文件均保存在 /sdcard/ExcelTest/ 目录,运行前会自动创建该目录。

创建并写入数据

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "data.xlsx"
});

var sheet = excel.sheet("数据表");
if (!sheet) {
    sheet = excel.createSheet("数据表");
}

sheet.createHeader(["姓名", "年龄", "分数", "是否通过"]);

sheet.insert({
    姓名: "张三",
    年龄: 25,
    分数: 90,
    是否通过: true
});

sheet.insert([
    { 姓名: "李四", 年龄: 30, 分数: 85, 是否通过: false },
    { 姓名: "王五", 年龄: 22, 分数: 76, 是否通过: true }
]);

excel.save();
excel.close();

查询和更新数据

js

var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "data.xlsx"
});

var sheet = excel.sheet("数据表");
if (!sheet) {
    console.log("❌ 数据表不存在");
    excel.close();
    exit();
}

// 如果文件已存在且包含表头行(之前已创建),需要设置表头行索引
// 否则查询条件中的列名(如"姓名")会被视为列字母,导致查询错误
sheet.setHeaderRow(0);

var cursor = sheet.query();
console.log("总行数:", cursor.count());

var cursor2 = sheet.query("年龄 > 20");
console.log("年龄 > 20 的行数:", cursor2.count());

var cursor3 = sheet.query("年龄 >= 20");
while (cursor3.moveToNext()) {
    var row = cursor3.pick();
    console.log(row.姓名, row.年龄, row.分数);
}
cursor3.close();

var updated = sheet.query("姓名 = '张三'").update({ 分数: 95 });
console.log("更新了", updated, "行");

var deleted = sheet.query("分数 < 60").delete();
console.log("删除了", deleted, "行");

var range = sheet.query("年龄 > 20").range();
console.log("匹配行范围:", range.address());

excel.save();
excel.close();

行操作

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "row_ops.xlsx"
});

var sheet = excel.sheet("行操作");
if (!sheet) {
    sheet = excel.createSheet("行操作");
}

sheet.write([
    ["姓名", "年龄", "分数"],
    ["张三", 25, 90],
    ["李四", 30, 85],
    ["王五", 22, 76]
]);

var row = sheet.row(1);
console.log("行索引:", row.index());
console.log("单元格数:", row.cellCount());
console.log("行高:", row.height());

row.setHeight(30);

row.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00")
    .border("thin");

var newRow = sheet.row(10);
row.copy(newRow);
console.log("复制后新行值:", JSON.stringify(newRow.value()));

var pasteRow = sheet.row(12);
row.paste(pasteRow);
console.log("粘贴后值:", JSON.stringify(pasteRow.value()));

row.setHidden(true);
console.log("行是否隐藏:", row.hidden());

var insertRow = sheet.row(4);
insertRow.setValue(["插入", "测试", "行"]);
insertRow.insert();

var delRow = sheet.row(6);
delRow.remove();

excel.save();
excel.close();

单元格操作

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "cell_ops.xlsx"
});

var sheet = excel.sheet("单元格操作");
if (!sheet) {
    sheet = excel.createSheet("单元格操作");
}

sheet.range("A1:E5").setValue([
    ["姓名", "年龄", "分数", "日期", "是否通过"],
    ["张三", 25, 90.5, "2024-01-15", true],
    ["李四", 30, 85.0, "2024-02-20", false],
    ["王五", 22, 76.5, "2024-03-10", true],
    ["赵六", 28, 92.0, "2024-04-05", false]
]);

var cell = sheet.cell("B2");
console.log("值:", cell.value());
console.log("类型:", cell.type());

if (cell.type() === "NUMERIC") {
    console.log("数字:", cell.number());
}

cell.style()
    .bold(true)
    .fontColor("#FF0000")
    .background("#FFFF00")
    .border("thin");

var formulaCell = sheet.cell("C6");
formulaCell.setFormula("SUM(C2:C5)");
console.log("公式:", formulaCell.formula());
console.log("计算结果:", formulaCell.calculatedValue());

var linkCell = sheet.cell("A10");
linkCell.setValue("点击访问");
linkCell.setHyperlink("https://example.com");
console.log("是否有超链接:", linkCell.hasHyperlink());

var link = linkCell.hyperlink();
if (link) {
    console.log("地址:", link.address());
    console.log("类型:", link.type());
}

var commentCell = sheet.cell("B10");
commentCell.setValue("批注单元格");

// 先移除已有批注(如果存在)
if (commentCell.hasComment()) {
    commentCell.removeComment();
}
// 再设置新批注
commentCell.setComment("这是批注内容", "这是作者");

console.log("是否有批注:", commentCell.hasComment());
var comment = commentCell.comment();
if (comment) {
    console.log("批注内容:", comment.text());
    console.log("作者:", comment.author());
}




var dropCell = sheet.cell("C10");
dropCell.setValue("性别");
dropCell.setDropdown(["男", "女", "其他"]);

var intCell = sheet.cell("D10");
intCell.setInteger({
    min: 18,
    max: 60,
    promptTitle: "年龄输入",
    promptText: "请输入18-60之间的整数",
    errorTitle: "错误",
    errorText: "年龄必须在18-60之间"
});

var imgPath = "/sdcard/logo.png";
if (files.isFile(imgPath)) {
    sheet.cell("E10").setImage(imgPath)
        .size(200, 150)
        .insert();
}

excel.save();
excel.close();

样式和格式设置

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "style.xlsx"
});

var sheet = excel.sheet("样式测试");
if (!sheet) {
    sheet = excel.createSheet("样式测试");
}

sheet.write([
    ["产品", "销量", "单价", "总价"],
    ["A", 100, 10.5, 1050],
    ["B", 200, 8.2, 1640],
    ["C", 150, 12.0, 1800]
]);

sheet.range("A1:D1").style()
    .bold(true)
    .fontColor("#FFFFFF")
    .background("#4472C4")
    .align("center")
    .border("thin");

sheet.range("A2:D4").style()
    .border("thin")
    .align("center")
    .vertical("middle");

sheet.range("B2:B4").style().format("#,##0");
sheet.range("C2:C4").style().format("0.00");
sheet.range("D2:D4").style().format("#,##0.00");

sheet.cell("E2").setValue(0.85);
sheet.cell("E2").style().format("0.00%");

sheet.cell("E3").setValue("2024-01-15");
sheet.cell("E3").style().format("yyyy年MM月dd日");

sheet.setColumnWidth(0, 12);
sheet.setColumnWidth(1, 15);
sheet.setColumnWidth(2, 12);
sheet.setColumnWidth(3, 15);

excel.save();
excel.close();

数据验证

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "validation.xlsx"
});

var sheet = excel.sheet("验证测试");
if (!sheet) {
    sheet = excel.createSheet("验证测试");
}

sheet.createHeader(["字段", "验证规则", "示例值"]);

sheet.cell(1, 0).setValue("性别");
sheet.cell(1, 2).setDropdown(["男", "女", "其他"]);

sheet.cell(2, 0).setValue("年龄");
sheet.cell(2, 2).setInteger({
    min: 18,
    max: 60,
    promptTitle: "年龄输入",
    promptText: "请输入18-60之间的整数",
    errorTitle: "错误",
    errorText: "年龄必须在18-60之间"
});

sheet.cell(3, 0).setValue("分数");
sheet.cell(3, 2).setDecimal({
    min: 0.0,
    max: 100.0,
    promptTitle: "分数输入",
    promptText: "请输入0-100之间的数值",
    errorTitle: "错误",
    errorText: "分数必须在0-100之间"
});

sheet.cell(4, 0).setValue("日期");
sheet.cell(4, 2).setDate({
    min: "2020-01-01",
    max: "2025-12-31",
    promptTitle: "日期输入",
    promptText: "请输入2020-2025之间的日期",
    errorTitle: "错误",
    errorText: "日期超出范围"
});

sheet.cell(5, 0).setValue("时间");
sheet.cell(5, 2).setTime({
    min: "08:00",
    max: "18:00",
    promptTitle: "时间输入",
    promptText: "请输入08:00到18:00之间的时间",
    errorTitle: "错误",
    errorText: "时间超出范围"
});

sheet.cell(6, 0).setValue("文本");
sheet.cell(6, 2).setTextLength({
    minLength: 5,
    maxLength: 10,
    promptTitle: "文本输入",
    promptText: "请输入5-10个字符",
    errorTitle: "错误",
    errorText: "长度必须在5-10之间"
});

var validations = sheet.validations();
console.log("验证数量:", validations.size());
validations.forEach(function(v) {
    console.log("类型:", v.type(), "区域:", JSON.stringify(v.regions()));
});

excel.save();
excel.close();

合并单元格和边框

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "merge.xlsx"
});

var sheet = excel.sheet("合并测试");
if (!sheet) {
    sheet = excel.createSheet("合并测试");
}

sheet.write([
    ["季度", "Q1", "Q2", "Q3", "Q4"],
    ["2023", 100, 120, 110, 130],
    ["2024", 140, 150, 135, 160]
]);

sheet.range("A1:E1").style()
    .bold(true)
    .background("#4472C4")
    .fontColor("#FFFFFF")
    .align("center");

sheet.range("A2:A3").merge();
sheet.range("A2:A3").style()
    .bold(true)
    .align("center")
    .vertical("middle")
    .border("thin");

sheet.range("B2:E3").style()
    .border("thin")
    .align("center");

sheet.cell(4, 0).setValue("合计");
sheet.cell(4, 1).setFormula("SUM(B2:B3)");
sheet.cell(4, 2).setFormula("SUM(C2:C3)");
sheet.cell(4, 3).setFormula("SUM(D2:D3)");
sheet.cell(4, 4).setFormula("SUM(E2:E3)");

sheet.range("A4:E4").style()
    .bold(true)
    .background("#E6E6E6")
    .border("thin");

sheet.range("A4:A4").isMerged();

if (sheet.range("A1:B1").hasMergedOverlap()) {
    sheet.range("A1:B1").clearMergedOverlap();
}

var merged = sheet.mergedRegions();
console.log("合并区域数:", merged.size());
merged.forEach(function(range) {
    console.log("合并区域:", range.formatAsString());
});

excel.save();
excel.close();

插入图片

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "image.xlsx"
});

var sheet = excel.sheet("图片测试");
if (!sheet) {
    sheet = excel.createSheet("图片测试");
}

sheet.write([
    ["图片示例", "说明"],
    ["Logo", "插入到 A1 单元格"],
    ["图表", "插入到 C5 单元格"]
]);

var imgPath1 = "/sdcard/logo.png";
if (files.isFile(imgPath1)) {
    sheet.image(imgPath1)
        .at("A1")
        .size(200, 150)
        .insert();
    console.log("✅ Logo 已插入到 A1");
}

var imgPath2 = "/sdcard/chart.png";
if (files.isFile(imgPath2)) {
    sheet.image(imgPath2)
        .at(4, 2)
        .width(300)
        .height(200)
        .insert();
    console.log("✅ 图表已插入到 E5");
}

var imgPath3 = "/sdcard/photo.jpg";
if (files.isFile(imgPath3)) {
    var cell = sheet.cell("A10");
    cell.setValue("照片");
    cell.setImage(imgPath3)
        .size(150, 150)
        .insert();
    console.log("✅ 照片已插入到 A10");
}

var imgPath4 = "/sdcard/icon.png";
if (files.isFile(imgPath4)) {
    var bytes = files.readBytes(imgPath4);
    sheet.image(bytes)
        .at("G1")
        .size(100, 100)
        .insert();
    console.log("✅ 图标已插入到 G1");
}

excel.save();
excel.close();

筛选、冻结和保护

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "ffp.xlsx"
});

var sheet = excel.sheet("FFP测试");
if (!sheet) {
    sheet = excel.createSheet("FFP测试");
}

sheet.write([
    ["姓名", "部门", "工资", "入职日期"],
    ["张三", "技术部", 8000, "2023-01-15"],
    ["李四", "市场部", 9000, "2022-11-01"],
    ["王五", "技术部", 7500, "2023-03-20"],
    ["赵六", "财务部", 8500, "2022-08-10"],
    ["孙七", "市场部", 7800, "2023-02-28"],
    ["周八", "技术部", 8200, "2023-04-05"],
    ["吴九", "财务部", 8800, "2022-12-11"]
]);

sheet.range("A1:D1").style()
    .bold(true)
    .background("#4472C4")
    .fontColor("#FFFFFF")
    .align("center");

sheet.autoFilter("A1:D9");
console.log("是否有自动筛选:", sheet.hasAutoFilter());
console.log("筛选范围:", sheet.autoFilterRange());

sheet.freeze(1, 0);
console.log("已冻结首行");

sheet.setProtect("password123");
console.log("工作表已保护");

sheet.setProtect();
console.log("已取消保护");

sheet.range("A2:D9").sort(2, false);
console.log("已按工资降序排序");

sheet.unfreeze();
console.log("已取消冻结");

excel.save();
excel.close();

完整工作流(综合示例)

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "workflow.xlsx"
}, {
    onOpen: function(excel) {
        console.log("✅ 文件打开成功");
    }
});

try {
    var sheet = excel.sheet("工作流");
    if (!sheet) {
        sheet = excel.createSheet("工作流");
        console.log("✅ 创建新 Sheet: 工作流");
    }

    sheet.createHeader(["ID", "任务", "负责人", "状态", "完成日期"]);
    console.log("✅ 表头已创建");

    sheet.insert([
        { ID: 1, 任务: "需求分析", 负责人: "张三", 状态: "已完成", 完成日期: "2024-01-15" },
        { ID: 2, 任务: "系统设计", 负责人: "李四", 状态: "进行中", 完成日期: "" },
        { ID: 3, 任务: "编码实现", 负责人: "王五", 状态: "未开始", 完成日期: "" },
        { ID: 4, 任务: "测试验证", 负责人: "赵六", 状态: "未开始", 完成日期: "" }
    ]);
    console.log("✅ 数据已插入");

    sheet.range("A1:E1").style()
        .bold(true)
        .background("#4472C4")
        .fontColor("#FFFFFF")
        .align("center")
        .border("thin");
    console.log("✅ 表头样式已应用");

    for (var i = 2; i <= 5; i++) {
        sheet.cell(i, 3).setDropdown(["未开始", "进行中", "已完成", "已取消"]);
    }
    console.log("✅ 状态列数据验证已添加");

    var cursor = sheet.query("状态 = '已完成'");
    cursor.ranges().style()
        .background("#C6EFCE")
        .fontColor("#006100");

    var cursor2 = sheet.query("状态 = '进行中'");
    cursor2.ranges().style()
        .background("#FFEB9C")
        .fontColor("#9C6500");

    var cursor3 = sheet.query("状态 = '未开始'");
    cursor3.ranges().style()
        .background("#FFC7CE")
        .fontColor("#9C0006");
    console.log("✅ 状态颜色已应用");

    sheet.autoFilter("A1:E5");
    console.log("✅ 自动筛选已设置");

    sheet.freeze(1, 0);
    console.log("✅ 表头已冻结");


    var cursor4 = sheet.query();
    console.log("📊 总记录数:", cursor4.count());

    excel.save();
    console.log("💾 保存成功");

} catch (e) {
    console.log("❌ 错误:", e);
} finally {
    excel.close();
    console.log("🔒 文件已关闭");
}

范围(Range)高级操作

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "range_advanced.xlsx"
});

var sheet = excel.sheet("Range高级");
if (!sheet) {
    sheet = excel.createSheet("Range高级");
}

sheet.write([
    ["产品", "销量", "单价", "总价"],
    ["A", 100, 10.5, 1050],
    ["B", 200, 8.2, 1640],
    ["C", 150, 12.0, 1800]
]);

var range = sheet.range("A1:D4");
console.log("范围地址:", range.address());
console.log("行数:", range.height());
console.log("列数:", range.width());
console.log("单元格数:", range.cellCount());
console.log("是否为空:", range.empty());

var singleCell = sheet.range("B2");
console.log("B2 值:", singleCell.value());

sheet.range("F1:G3").setValue([
    ["复制", "区域"],
    ["A", "B"],
    ["C", "D"]
]);

var src = sheet.range("A1:D1");
var tgt = sheet.range("A6:D6");
src.copy(tgt);
console.log("复制后目标值:", JSON.stringify(tgt.value()));

var pasteVal = sheet.range("A8:D8");
src.paste(pasteVal);
console.log("粘贴值后:", JSON.stringify(pasteVal.value()));

var srcStyle = sheet.range("A1:D1").style().bold(true).background("#4472C4");
var tgtStyle = sheet.range("A10:D10");
src.pasteStyle(tgtStyle);
console.log("样式已粘贴");

var r6 = sheet.range("A12:B13");
console.log("原始地址:", r6.address());
var offset = r6.offset(2, 3);
console.log("偏移后地址:", offset.address());
var resize = r6.resize(4, 5);
console.log("调整大小后地址:", resize.address());

var r7a = sheet.range("A15:C18");
var r7b = sheet.range("B16:D19");
var inter = r7a.intersect(r7b);
console.log("交集:", inter ? inter.address() : "无交集");
var union = r7a.union(r7b);
console.log("并集:", union ? union.address() : "不相邻");

var r8 = sheet.range("A20:C22");
r8.setValue([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]);
// 遍历每个单元格
for (var r = 0; r < r8.height(); r++) {
    for (var c = 0; c < r8.width(); c++) {
        var cell = r8.cell(r, c);
        console.log("单元格[" + r + "][" + c + "]值:", cell.value());
    }
}
excel.save();
excel.close();

查询结果范围(Cursor Ranges)

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "cursor_ranges.xlsx"
});

var sheet = excel.sheet("查询范围");
if (!sheet) {
    sheet = excel.createSheet("查询范围");
}

sheet.createHeader(["姓名", "年龄", "班级"]);
sheet.insert([
    { 姓名: "张三", 年龄: 18, 班级: "一班" },
    { 姓名: "李四", 年龄: 19, 班级: "二班" },
    { 姓名: "王五", 年龄: 20, 班级: "一班" },
    { 姓名: "赵六", 年龄: 21, 班级: "三班" },
    { 姓名: "孙七", 年龄: 22, 班级: "二班" },
    { 姓名: "周八", 年龄: 23, 班级: "三班" }
]);

var cursor = sheet.query("班级 = '一班' OR 班级 = '三班'");

var ranges = cursor.ranges();
console.log("连续块数量:", ranges.size());
ranges.forEach(function(range) {
    console.log("块地址:", range.address());
});

ranges.style()
    .bold(true)
    .background("#FFFF00")
    .border("thin");

var singleRange = cursor.range();
console.log("合并范围地址:", singleRange.address());

singleRange.style()
    .fontColor("#FF0000")
    .border("medium");

var rows = cursor.rows();
console.log("匹配行数:", rows.size());
rows.forEach(function(row) {
    console.log("行索引:", row.index());
});

var indexes = cursor.rowIndexes();
console.log("行索引:", JSON.stringify(indexes));

console.log("使用 size/get 遍历:");
for (var i = 0; i < ranges.size(); i++) {
    var r = ranges.get(i);
    console.log("块", i, ":", r.address());
}

excel.save();
excel.close();

无表头模式操作

js
var TEST_DIR = "/sdcard/ExcelTest/";
files.ensureDir(TEST_DIR);

var excel = $excel.open({
    path: TEST_DIR + "noheader.xlsx"
});

var sheet = excel.sheet("无表头测试");
if (!sheet) {
    sheet = excel.createSheet("无表头测试");
}

sheet.noHeader();

sheet.insert({ "A": "姓名", "B": "年龄", "C": "分数" });
sheet.insert([
    { "A": "张三", "B": 18, "C": 90 },
    { "A": "李四", "B": 19, "C": 85 },
    { "A": "王五", "B": 20, "C": 78 }
]);

var cursor = sheet.query();
console.log("所有数据:", JSON.stringify(cursor.all(), null, 2));

var cursor2 = sheet.query("B > 18");
console.log("年龄 > 18 的数据:", JSON.stringify(cursor2.all(), null, 2));

var updated = sheet.query("A = '张三'").update({ "C": 95 });
console.log("更新了", updated, "行");

var columns = sheet.columns();
console.log("列名:", JSON.stringify(columns));

sheet.write([
    ["产品", "价格"],
    ["苹果", 5.5],
    ["香蕉", 3.2]
]);

excel.save();
excel.close();