更新记录

1.0.4(2026-08-31) 下载此版本

更新示例

1.0.3(2026-08-31) 下载此版本

更新说明

1.0.2(2026-08-25) 下载此版本

更新文档

查看更多

平台兼容性

uni-app(3.8.3)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ √ - - √ - √ - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

lu-export-excel

文档说明

本插件基于 xlsx-js-style 在 UniApp 的 renderjs 层 实现 APP 端 Excel 文件导出,支持自定义文件名、表头、列宽与表头样式。 插件内部仅暴露 renderjs 模块(js_sdk/exportExcelRender.js + js_sdk/tool.js),并内置一个 easycom 组件 lu-excel-export(components/lu-excel-export/lu-excel-export.vue),封装好 prop 同步、trigger 翻转、结果回调与内置结果弹窗,业务侧无需手动 import,直接 <lu-excel-export /> 即可使用。

功能列表

  1. APP 端导出 Excel 文件,已在 UniApp + Vue2/3 环境测试通过;
  2. 自适应列宽(中英文混排按显示宽度估算);
  3. 表头样式可自定义(默认加粗,基于 xlsx-js-style);
  4. 文件保存到 plus.io.PUBLIC_DOWNLOADS 公共下载目录;
  5. 通过 trigger 字段翻转触发导出,避免 prop 初始同步误触发;
  6. 内置重复同步守卫,避免 uni-app prop 重复同步导致重复导出;
  7. 公共组件自带结果弹窗(自绘全屏遮罩 + 居中卡片),并通过 @result 事件回传完整结果。

目录

  1. 功能列表
  2. 安装依赖
  3. 核心参数说明
  4. 导出结果回调
  5. 使用方式
  6. 工具函数说明
  7. 关键注意事项
  8. 常见问题排查

一、安装依赖

本插件依赖 xlsx-js-style(带样式的 xlsx 库):

# 使用 pnpm 安装依赖
pnpm add xlsx-js-style

# 或者使用 npm 安装依赖
npm install xlsx-js-style

# 或者使用 yarn 安装依赖
yarn add xlsx-js-style

二、核心参数说明

renderjs 内部使用一个 excelData 对象承载所有导出配置,字段如下(均有默认值,按需覆盖):

参数 类型 默认值 说明
fileName String "export" 基础文件名,最终文件名默认拼接时间戳(格式:_YYYYMMDD_HHMMSS),非法字符会被替换为 _
sheetName String "Sheet1" Excel 工作表名称
hideNameTime Boolean false 是否隐藏文件名后的时间戳;隐藏后重名文件会被直接覆盖
hideHeader Boolean false 是否隐藏表头行(隐藏后仅显示数据行,且不应用表头样式)
headerList Array<String> [] 自定义表头文本;若为空,则自动取 Object.keys(list[0]) 作为表头
list Array<Object> [] 导出的核心数据行,数组内为键值对对象格式;为空时 renderjs 端会忽略本次导出
trigger Number/String - 触发位:业务侧每次 +1(或换成任意新值)即可驱动 renderjs 自动调用 startExport

三、导出结果回调

renderjs 通过 ownerInstance.callMethod("handleExportExcel", res) 把结果回传到 Vue 层。

res 结构

字段 类型 说明
success Boolean 导出是否成功,true 表示成功,false 表示失败
message String 导出结果描述,如 "导出成功" / "导出失败"
filePath String 导出文件的本地路径(仅成功时有值)
fileName String 导出文件的名称(含 .xlsx 后缀,仅成功时有值)
data Any 导出失败时的错误对象(仅失败时有值)

四、使用方式

方式一:easycom 组件(推荐)

插件内置组件 lu-excel-export,已通过 package.json 的 easycom 自动注册,业务页面无需手动 import,模板里直接写即可:

<template>
    <lu-excel-export ref="excelExportRef" @result="onExportResult" />
</template>

组件位置:uni_modules/lu-export-excel/components/lu-excel-export/lu-excel-export.vue,封装好了 :prop 同步、trigger 翻转、结果回调与内置结果弹窗(自绘全屏遮罩 + 居中卡片),业务侧无需手动写 renderjs。

弹窗实现说明:组件不再使用 useModal() 全局函数式弹窗(多次弹出时存在 tempConfig 残留问题,导致第二次弹窗内容空白),改用组件内置的自绘遮罩 + 卡片;同时始终 emit('result', e),父组件可监听做自定义处理。

模板:

<template>
    <lu-excel-export ref="excelExportRef" @result="onExportResult" />
</template>

默认行为(不开 silent):

  • 成功:弹出组件内置遮罩卡片,分段显示「文件已保存到:<filePath>」+「文件名:<fileName>」,确认按钮文字为「确定」;
  • 失败:弹出「<errorTitle>」标题 + 「<errorPrefix><错误信息>」内容;
  • 同时始终 emit('result', e),父组件可监听做自定义处理(例如关闭 loading)。

Props:

Prop 类型 默认值 说明
silent Boolean false 设为 true 时关闭组件内置弹窗,仅 emit result
successTitle String "导出成功" 成功弹窗标题
errorTitle String "导出失败" 失败弹窗标题
errorPrefix String "导出失败:" 失败提示前缀(例如「导出失败:xxx」)

注:successTemplate 模板字符串参数已在当前版本移除。成功结果固定按「文件已保存到:<filePath>」+「文件名:<fileName>」两段渲染,便于长路径/长文件名正确换行显示。

Events:

事件名 参数 说明
result (e: ExportResult) renderjs 回调结果,始终触发

方法(通过 ref 调用):

方法 参数 说明
trigger (payload: object) 触发一次导出

trigger(payload) 的 payload 字段:

字段 类型 默认值 说明
fileName String "export" 导出文件名(不含后缀)
sheetName String "Sheet1" 工作表名
headerList Array<String> [] 表头数组
list Array<Object> [] 行数据
hideNameTime Boolean false 是否隐藏文件名时间戳
hideHeader Boolean false 是否隐藏表头

完整示例:

<template>
    <view class="page">
        <lu-excel-export ref="excelExportRef" @result="onExportResult" />
        <view class="action-item" @click="handleExport(item)">导出</view>
    </view>
</template>

<script setup>
import { ref } from "vue";

const excelExportRef = ref();

const handleExport = (item) => {
    const headerList = ["姓名", "年龄", "出生日期", "随机", "备注"];
    const list = new Array(1000).fill().map((_, i) => ({
        name: `用户${i}`,
        age: Math.floor(Math.random() * 30) + 20,
        birthday: `user${i}@example.com`,
        rod: `2025-01-01 23:22:44`,
        remark: Date.now() + Math.random().toString(36),
    }));

    // 触发导出:组件内部会把 trigger 自增,renderjs 监听到翻转后自动调用 startExport
    excelExportRef.value?.trigger({
        fileName: item?.billName || "盘点详情",
        sheetName: "Sheet1",
        hideNameTime: false,
        hideHeader: false,
        headerList,
        list,
    });
};

const onExportResult = (e) => {
    // 业务侧可选:关闭 loading、刷新列表等
    uni.hideLoading();
    console.log("导出结果:", e);
};
</script>

静默模式(自定义提示):

<lu-excel-export ref="excelExportRef" :silent="true" @result="onExportResult" />
const onExportResult = (e) => {
    if (e?.success) {
        uni.showToast({ title: "已保存", icon: "success" });
    } else {
        uni.showModal({ title: "失败", content: e?.message });
    }
};

方式二:手动 import 公共组件

如果你的项目关闭了 easycom 或不想走自动注册,可以手动 import 组件并按需起别名:

<script>
import LuExcelExport from "@/uni_modules/lu-export-excel/components/lu-excel-export/lu-excel-export.vue";

export default {
    components: { LuExcelExport },
};
</script>

<template>
    <LuExcelExport ref="excelExportRef" @result="onExportResult" />
</template>

方式三:直接使用 renderjs

如需绕过公共组件,直接在页面模板中引入 renderjs:

<template>
    <view class="page">
        <view
            class="excel-host"
            :prop="excelData"
            :change:prop="exportExcel.onPropChange"
            @click="handleExport"
        ></view>
    </view>
</template>

<script>
export default {
    data() {
        return {
            excelData: {
                fileName: "export",
                sheetName: "Sheet1",
                hideNameTime: false,
                hideHeader: false,
                headerList: [],
                list: [],
                trigger: 0,
            },
        };
    },
    methods: {
        // 接收 renderjs 通过 ownerInstance.callMethod('handleExportExcel', res) 回调
        handleExportExcel(res) {
            console.log(res);
            uni.showModal({
                title: res.message,
                content: res.filePath || res.message,
            });
        },
        handleExport() {
            const headerList = ["姓名", "年龄", "出生日期", "随机", "备注"];
            const list = new Array(1000).fill().map((_, i) => ({
                name: `用户${i}`,
                age: Math.floor(Math.random() * 30) + 20,
                birthday: `user${i}@example.com`,
                rod: `2025-01-01 23:22:44`,
                remark: Date.now() + Math.random().toString(36),
            }));

            // 注意:模板必须把 trigger 翻转,否则 renderjs 不会自动调用 startExport
            this.excelData = {
                ...this.excelData,
                fileName: "盘点详情",
                sheetName: "Sheet1",
                hideNameTime: false,
                hideHeader: false,
                headerList,
                list,
                trigger: (this.excelData?.trigger || 0) + 1,
            };
        },
    },
};
</script>

<!-- 引入 Excel 导出插件(renderjs 模块,UniApp APP 端专用) -->
<script
    module="exportExcel"
    lang="renderjs"
    src="@/uni_modules/lu-export-excel/js_sdk/exportExcelRender.js"
></script>

<style>
.excel-host {
    display: none;
}
</style>

渲染节点要求:renderjs 要求组件至少有一个可渲染节点,否则会出现 $t.setAttribute is not a function 异常;因此即使不显示,也必须保留 <view ... :prop="..." :change:prop="..."> 这一节点。

五、工具函数说明

js_sdk/tool.js 暴露的工具函数(renderjs 内部已使用,业务侧如需复用可按需 import):

getCompactDateTime(date?)

生成紧凑格式时间字符串,形如 20251015_121532。

  • date:Date | number | string,默认 new Date()。

arrayBufferToBase64(buffer)

将 ArrayBuffer / Uint8Array 等二进制数据转为 Base64 字符串(用于 plus.io 文件写入)。

saveArrayBufferToFile(fileName, arrayBuffer)

将二进制数据写入 plus.io.PUBLIC_DOWNLOADS 公共下载目录,返回 Promise<{ filePath, fileName }>。

getAutoColumnWidths(data)

基于数据字段计算自适应列宽(中文算 2、英文算 1),返回宽度数组。

setHeaderStyle(worksheetData, headerList, headerStyle?)

给 worksheet 的第 1 行表头单元格写入样式,默认仅 font.bold = true。更多样式字段可参考 xlsx-js-style 官方文档。headerStyle 可选参数示例:

setHeaderStyle(worksheet, ["姓名", "年龄"], {
    font: {
        bold: true,
        italic: true,
        sz: 12,
        name: "华文琥珀",
        strike: true,
        underline: true,
        color: { rgb: "FFFFFF" },
    },
    fill: {
        fgColor: { rgb: "4CAF50" },
    },
    alignment: {
        horizontal: "center",
        vertical: "center",
    },
    // border: {
    //  top:    { style: "thin", color: { rgb: "9417ed" } },
    //  bottom: { style: "thin", color: { rgb: "9417ed" } },
    //  left:   { style: "thin", color: { rgb: "9417ed" } },
    //  right:  { style: "thin", color: { rgb: "9417ed" } },
    // },
});

// border.style 可选值:
// thin / medium / thick / dashed / dotted / hair / dashDot / dashDotDot
// mediumDashDot / mediumDashDotDot / mediumDashed / slantDashDot / double

六、关键注意事项

  1. 必须安装 xlsx-js-style(而非原生 xlsx),因为 setHeaderStyle 需要带样式的版本;
  2. trigger 翻转机制:renderjs 端 onPropChange 仅在 oldTrigger 与 trigger 都是明确值且不同时触发导出,避免 prop 初始同步(0 vs null)误触发;
  3. 重复同步守卫:renderjs 端会记忆 lastTrigger,prop 重复同步同一 trigger 时直接 return,不会重复导出;
  4. 数据为空时忽略导出:list 为空数组时 renderjs 端直接 return,避免误生成空文件;
  5. 文件名安全处理:fileName 中的 /\:?"<>| 会被替换为 _;
  6. 文件保存位置:默认 plus.io.PUBLIC_DOWNLOADS(公共下载目录),仅支持 APP 端;
  7. 样式范围:仅表头单元格会应用样式,正文单元格样式需要自行扩展 setHeaderStyle 类似工具函数或直接修改 worksheet;
  8. 平台支持:仅 UniApp APP 端(vue2 / vue3),不支持 Web、小程序、H5;
  9. 公共组件渲染节点:lu-excel-export.vue 的 .excel-export-root 节点 position: absolute; width: 0; height: 0; overflow: visible;(脱离文档流、不占布局),内部 .excel-export-host 为 renderjs prop-change 锚点(display: none),不能删除;同时根节点不能直接 display: none,否则会裁剪掉内部的 fixed 定位遮罩弹层;
  10. 公共组件结果弹窗:组件已内置自绘遮罩弹层(成功显示 filePath/fileName,失败显示错误信息),父组件也可通过 silent 关闭内置弹窗并监听 @result 自定义处理。

七、常见问题排查

  1. $t.setAttribute is not a function:模板中没有可渲染节点导致 renderjs 异常,保留一个挂 :prop 与 :change:prop 的 <view> 即可;
  2. 首次点击没反应:检查 list 是否为空数组(空时 renderjs 直接忽略);
  3. 导出没触发:trigger 没有翻转,或 oldTrigger / trigger 中存在 undefined / null;请确认每次都做 trigger + 1;
  4. 导出文件被覆盖:hideNameTime = true 时同名文件会被覆盖,需要保留历史请设为 false;
  5. 仅在 Android 验证通过:本插件目前仅在 Android 端测试验证;
  6. 表头样式不生效:确认安装了 xlsx-js-style 而非 xlsx;xlsx 不支持单元格样式;
  7. 中文列宽过窄:getAutoColumnWidths 已按中英文混排估算显示宽度,若仍有偏差可在 tool.js 中调整 + 2 的 padding;
  8. 公共组件遮罩被裁剪/不显示:检查 .excel-export-root 是否被父级 display: none 或 overflow: hidden 包裹;组件根节点自身仅做绝对定位 0 尺寸 + overflow: visible。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

无

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

无

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

许可协议

MIT协议