乐于分享
好东西不私藏

WPS AirScript脚本合集(二六):一次搞定!实现多个字段自动写入表格存档成图片、PDF、Excel文件

WPS AirScript脚本合集(二六):一次搞定!实现多个字段自动写入表格存档成图片、PDF、Excel文件

家人们,看见标题有木有一股熟悉的味道?(我就站在你面前,你看我有几分像从前……)

事情是这样的(´▽`)(说来话长,长话短说)去年某天,主包发过这样一篇文WPS AirScript脚本合集(十七):瞬时归档!多维表格结合脚本自动存档成图片、PDF、表格文件

当时只选取了文本和图片字段作为演示看完文章内容,

就有好学的小伙伴向主包提问啦“那联系人字段怎么搞叻?”于是主包又发了篇文WPS AirScript脚本合集(二三):自动触发!联系人字段内容文件存档

知道了文本、图片、联系人字段后还有其他字段,单选项、多选项、关联字段等这些要怎么搞捏?(._.)

(不少小伙伴私信后台表示强烈的学习欲望与其每天东回复一个西回复一个干脆整个集合案例,让我们来一次性摸索个清楚吧(Let’s go, go, go!↖(^ω^)↗

搞点TMI:整个文档数据和脚本都是指挥着灵犀完成的常规操作指路:WPS Air Script脚本合集(二五):携手共创!骑上灵犀Claw驾嘚一声就出发啦过程基本上很顺利,心痛的是花了快4000灵点(ToT)/~~~

一、多维表格数据表及字段结构

数据表

1、数据表名:数据表2、视图名:表格视图3、字段设置:◉ 字段名称:课程名称 – 字段类型:文本 – 限制字数:否 – 禁止录入重复值:否 – 默认值:无◉ 字段名称:课程简介 – 字段类型:文本 – 限制字数:否 – 禁止录入重复值:否 – 默认值:无◉ 字段名称:课程时长(h) – 字段类型:数字 – 数字格式:保留2位小数 – 显示千位符:否 – 数据校验:不限定数据范围 – 默认值:无◉ 字段名称:图片和附件 – 字段类型:图片和附件 – 图片仅可通过移动端拍摄上传:否 – 显示样式:以缩略图样式显示◉ 字段名称:课程类型 – 字段类型:单选项 – 选项:必修课 / 选修课 / 实践课 / 公选课 – 允许在输入时添加选项:是 – 默认值:无◉ 字段名称:适用年级 – 字段类型:多选项 – 选项:大一 / 大二 / 大三 / 大四 / 研一 / 研二 – 允许在输入时添加选项:是 – 默认值:无◉ 字段名称:日期 – 字段类型:日期 – 格式:yyyy/MM/dd – 显示星期:否 – 显示时间:否 – 数据校验:不限定数据范围 – 默认值:无◉ 字段名称:时间 – 字段类型:时间 – 格式:hh:mm:ss – 数据校验:不限定数据范围◉ 字段名称:联系人 – 字段类型:联系人 – 使用设置:勾选 允许选择多个联系人,勾选 允许向新插入的联系人发送通知 – 默认值:无◉ 字段名称:公式 – 字段类型:公式 – 公式概览:=COUNT([@课程时长(h)]) – 数字格式:数值 – 精度:保留两位小数◉ 字段名称:身份证 – 字段类型:身份证 – 禁止录入重复值:否◉ 字段名称:电话 – 字段类型:电话 – 禁止录入重复值:否◉ 字段名称:地址 – 字段类型:地址 – 地址格式:省-详细地址 – 填写详细地址:勾选 – 填写预设:不预设指定地址◉ 字段名称:电子邮箱 – 字段类型:电子邮箱◉ 字段名称:进度 – 字段类型:进度 – 进度数值范围:0%-100%◉ 字段名称:等级 – 字段类型:等级 – 等级数量:5◉ 字段名称:百分比 – 字段类型:百分比 – 格式:0.00% – 数据校验:不限定数据范围◉ 字段名称:货币 – 字段类型:货币 – 货币:¥- 数字格式:1,234 – 数据校验:不限定数据范围 – 默认值:无◉ 字段名称:超链接 – 字段类型:超链接 – 显示样式:以超链接形式显示◉ 字段名称:复选框 – 字段类型:复选框 – 默认勾选:无◉ 字段名称:授课教师 – 字段类型:双向关联 – 来自:教师表 – 可关联的记录范围:全部记录 – 可关联的数量:勾选 允许关联多条记录◉ 字段名称:级联 – 字段类型:级联选项 – 选项:手动配置选项 – 显示完整的选择路径:勾选◉ 字段名称:定位 – 字段类型:定位 – 输入方式:不勾选 仅允许移动端定位 – 范围校验:不勾选 仅允许在指定范围内定位◉ 字段名称:创建人 – 字段类型:创建人◉ 字段名称:最后修改人 – 字段类型:最后修改人 – 范围:所有字段◉ 字段名称:创建时间 – 字段类型:创建时间 – 日期格式:yyyy/MM/dd hh:mm – 显示时间:勾选◉ 字段名称:最后修改时间 – 字段类型:最后修改时间 – 范围:所有字段  – 格式:yyyy/MM/dd hh:mm  – 显示时间:勾选◉ 字段名称:编号 – 字段类型:编号 – 编号类型:自增数字 – 编号格式:000001◉ 字段名称:点击进行资料存档 – 字段类型:按钮 – 执行操作:点击按钮时,触发执行Air Script脚本 – 执行成功的提示:存档成功^_−☆ – 按钮名称:点击进行存档 – 按钮颜色:绿色◉ 字段名称:图片存档 – 字段类型:图片和附件 – 不勾选图片仅可通过移动端拍摄上传 – 显示样式:以缩略图样式显示◉ 字段名称:PDF存档 – 字段类型:图片和附件 – 不勾选图片仅可通过移动端拍摄上传 – 显示样式:以缩略图样式显示◉ 字段名称:表格存档 – 字段类型:图片和附件 – 不勾选图片仅可通过移动端拍摄上传 – 显示样式:以缩略图样式显示

教师表

1、数据表名:教师表2、视图名:表格视图3、字段设置:◉ 字段名称:教师姓名 – 字段类型:文本 – 限制字数:否 – 禁止录入重复值:否 – 默认值:无◉ 字段名称:入职日期 – 字段类型:日期 – 格式:yyyy/MM/dd – 显示星期:否 – 显示时间:否 – 数据校验:不限定数据范围 – 默认值:无◉ 字段名称:职称 – 字段类型:单选项 – 选项:教授 / 副教授 / 讲师 / 助教 – 允许在输入时添加选项:是 – 默认值:无◉ 字段名称:教学评分 – 字段类型:等级 – 等级数量:5◉ 字段名称:课程 – 字段类型:多选项 – 选项:日语入门/ 篮球 / 微观经济学 等 – 允许在输入时添加选项:是 – 默认值:无◉ 字段名称:关联:数据表 – 字段类型:双向关联 – 来自:数据表 – 可关联的记录范围:全部记录 – 可关联的数量:勾选 允许关联多条记录

打印模板

二、AirScript脚本

/** * AirScript 脚本:根据记录ID获取该行记录所有字段的内容 *   - 将字段内容写入Excel模版表格对应单元格 *   - 关联字段-授课教师自动展开,逐行填入教师信息模块 *   - 特殊字段(图片/数组/级联/地址等)做格式化处理 *   - 写入后保存 Excel 到多维表附件字段(表格存档/图片存档/PDF存档) */// ============================================================// 配置区域// ============================================================var TABLE_NAME  = Context.argv.tablename ||"数据表";            // 目标数据表名称var VIEW_NAME   = Context.argv.viewname ||"表格视图";          // 视图名称var RECORD_ID    = Context.argv.recordid || Application.Selection.GetSelectionRecordIds()[0][0]; // 目标记录IDvar TEMPLATE_URL = Context.argv.templateurl ||"https://www.kdocs.cn/Excel表格模板链接";  // Excel表格模板链接var SAVE_DIR_URL = Context.argv.savedirurl ||"https://www.kdocs.cn/另存目标目录URL"// 另存目标目录URL(留空则保存到云文档根目录;可在云文档中打开目标文件夹后复制地址栏链接)// ============================================================/** * 根据表名和记录ID,获取该记录的所有字段内容 */function getRecordById(tableName, recordId){  var sheets = Application.Sheet.GetSheets();  var targetSheet = null;  for (var i = 0; i < sheets.length; i++) {    if (sheets[i].name === tableName) {      targetSheet = sheets[i];      break;    }  }  if (!targetSheet) {    thrownew Error("找不到表名为【" + tableName + "】的数据表");  }  var offset = null;  do {    var response = Application.Record.GetRecords({      SheetId: targetSheet.id,      PageSize: 1000,      Offset: offset    });    if (!response || !response.records) {      thrownew Error("获取记录失败");    }    for (var j = 0; j < response.records.length; j++) {      var record = response.records[j];      if (String(record.id) === String(recordId)) {        return { tableId: targetSheet.id, tableName: targetSheet.name, record: record };      }    }    offset = response.offset;  } while (offset);  thrownew Error("在表【" + tableName + "】中未找到记录ID为【" + recordId + "】的记录");}/** * 根据关联ID列表,获取关联表中的具体记录内容 */function getLinkedRecords(linkedIds, linkedTableName){  if (!linkedIds || linkedIds.length === 0return [];  var sheets = Application.Sheet.GetSheets();  var targetSheet = null;  for (var i = 0; i < sheets.length; i++) {    if (sheets[i].name === linkedTableName) {      targetSheet = sheets[i];      break;    }  }  if (!targetSheet) return [];  var result = [];  var offset = null;  do {    var response = Application.Record.GetRecords({      SheetId: targetSheet.id,      PageSize: 1000,      Offset: offset    });    if (!response || !response.records) break;    for (var j = 0; j < response.records.length; j++) {      var rec = response.records[j];      for (var k = 0; k < linkedIds.length; k++) {        if (rec.id === linkedIds[k]) {          result.push(rec);          break;        }      }    }    offset = response.offset;  } while (offset);  return result;}// ============================================================// 值格式化函数:将各类字段的原始值转为适合写入单元格的字符串// ============================================================function toStr(value){  if (value === null || value === undefined) return"";  if (typeof value === "string"return value;  if (typeof value === "number" || typeof value === "boolean"return String(value);  if (typeof value === "object") {    try { return value.toString(); } catch (e) { return""; }  }  return"";}/** 数组 → 顿号拼接 */function fmtArray(arr){  if (!arr || !arr.length) return"";  var out = [];  for (var i = 0; i < arr.length; i++) {    out.push(String(arr[i]));  }  return out.join("、");}/** 超链接: [{address, displayText}] → address */function fmtUrl(arr){  if (!arr || !arr.length) return"";  return arr[0].address || "";}/** 级联: {districts:[...]} → 用 - 连接 */function fmtCascade(obj){  if (!obj || !obj.districts || !obj.districts.length) return"";  return obj.districts.join("-");}/** 联系人: [{nickName,...}] → nickName 顿号拼接 */function fmtContact(arr){  if (!arr || !arr.length) return"";  var names = [];  for (var i = 0; i < arr.length; i++) {    if (arr[i].nickName) names.push(arr[i].nickName);  }  return names.join("、");}/** 地址: {districts:[], detail:""} → 重庆市-天庭 */function fmtAddress(obj){  if (!obj) return"";  var parts = [];  if (obj.districts && obj.districts.length) parts.push(obj.districts.join(""));  if (obj.detail) parts.push(obj.detail);  return parts.join("-");}/** 定位: {displayText:"..."} → displayText */function fmtLocation(obj){  if (!obj) return"";  return obj.displayText || "";}/** 图片和附件: [{fileName,...}] → 文件名拼接 */function fmtAttachment(arr){  if (!arr || !arr.length) return"";  var names = [];  for (var i = 0; i < arr.length; i++) {    if (arr[i].fileName) names.push(arr[i].fileName);  }  return names.join("、");}/** 用户字段(创建人/最后修改人): 兼容 {nickName}, {name}, [{nickName}], 字符串 */function fmtUser(value){  if (!value) return"";  // 数组格式: [{nickName: "-.-"}]  if (Array.isArray(value) && value.length > 0) {    return value[0].nickName || value[0].name || "";  }  // 对象格式: {nickName: "-.-"} 或 {name: "-.-"}  if (typeof value === "object") {    return value.nickName || value.name || "";  }  return toStr(value);}/** 时间字段(创建时间/最后修改时间): 兼容字符串 "2026/06/06 16:41:01"、时间戳数字 */function fmtTime(value){  if (!value) return"";  // 如果是纯数字(时间戳毫秒),格式化为日期字符串  if (typeof value === "number") {    var d = new Date(value);    var y = d.getFullYear();    var M = ("0" + (d.getMonth() + 1)).slice(-2);    var day = ("0" + d.getDate()).slice(-2);    var h = ("0" + d.getHours()).slice(-2);    var m = ("0" + d.getMinutes()).slice(-2);    return y + "/" + M + "/" + day + " " + h + ":" + m;  }  // 字符串格式直接返回  return toStr(value);}// ============================================================// 主执行入口// ============================================================try {  if (!RECORD_ID) {    console.log("请先在配置区域设置 RECORD_ID(目标记录ID)");  } else {    var result = getRecordById(TABLE_NAME, RECORD_ID);    var f = result.record.fields;    // ---------- 系统字段:编号 / 创建人 / 创建时间 / 最后修改人 / 最后修改时间 ----------    var val_编号         = toStr(f["编号"]);    var val_创建人       = fmtUser(f["创建人"]);    var val_创建时间     = fmtTime(f["创建时间"]);    var val_最后修改人   = fmtUser(f["最后修改人"]);    var val_最后修改时间 = fmtTime(f["最后修改时间"]);    // ---------- 原有字段 ----------    var val_课程名称    = toStr(f["课程名称"]);    var val_课程时长     = toStr(f["课程时长(h)"]);    var val_日期        = toStr(f["日期"]);    var val_课程类型    = toStr(f["课程类型"]);    var val_等级        = toStr(f["等级"]);    var val_时间        = toStr(f["时间"]);    var val_复选框      = toStr(f["复选框"]);    var val_货币        = toStr(f["货币"]);    var val_百分比      = toStr(f["百分比"]);    var val_电话        = toStr(f["电话"]);    var val_电子邮箱    = toStr(f["电子邮箱"]);    var val_课程简介    = toStr(f["课程简介"]);    var val_身份证      = toStr(f["身份证"]);    var val_公式        = toStr(f["公式"]);    var val_进度        = toStr(f["进度"]);    // 需要特殊格式化的字段    var val_适用年级    = fmtArray(f["适用年级"]);    var val_超链接      = fmtUrl(f["超链接"]);    var val_级联        = fmtCascade(f["级联"]);    var val_联系人      = fmtContact(f["联系人"]);    var val_地址        = fmtAddress(f["地址"]);    var val_定位        = fmtLocation(f["定位"]);    var val_图片和附件  = fmtAttachment(f["图片和附件"]);    // ====================================================    // 打印所有字段值    // ====================================================    console.log("记录ID: " + result.record.id);    console.log("记录内容: " + JSON.stringify(result));    console.log("========== 模块0:系统字段 ==========");    console.log("编号: " + val_编号);    console.log("创建人: " + val_创建人);    console.log("创建时间: " + val_创建时间);    console.log("最后修改人: " + val_最后修改人);    console.log("最后修改时间: " + val_最后修改时间);    console.log("\n========== 模块1:课程基础信息字段值 ==========");    console.log("课程名称: " + val_课程名称);    console.log("课程类型: " + val_课程类型);    console.log("图片和附件: " + val_图片和附件);    console.log("课程简介: " + val_课程简介);    console.log("课程时长(h): " + val_课程时长);    console.log("进度: " + val_进度);    console.log("适用年级: " + val_适用年级);    console.log("日期: " + val_日期);    console.log("时间: " + val_时间);    console.log("等级: " + val_等级);    console.log("复选框: " + val_复选框);    console.log("货币: " + val_货币);    console.log("百分比: " + val_百分比);    console.log("电话: " + val_电话);    console.log("电子邮箱: " + val_电子邮箱);    console.log("超链接: " + val_超链接);    console.log("级联: " + val_级联);    console.log("身份证: " + val_身份证);    console.log("联系人: " + val_联系人);    console.log("公式: " + val_公式);    console.log("地址: " + val_地址);    console.log("定位: " + val_定位);    // 基于模板另存为新文件    var newFileName = val_课程名称 + "-课程介绍-" + new Date().getTime();    var createParams = { source: TEMPLATE_URL, name: newFileName };    if (SAVE_DIR_URL) createParams.dirUrl = SAVE_DIR_URL;    var newFileUrl = KSDrive.createFile(KSDrive.FileType.ET, createParams);    console.log("========== 另存模板文件 ==========");    console.log("另存模板文件链接: " + newFileUrl);    // 打开新文件进行操作    var file = KSDrive.openFile(newFileUrl);    if (!file) thrownew Error("无法打开另存后的文件");    console.log("\n已打开另存文件,开始写入...");    var sheet = file.Application.ActiveSheet;    // ====================================================    // 模块0:系统字段写入(第2-3行)    // 标签在B/D/F列,值写入C/E/G列(值区域)    // ====================================================    // 第2行:编号(C2) / 创建人(E2) / 创建时间(G2)    sheet.Range("C2").Value = val_编号;    console.log("  C2 编号 => OK");    sheet.Range("E2").Value = "'" + val_创建人;    console.log("  E2 创建人 => OK");    sheet.Range("G2").Value = val_创建时间;    console.log("  G2 创建时间 => OK");    // 第3行:最后修改人(C3) / 最后修改时间(E3)    sheet.Range("C3").Value = "'" + val_最后修改人;    console.log("  C3 最后修改人 => OK");    sheet.Range("E3").Value = val_最后修改时间;    console.log("  E3 最后修改时间 => OK");    console.log("系统字段 已写入");    // ====================================================    // 模块1:课程基础信息写入(第4-11行)    // 标签在B/D/F列,值写入C/E/G列(值区域)    // ====================================================    // 第4行:C4(课程名称值) / E4(课程类型值) / G4(图片和附件值)    sheet.Range("C4").Value = val_课程名称;    console.log("  C4 课程名称 => OK");    sheet.Range("E4").Value = val_课程类型;    console.log("  E4 课程类型 => OK");    // 第5行(C5-G5整行合并)→ 值填 C5    sheet.Range("C5").Value = val_课程简介;    console.log("  C5 课程简介 => OK");    // 第6行:C6(课程时长值) / E6(进度值) / G6(适用年级值)    sheet.Range("C6").Value = val_课程时长;    console.log("  C6 课程时长 => OK");    sheet.Range("E6").Value = val_进度;    console.log("  E6 进度 => OK");    sheet.Range("G6").Value = val_适用年级;    console.log("  G6 适用年级 => OK");    // 第7行:C7(日期值) / E7(时间值) / G7(等级值)    sheet.Range("C7").Value = val_日期;    console.log("  C7 日期 => OK");    sheet.Range("E7").Value = val_时间;    console.log("  E7 时间 => OK");    sheet.Range("G7").Value = val_等级;    console.log("  G7 等级 => OK");    // 第8行:C8(复选框值) / E8(货币值) / G8(百分比值)    sheet.Range("C8").Value = val_复选框;    console.log("  C8 复选框 => OK");    sheet.Range("E8").Value = val_货币;    console.log("  E8 货币 => OK");    sheet.Range("G8").Value = val_百分比;    console.log("  G8 百分比 => OK");    // 第9行:C9(电话值) / E9(电子邮箱值) / G9(超链接值)    sheet.Range("C9").Value = val_电话;    console.log("  C9 电话 => OK");    sheet.Range("E9").Value = val_电子邮箱;    console.log("  E9 电子邮箱 => OK");    sheet.Range("G9").Value = val_超链接;    console.log("  G9 超链接 => OK");    // 第10行:C10(级联值) / E10(身份证值) / G10(联系人值)    // 身份证纯数字18位 → 加空格前缀强制文本    // 联系人可能以-开头 → 加空格前缀防误解析    sheet.Range("C10").Value = val_级联;    console.log("  C10 级联 => OK");    sheet.Range("E10").Value = " " + String(val_身份证);    console.log("  E10 身份证 => OK");    sheet.Range("G10").Value = " " + val_联系人;    console.log("  G10 联系人 => OK");    // 第11行:C11(公式值) / E11(地址值) / G11(定位值)    sheet.Range("C11").Value = val_公式;    console.log("  C11 公式 => OK");    sheet.Range("E11").Value = val_地址;    console.log("  E11 地址 => OK");    sheet.Range("G11").Value = val_定位;    console.log("  G11 定位 => OK");    console.log("课程基础信息 已写入");    // ====================================================    // 模块2:授课教师信息    // ====================================================    var teacherIds = f["授课教师"];    if (teacherIds && teacherIds.length > 0) {      var teachers = getLinkedRecords(teacherIds, "教师表");      var teacherRows = [];      console.log("\n========== 模块2:授课教师信息字段值 ==========");      console.log("共 " + teachers.length + " 位授课教师");      for (var t = 0; t < teachers.length; t++) {        var tf = teachers[t].fields;        var tName     = toStr(tf["教师姓名"]);        var tDate     = toStr(tf["入职日期"]);        var tTitle    = toStr(tf["职称"]);        var tScore    = toStr(tf["教学评分"]);        var tCourses  = fmtArray(tf["课程"]);        console.log("  --- 教师" + (t+1) + " ---");        console.log("    教师姓名: " + tName);        console.log("    入职日期: " + tDate);        console.log("    职称: " + tTitle);        console.log("    教学评分: " + tScore);        console.log("    课程: " + tCourses);        teacherRows.push([tName, tDate, tTitle, tScore, tCourses]);      }      var startRow = 14;      for (var r = 0; r < teacherRows.length && r < 9; r++) {        var row = startRow + r;        var tr = teacherRows[r];        sheet.Range("B" + row).Value = tr[0];        sheet.Range("C" + row).Value = tr[1];        sheet.Range("D" + row).Value = tr[2];        sheet.Range("E" + row).Value = tr[3];        sheet.Range("F" + row).Value = tr[4];        console.log("  教师" + (r+1) + ": " + tr[0] + " → 行" + row);      }    } else {      console.log("\n 无授课教师");    }    // --- 图片插入(所有文本写入完成后执行)---    try {      var attVal = Application.Sheets(TABLE_NAME).Views(VIEW_NAME)                     .RecordRange(RECORD_ID, "@图片和附件").Value;      if (attVal && attVal.Value && attVal.Value.length > 0) {        var imgUrl = attVal.Value[0].ThumbnailsUrl;        if (imgUrl) {          var resp = HTTP.get(imgUrl, { timeout: 10000 });          if (resp.status === 200) {            var base64 = "data:image/png;base64," + resp.binary().toString("base64");            sheet.Range("G4").InsertImage(base64);            console.log("图片已插入 G4");          }        }      }    } catch (imgErr) {      console.log("图片插入跳过: " + imgErr.message);    }    console.log("\n========== Excel 表格写入完成 ==========");    console.log("Excel 链接: " + newFileUrl);    // ====================================================    // 模块3:保存到多维表附件字段    // 导出PDF、导出图片、保存Excel → 写入「图片存档」「PDF存档」「表格存档」    // ====================================================    console.log("\n========== 模块3:保存到多维表附件字段 ==========");    // 3.1 导出 PDF(先设置页面:横向A4 + 打印区域 + 自适应1页宽)    try {      sheet.PageSetup.Orientation = 1;      // 横向:1=纵向,2=横向      sheet.PageSetup.PrintArea = "B2:G22";  // 打印区域限定表格范围      sheet.PageSetup.Zoom = false;          // 关闭固定缩放      sheet.PageSetup.FitToPagesWide = 1;    // 横向适配1页宽      sheet.PageSetup.FitToPagesTall = false// 纵向上不限制页数(自动分页)      console.log("页面设置完成: 纵向A4, 打印区域B2:G22, 自适应1页宽");    } catch (psErr) {      console.log("页面设置跳过: " + psErr.message);    }    var json_pdf = file.Application.ActiveWorkbook.ExportAsFixedFormat(0);    console.log("PDF导出链接: " + json_pdf.url);    Application.Sheets(TABLE_NAME).RecordRange(RECORD_ID, "@PDF存档").Value =      DBCellValue([{ fileData: json_pdf.url, fileName: val_课程名称 + "-课程介绍.PDF" }]);    console.log("PDF存档 => OK");    // 3.2 导出图片    var json_pic = file.Application.ActiveWorkbook.ExportAsFixedFormat(2);    console.log("图片导出链接: " + json_pic.url);    Application.Sheets(TABLE_NAME).RecordRange(RECORD_ID, "@图片存档").Value =      DBCellValue([{ fileData: json_pic.url, fileName: val_课程名称 + "-课程介绍.PNG" }]);    console.log("图片存档 => OK");    // 3.3 保存 Excel 文件引用    console.log("Excel 链接: " + newFileUrl);    Application.Sheets(TABLE_NAME).RecordRange(RECORD_ID, "@表格存档").Value =      DBCellValue([{ fileData: newFileUrl, fileName: val_课程名称 + "-课程介绍.XLSX" }]);    console.log("表格存档 => OK");    // 关闭文件    file.close();    console.log("\n========== 全部完成:数据写入 + 三种格式存档 ==========");  }catch (error) {  console.log("运行错误: " + error.message);}

三、脚本逻辑解说

整体代码是从多维表一行记录 → Excel 模板 → 写入并导出的端到端自动化脚本,运行时由自动化流程触发的按钮驱动。整体分为 4 层架构:

第一层:配置入口(第13-17行)

var TABLE_NAME  = Context.argv.tablename || "数据表";var VIEW_NAME   = Context.argv.viewname ||"表格视图";  var RECORD_ID   = Context.argv.recordid || Application.Selection.GetSelectionRecordIds()[0][0];vvar TEMPLATE_URL = Context.argv.templateurl ||"https://www.kdocs.cn/Excel表格模板链接";  // Excel表格模板链接var SAVE_DIR_URL = Context.argv.savedirurl ||"https://www.kdocs.cn/另存目标目录URL"// 另存目标目录URL(留空则保存到云文档根目录;可在云文档中打开目标文件夹后复制地址栏链接)

核心逻辑:Context.argv.xxx || 默认值 的双保险机制。

这样同一份脚本既能手动调试(点运行按钮),也能被自动化流程触发(按钮→自动化→脚本),互不影响。

第二层:数据获取与格式化(第22-171行)

getRecordById(tableName, recordId) — 根据表名+记录ID从多维表检索一条完整记录:

  1. 遍历 Application.Sheet.GetSheets() 找到目标表

  2. 分页调用 Application.Record.GetRecords({SheetId, PageSize:1000, Offset})

  3. 用 String(record.id) === String(recordId) 做类型安全的 ID 匹配(防御自动化传参可能的类型不一致)

  4. 找到后返回 { tableId, tableName, record }

getLinkedRecords(linkedIds, linkedTableName) — 按 ID 列表批量获取关联表(教师表)的记录,同样的分页遍历逻辑,用双重循环做交集匹配。

10 个格式化函数,每种对应一个字段的特殊结构:

第三层:文件操作管线(第174-469行)

这是脚本的核心执行流,分为 3 个阶段:

阶段 1 — 另存模板(第241-249行)

var newFileUrl = KSDrive.createFile(KSDrive.FileType.ET, {    source: TEMPLATE_URL,   // 模板来源    dirUrl: SAVE_DIR_URL,   // 目标目录    name: "泛函分析-课程介绍-1749260141"});var file = KSDrive.openFile(newFileUrl);

不在原模板上直接改,而是 KSDrive.createFile 基于模板拷贝一份到指定目录,再打开副本操作。这样模板始终保持空白,每次触发都生成独立的新文件。

阶段 2 — 逐行写入 Excel(第251-398行)

按照课程介绍表格的物理布局,将所有 26 个字段 + 系统字段的值写入对应单元格:

两个防 Excel 误解析的处理:

  • 身份证 440103… 是纯 18 位数字 → 前面加空格 ” ” + String(val_身份证) 阻止科学计数法

  • 联系人值以 – 开头 → 同样加空格前缀,防止 Excel 误认为公式

教师信息不直接取多维表原始 ID 数组,而是通过 getLinkedRecords 从教师表查出具体的姓名、日期、职称、评分、课程,再逐行填入第 14-22 行。

阶段 2.5 — 图片插入(第400-415行)

var attVal = Application.Sheets(TABLE_NAME).Views(VIEW_NAME)               .RecordRange(RECORD_ID, "@图片和附件").Value;var imgUrl = attVal.Value[0].ThumbnailsUrl;var base64 = "data:image/png;base64," + HTTP.get(imgUrl).binary().toString("base64");sheet.Range("G4").InsertImage(base64);

从多维表视图层获取图片的缩略图 URL,通过 HTTP.get 下载二进制,转 base64,用 InsertImage 插入 G4 单元格。所有文本写入完成后才执行,避免图片影响后续格子。

第四层:三格式存档(第417-469行)

页面设置(导出前):

sheet.PageSetup.Orientation = 1;       // 1=纵向sheet.PageSetup.PrintArea = "B2:G22";  // 限定表格区域sheet.PageSetup.FitToPagesWide = 1;    // 宽度自适应一页

三种格式 + 写入多维表附件字段:

写入附件字段的核心 API:

Application.Sheets(TABLE_NAME).RecordRange(RECORD_ID, "@PDF存档").Value =    DBCellValue([{ fileData: url, fileName"泛函分析-课程介绍.PDF" }]);

最后 file.close() 关闭文件,释放资源。

整体数据流

用户点击按钮  自动化流程传入 Context.argv   getRecordById 读取多维表记录   10 个格式化函数处理 26 个字段   KSDrive.createFile 基于模板另存新文件   sheet.Range().Value 逐行写入 26 个单元格   getLinkedRecords 获取教师信息并填入   InsertImage 插入图片   ExportAsFixedFormat(0) 导出 PDF  @PDF存档   ExportAsFixedFormat(2) 导出图片  @图片存档   newFileUrl  @表格存档   file.close()

核心设计思想:模板不动、副本操作、格式防御、三档齐出。

四、脚本编辑器整体输出情况

如果想要直接运行脚本,需要先选择某一行记录哦因为脚本内容不是批量处理所有记录的,所以不指定行记录,脚本是不知道要处理的是哪一行,结果自然而然就是报错啦

择某一行记录运行脚本后,脚本编辑器输出结果如下:

五、自动化流程设置

六、动图演示

1、选择某行记录然后运行脚本

2、点击按钮触发自动化流程

最终存档效果如下,依次为图片、PDF和表格~

最后说点啥

1、图片和附件字段仅支持本地文件上传,如果是从云文档上传的图片,图片识别结果返回“响应状态: 302”,大概率是因为访问这张图片需要WPS账号申请访问权限,不信的话可以登录另一个账号去尝试访问这张图片URL试试看哟o(∩_∩)o

2、图片和附件字段仅支持获取第一张图片,如果字段有多张图片,默认只获取第一张图片哦

3、碍于内容篇幅长度,文本、多选项等具体字段内容的获取思路我们后续再慢慢分享。(来不及解释啦,快上车!

点赞、收藏+关注一键三连,

免得之后找不到啦。(最后的最后,↖(^ω^)↗(假如再也见不到你,

祝你早安、午安、晚安。(即使每天都能见到你,

也祝你早安、午安、晚安。

        ☆  .  ☆       . ∧_∧ ∩  ☆

   ☆ ( ・∀・)/ .   . ⊂   ノ ☆☆  (つ ノ .☆

      (ノ

往期推荐 ·盲选时刻

点兵点将,点到谁就是谁!

关注博主不迷路,开启缘分第一步~

带你见证一个普通小厂搬砖人的牛马人生!

请在微信客户端打开