写在开头
Hello,各位好呀!今是 2025 年 10 月 26 日。😀
距离上次写文已经是一个多月前了呢,事有点多😋,简洁罗列记录下:
- 又去爬了几次山,这次不仅广州,也去了周边城市
- 参加了两次 TRAE 的线下活动
- 换季中招,感冒了一场
- 换了些身上的电子设备
- ...
这一个多月过得还是比较充实的,平衡了工作与生活。💯
然后呢,最近小编在做飞书多维表格插件方面的业务开发,飞书官方提供了 JS SDK 来帮助咱们开发插件:
📖 官方文档:传送门
不过呢,官方 SDK 的方法都比较"原子化",虽然灵活,但写业务时往往需要把多个 API 组合起来才能完成一次具体操作。每次都要写一堆重复代码,有点...痛苦!😭
所以呢,小编基于项目里的真实需求,进一步做了"组合封装",让咱们在写插件时能"一把梭",效率蹭蹭往上涨!希望你能多一些时间出来摸鱼,沸点池里都没鱼了喂 (๑•̀ㅂ•́)و✧。
接下来,是一些方法情况的详情介绍,请诸君按需食用哈。
方法详解 🔧
在实际开发中,咱们经常需要:
- 🔍 获取表格实例
- 🏷️ 获取字段 ID
- 📝 批量添加记录
- ✅ 确保字段存在
- 📊 处理大量数据的分批操作
- 🔄 数据格式转换和映射
- ...
基于这些常见需求,小编封装了一系列实用方法,让开发更加高效,告别重复造轮子!🎉
本次的封装形式是直接写了一个 bitable.js 的工具文件,使用ESM形式,使用非常简单,需要就直接导出使用即可。
1️⃣ 获取表格实例
功能说明:支持通过表格名称或ID获取表格实例,传入名称时若表格不存在会自动创建。
// 导入飞书多维表格的 JS SDK
import { bitable } from '@lark-base-open/js-sdk';
// 获取基础操作对象和UI操作对象
const base = bitable.base;
const ui = bitable.ui;
/**
* 获取表格实例
* @description 支持通过表格名称或ID获取表格实例,传入名称时若表格不存在会自动创建
* @param {string} tableIdentifier - 表格标识符,可以是表格名称或表格ID
* @returns {Promise<Object>} 返回表格实例对象
* @example
* // 通过表格名称获取(不存在则自动创建)
* const table = await getTableInstance('我的数据表');
*
* // 通过表格ID获取
* const table = await getTableInstance('tblxxxxxxxxxxxxxx');
*/
export async function getTableInstance(tableIdentifier) {
let targetTableId = ""; // 用于存储目标表格的ID
// 判断传入的参数是表格ID还是表格名称
// 表格ID通常以'tbl'开头且长度较长,或者长度超过15个字符
const isTableId = tableIdentifier.length > 10 && (
tableIdentifier.startsWith('tbl') ||
tableIdentifier.length > 15
);
if (isTableId) {
// 如果传入的是表格ID,直接通过ID获取表格实例
const table = await base.getTable(tableIdentifier);
return table; // 返回表格实例
} else {
// 如果传入的是表格名称,需要先尝试获取,失败则创建
try {
// 尝试通过名称获取已存在的表格
const { id } = await base.getTable(tableIdentifier);
targetTableId = id; // 获取成功,记录表格ID
} catch (error) {
// 表格不存在,创建新表格
const { tableId } = await base.addTable({ name: tableIdentifier });
targetTableId = tableId; // 记录新创建的表格ID
await ui.switchToTable(targetTableId); // 自动切换到新创建的表格
}
// 通过表格ID获取表格实例并返回
const table = await base.getTable(targetTableId);
return table;
}
}
使用示例:
// 通过“名称”获取(不存在则自动创建)
const table = await getTableInstance('分镜设计表');
// 或通过“ID”获取
const tableById = await getTableInstance('tblxxxxxxxxxxxxxx');
2️⃣ 获取字段ID
功能说明:通过字段名获取字段ID,若字段不存在则自动创建。
import { FieldType } from '@lark-base-open/js-sdk';
/**
* 获取字段ID
* @description 通过字段名获取字段ID,若字段不存在则自动创建
* @param {Object} tableInstance - 表格实例对象
* @param {string} fieldName - 字段名称
* @param {FieldType} [fieldType=FieldType.Text] - 字段类型,默认为文本类型
* @returns {Promise<string>} 返回字段ID
* @example
* // 获取文本字段ID(不存在则创建)
* const fieldId = await getFieldId(tableInstance, '姓名');
*
* // 获取数字字段ID
* const numberFieldId = await getFieldId(tableInstance, '年龄', FieldType.Number);
*/
export async function getFieldId(tableInstance, fieldName, fieldType = FieldType.Text) {
try {
// 尝试获取已存在的字段
const { id } = await tableInstance.getField(fieldName);
return id;
} catch (error) {
// 字段不存在,创建新字段
const fieldId = await tableInstance.addField({ type: fieldType, name: fieldName });
return fieldId;
}
}
使用示例:
const fieldId = await getFieldId(table, '镜头', FieldType.Text);
3️⃣ 批量添加记录
功能说明:按字段映射关系将对象数组批量写入表格,相比逐条添加效率更高。
/**
* 批量添加记录到表格
* @description 按字段映射关系将对象数组批量写入表格,相比逐条添加效率更高
* @param {Object} tableInstance - 表格实例对象
* @param {Array<Object>} dataList - 要添加的数据列表,每个元素为一个数据对象
* @param {Array<Object>} tableFields - 字段映射配置数组
* @param {string} tableFields[].field_name - 表格中的目标字段名
* @param {string} tableFields[].field_value - 数据对象中的源字段名
* @returns {Promise<Array<string>>} 返回所有添加记录的ID数组
* @example
* const dataList = [
* { name: '张三', age: 25, city: '北京' },
* { name: '李四', age: 30, city: '上海' }
* ];
* const tableFields = [
* { field_name: '姓名', field_value: 'name' },
* { field_name: '年龄', field_value: 'age' },
* { field_name: '城市', field_value: 'city' }
* ];
* const recordIds = await addRecordsToTable(table, dataList, tableFields);
*/
export async function addRecordsToTable(tableInstance, dataList, tableFields) {
const allRecordIds = [] // 存储所有添加的记录ID
// 遍历每一行数据
for (const rowData of dataList) {
const textCellList = [] // 存储当前行的所有单元格
// 根据字段映射创建单元格
for (const item of tableFields) {
const fieldName = item.field_name // 目标字段名
const fieldValue = item.field_value // 数据源字段名
const textField = await tableInstance.getField(fieldName); // 获取字段实例
const cellValue = rowData[fieldValue] || ''; // 获取单元格值
const textCell = await textField.createCell(cellValue); // 创建单元格
textCellList.push(textCell) // 添加到单元格列表
}
// 将当前行的所有单元格添加为一条记录
const recordIds = await tableInstance.addRecords(textCellList);
allRecordIds.push(...recordIds) // 收集记录ID
}
return allRecordIds // 返回所有记录ID
}
映射配置示例:
const tableFields = [
{ field_name: '镜头', field_value: 'shot' },
{ field_name: '场景类型', field_value: 'sceneType' },
{ field_name: '时长', field_value: 'duration' },
{ field_name: '内容', field_value: 'content' },
{ field_name: '对话', field_value: 'dialogue' }
];
const dataList = [
{ shot: '第1镜', sceneType: '室内', duration: '30秒', content: '主角进入房间', dialogue: '你好,我回来了' },
{ shot: '第2镜', sceneType: '室外', duration: '45秒', content: '街道场景', dialogue: '再见' }
];
await addRecordsToTable(table, dataList, tableFields);
4️⃣ 确保字段存在
功能说明:自动检查并创建缺失的字段,避免因字段不存在导致的数据写入失败。
/**
* 确保字段存在,不存在则自动创建
* @description 检查表格中是否存在指定字段,如不存在则按指定类型创建。对于单选字段,会自动添加选项
* @param {Object} tableInstance - 表格实例对象
* @param {Array<Object>} fieldConfigs - 字段配置数组
* @param {string} fieldConfigs[].name - 字段名称
* @param {FieldType} fieldConfigs[].type - 字段类型
* @param {Object} fieldConfigs[].property - 字段属性配置(可选)
* - 对于单选字段:property.options 为 { name: string, color?: number }[] 格式
* - 对于其他字段:根据字段类型传入相应的配置对象
* @returns {Promise<Array<Object>>} 返回所有字段实例数组
* @example
* const fieldConfigs = [
* { name: '姓名', type: FieldType.Text },
* { name: '年龄', type: FieldType.Number },
* {
* name: '状态',
* type: FieldType.SingleSelect,
* property: {
* options: [
* { name: '进行中', color: 0 },
* { name: '已完成', color: 1 }
* ]
* }
* }
* ];
* const fields = await ensureFieldsExist(table, fieldConfigs);
*/
export async function ensureFieldsExist(tableInstance, fieldConfigs) {
const fieldInstances = []
for (const config of fieldConfigs) {
try {
// 尝试获取已存在的字段
const existingField = await tableInstance.getField(config.name)
fieldInstances.push(existingField)
} catch (error) {
// 字段不存在,创建新字段
const newField = await tableInstance.addField({
type: config.type,
name: config.name,
property: config.property || {}
})
// 如果是单选字段且有选项配置,添加选项
if (config.type === FieldType.SingleSelect && config.property?.options && Array.isArray(config.property.options)) {
await newField.addOptions(config.property.options)
}
fieldInstances.push(newField)
}
}
return fieldInstances
}
使用示例:
import { FieldType } from '@lark-base-open/js-sdk';
const fieldConfigs = [
{ name: '项目名称', type: FieldType.Text },
{
name: '优先级',
type: FieldType.SingleSelect,
property: {
options: [
{ name: '高', color: 0 },
{ name: '中', color: 1 },
{ name: '低', color: 2 }
]
}
},
{ name: '完成度', type: FieldType.Number },
{ name: '截止日期', type: FieldType.DateTime }
];
const fields = await ensureFieldsExist(table, fieldConfigs);
console.log('所有字段准备就绪!');
5️⃣ 分批处理大量数据
功能说明:将大量数据分批处理,避免一次性操作过多数据导致的接口超时或性能问题。
/**
* 分批添加记录到表格
* @description 将大量数据分批处理,避免一次性操作过多数据导致超时
* @param {Object} tableInstance - 表格实例对象
* @param {Array<Object>} dataList - 要添加的数据列表
* @param {Array<Object>} tableFields - 字段映射配置数组
* @param {Object} options - 配置选项
* @param {number} options.batchSize - 每批处理的记录数,默认50
* @param {number} options.delay - 批次间延迟时间(毫秒),默认100
* @param {Function} options.onProgress - 进度回调函数
* @returns {Promise<Array<string>>} 返回所有添加记录的ID数组
* @example
* const recordIds = await chunkedAddRecords(table, dataList, tableFields, {
* batchSize: 30,
* delay: 200,
* onProgress: (current, total) => console.log(`进度: ${current}/${total}`)
* });
*/
export async function chunkedAddRecords(tableInstance, dataList, tableFields, options = {}) {
const { batchSize = 50, delay = 100, onProgress } = options
const allRecordIds = []
const totalBatches = Math.ceil(dataList.length / batchSize)
for (let i = 0; i < dataList.length; i += batchSize) {
const batch = dataList.slice(i, i + batchSize)
const currentBatch = Math.floor(i / batchSize) + 1
// 处理当前批次
const batchRecordIds = await addRecordsToTable(tableInstance, batch, tableFields)
allRecordIds.push(...batchRecordIds)
// 进度回调
if (onProgress) {
onProgress(i + batch.length, dataList.length)
}
// 批次间延迟,避免接口压力过大
if (i + batchSize < dataList.length && delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay))
}
}
return allRecordIds
}
使用示例:
// 处理大量数据(比如1000条记录)
const largeDataList = [...]; // 1000条数据
const tableFields = [
{ field_name: '姓名', field_value: 'name' },
{ field_name: '部门', field_value: 'department' }
];
const recordIds = await chunkedAddRecords(table, largeDataList, tableFields, {
batchSize: 30, // 每批30条
delay: 200, // 批次间延迟200ms
onProgress: (current, total) => {
const percent = Math.round((current / total) * 100);
console.log(`导入进度: ${percent}% (${current}/${total})`);
}
});
6️⃣ 文本解析转对象数组
功能说明:将制表符分隔的文本(如从Excel复制的数据)解析为对象数组,支持自动识别表头和数据行,是数据导入的核心工具。
/**
* 将制表符分隔的文本解析为对象数组
* @description 解析从Excel或表格复制的制表符分隔文本,第一行作为表头
* @param {string} text - 制表符分隔的文本内容
* @param {Object} options - 解析选项
* @param {string} options.delimiter - 分隔符,默认为制表符
* @param {boolean} options.hasHeader - 是否包含表头,默认true
* @param {boolean} options.trimValues - 是否去除值的首尾空格,默认true
* @returns {Array<Object>} 解析后的对象数组
* @example
* const text = `姓名\t年龄\t城市\n张三\t25\t北京\n李四\t30\t上海`;
* const objects = parseTabularTextToObjects(text);
* // 返回: [
* // { 姓名: '张三', 年龄: '25', 城市: '北京' },
* // { 姓名: '李四', 年龄: '30', 城市: '上海' }
* // ]
*/
export function parseTabularTextToObjects(text, options = {}) {
const { delimiter = '\t', hasHeader = true, trimValues = true } = options
if (!text || typeof text !== 'string') {
return []
}
// 按行分割文本
const lines = text.split('\n').filter(line => line.trim())
if (lines.length === 0) {
return []
}
// 获取表头
const headers = lines[0].split(delimiter).map(header =>
trimValues ? header.trim() : header
)
if (!hasHeader) {
// 如果没有表头,使用列索引作为键名
headers = headers.map((_, index) => `column_${index}`)
}
// 解析数据行
const dataLines = hasHeader ? lines.slice(1) : lines
const objects = []
for (let i = 0; i < dataLines.length; i++) {
const values = dataLines[i].split(delimiter)
const obj = {}
headers.forEach((header, index) => {
const value = values[index] || ''
obj[header] = trimValues ? value.trim() : value
})
objects.push(obj)
}
return objects
}
使用示例:
// 从剪贴板获取的文本
const clipboardText = `项目名称负责人状态优先级
网站重构张三进行中高
移动端开发李四已完成中
数据分析王五待开始低`;
// 解析文本
const projects = parseTabularTextToObjects(clipboardText);
console.log('解析结果:', projects);
// 输出:[
// { 项目名称: '网站重构', 负责人: '张三', 状态: '进行中', 优先级: '高' },
// { 项目名称: '移动端开发', 负责人: '李四', 状态: '已完成', 优先级: '中' },
// { 项目名称: '数据分析', 负责人: '王五', 状态: '待开始', 优先级: '低' }
// ]
// 自定义分隔符解析CSV
const csvText = `姓名,年龄,部门\n张三,25,技术部\n李四,30,产品部`;
const employees = parseTabularTextToObjects(csvText, { delimiter: ',' });
7️⃣ 条件查询记录
功能说明:根据指定条件查询表格记录,支持多字段组合查询、模糊匹配等,是数据筛选和分析的基础工具。
/**
* 根据条件查询表格记录
* @description 支持多字段条件查询,可进行精确匹配或模糊匹配
* @param {Object} tableInstance - 表格实例对象
* @param {Array<Object>} conditions - 查询条件数组
* @param {string} conditions[].fieldName - 字段名称
* @param {any} conditions[].value - 查询值
* @param {string} conditions[].operator - 操作符:'equals'(精确)、'contains'(包含)、'startsWith'(开头)、'endsWith'(结尾)
* @param {string} logic - 条件间逻辑关系:'AND' 或 'OR',默认'AND'
* @returns {Promise<Array<Object>>} 返回符合条件的记录数组
* @example
* const conditions = [
* { fieldName: '状态', value: '进行中', operator: 'equals' },
* { fieldName: '负责人', value: '张', operator: 'contains' }
* ];
* const records = await queryRecordsByConditions(table, conditions, 'AND');
*/
export async function queryRecordsByConditions(tableInstance, conditions, logic = 'AND') {
try {
// 获取所有记录
const recordList = await tableInstance.getRecords({
pageSize: 5000 // 获取足够多的记录
})
const matchedRecords = []
for (const record of recordList.records) {
let isMatch = logic === 'AND' ? true : false
for (const condition of conditions) {
const { fieldName, value, operator = 'equals' } = condition
// 获取字段值
const field = await tableInstance.getField(fieldName)
const cellValue = await record.getCellValueString(field.id)
// 执行匹配检查
let conditionMatch = false
switch (operator) {
case 'equals':
conditionMatch = cellValue === String(value)
break
case 'contains':
conditionMatch = cellValue.includes(String(value))
break
case 'startsWith':
conditionMatch = cellValue.startsWith(String(value))
break
case 'endsWith':
conditionMatch = cellValue.endsWith(String(value))
break
default:
conditionMatch = cellValue === String(value)
}
// 根据逻辑关系更新匹配状态
if (logic === 'AND') {
isMatch = isMatch && conditionMatch
if (!isMatch) break // AND逻辑下,一个不匹配就可以跳出
} else {
isMatch = isMatch || conditionMatch
if (isMatch) break // OR逻辑下,一个匹配就可以跳出
}
}
if (isMatch) {
// 构建记录对象,包含所有字段值
const recordData = { recordId: record.recordId }
const fieldMetaList = await tableInstance.getFieldMetaList()
for (const fieldMeta of fieldMetaList) {
const cellValue = await record.getCellValueString(fieldMeta.id)
recordData[fieldMeta.name] = cellValue
}
matchedRecords.push(recordData)
}
}
return matchedRecords
} catch (error) {
throw error
}
}
使用示例:
// 查询状态为"进行中"且负责人包含"张"的记录
const conditions = [
{ fieldName: '状态', value: '进行中', operator: 'equals' },
{ fieldName: '负责人', value: '张', operator: 'contains' }
];
const records = await queryRecordsByConditions(table, conditions, 'AND');
console.log('查询结果:', records);
// 查询优先级为"高"或"紧急"的记录
const urgentConditions = [
{ fieldName: '优先级', value: '高', operator: 'equals' },
{ fieldName: '优先级', value: '紧急', operator: 'equals' }
];
const urgentRecords = await queryRecordsByConditions(table, urgentConditions, 'OR');
8️⃣ 批量删除记录
功能说明:根据条件批量删除表格记录,支持条件筛选删除和记录ID列表删除,操作前会进行安全确认。
/**
* 批量删除表格记录
* @description 根据条件或记录ID列表批量删除记录,支持安全确认
* @param {Object} tableInstance - 表格实例对象
* @param {Object} options - 删除选项
* @param {Array<string>} options.recordIds - 要删除的记录ID数组(优先使用)
* @param {Array<Object>} options.conditions - 删除条件数组(当recordIds为空时使用)
* @param {boolean} options.confirm - 是否需要确认,默认true
* @param {boolean} options.dryRun - 是否为试运行(只查询不删除),默认false
* @returns {Promise<Object>} 返回删除结果统计
* @example
* // 按记录ID删除
* const result = await batchDeleteRecords(table, {
* recordIds: ['rec123', 'rec456'],
* confirm: false
* });
*
* // 按条件删除
* const result = await batchDeleteRecords(table, {
* conditions: [{ fieldName: '状态', value: '已废弃', operator: 'equals' }],
* dryRun: true // 先试运行看看会删除哪些记录
* });
*/
export async function batchDeleteRecords(tableInstance, options = {}) {
const { recordIds, conditions, confirm = true, dryRun = false } = options
try {
let targetRecords = []
// 确定要删除的记录
if (recordIds && recordIds.length > 0) {
// 按记录ID删除
targetRecords = recordIds.map(id => ({ recordId: id }))
} else if (conditions && conditions.length > 0) {
// 按条件查询要删除的记录
const queryResults = await queryRecordsByConditions(tableInstance, conditions)
targetRecords = queryResults
} else {
throw new Error('必须提供 recordIds 或 conditions 参数')
}
if (targetRecords.length === 0) {
return { deleted: 0, skipped: 0, errors: 0 }
}
// 试运行模式
if (dryRun) {
targetRecords.forEach((record, index) => {
console.log(`${index + 1}. 记录ID: ${record.recordId}`)
})
return {
deleted: 0,
skipped: targetRecords.length,
errors: 0,
preview: targetRecords
}
}
// 安全确认
if (confirm) {
const confirmMessage = `⚠️ 即将删除 ${targetRecords.length} 条记录,此操作不可撤销!确认继续吗?`
console.warn(confirmMessage)
// TODO:在实际应用中,这里应该弹出确认对话框
}
// 执行删除
const deleteResults = { deleted: 0, skipped: 0, errors: 0 }
// 分批删除,避免一次删除过多
const batchSize = 50
for (let i = 0; i < targetRecords.length; i += batchSize) {
const batch = targetRecords.slice(i, i + batchSize)
const batchIds = batch.map(record => record.recordId)
try {
await tableInstance.deleteRecords(batchIds)
deleteResults.deleted += batchIds.length
} catch (error) {
deleteResults.errors += batchIds.length
}
}
return deleteResults
} catch (error) {
throw error
}
}
使用示例:
// 删除所有状态为"已废弃"的记录(先试运行)
const previewResult = await batchDeleteRecords(table, {
conditions: [{ fieldName: '状态', value: '已废弃', operator: 'equals' }],
dryRun: true
});
console.log('预览删除结果:', previewResult);
// 确认后执行删除
if (previewResult.preview.length > 0) {
const deleteResult = await batchDeleteRecords(table, {
conditions: [{ fieldName: '状态', value: '已废弃', operator: 'equals' }],
confirm: false // 已经预览过了,跳过确认
});
}
// 按记录ID删除
const specificIds = ['rec123', 'rec456', 'rec789'];
await batchDeleteRecords(table, {
recordIds: specificIds,
confirm: false
});
9️⃣ 数据导出功能
功能说明:将表格数据导出为多种格式(JSON、CSV、TSV、Excel),支持字段筛选、条件过滤和格式化选项。
/**
* 导出表格数据
* @description 将表格数据导出为指定格式,支持字段筛选和条件过滤
* @param {Object} tableInstance - 表格实例对象
* @param {Object} options - 导出选项
* @param {string} options.format - 导出格式:'json'、'csv'、'tsv'(制表符分隔)、'excel'(.xlsx格式),默认'json'
* @param {Array<string>} options.fields - 要导出的字段名数组,为空则导出所有字段
* @param {Array<Object>} options.conditions - 过滤条件数组,为空则导出所有记录
* @param {boolean} options.includeHeader - CSV/TSV/Excel格式是否包含表头,默认true
* @param {string} options.filename - 导出文件名(不含扩展名)
* @param {string} options.sheetName - Excel工作表名称,默认'Sheet1'
* @returns {Promise<Object>} 返回导出结果,包含数据和下载链接
* @example
* // 导出为JSON格式
* const result = await exportTableData(table, {
* format: 'json',
* fields: ['姓名', '部门', '状态'],
* filename: '员工数据'
* });
*
* // 导出符合条件的记录为CSV
* const result = await exportTableData(table, {
* format: 'csv',
* conditions: [{ fieldName: '状态', value: '在职', operator: 'equals' }],
* filename: '在职员工'
* });
*
* // 导出为Excel格式
* const excelResult = await exportTableData(table, {
* format: 'excel',
* fields: ['姓名', '部门', '状态'],
* filename: '员工数据',
* sheetName: '员工信息'
* });
*/
export async function exportTableData(tableInstance, options = {}) {
const {
format = 'json',
fields = [],
conditions = [],
includeHeader = true,
filename = 'export_data',
sheetName = 'Sheet1'
} = options
try {
// 获取要导出的记录
let records = []
if (conditions.length > 0) {
records = await queryRecordsByConditions(tableInstance, conditions)
} else {
const recordList = await tableInstance.getRecords({ pageSize: 5000 })
// 获取所有字段信息
const fieldMetaList = await tableInstance.getFieldMetaList()
for (const record of recordList.records) {
const recordData = { recordId: record.recordId }
for (const fieldMeta of fieldMetaList) {
const cellValue = await record.getCellValueString(fieldMeta.id)
recordData[fieldMeta.name] = cellValue
}
records.push(recordData)
}
}
if (records.length === 0) {
return { success: false, message: '没有数据可导出' }
}
// 确定要导出的字段
let exportFields = fields
if (exportFields.length === 0) {
// 如果没有指定字段,导出所有字段(除了recordId)
exportFields = Object.keys(records[0]).filter(key => key !== 'recordId')
}
// 过滤记录,只保留指定字段
const filteredRecords = records.map(record => {
const filteredRecord = {}
exportFields.forEach(field => {
filteredRecord[field] = record[field] || ''
})
return filteredRecord
})
// 根据格式生成导出内容
let exportContent = ''
let mimeType = 'text/plain'
let fileExtension = 'txt'
switch (format.toLowerCase()) {
case 'json':
exportContent = JSON.stringify(filteredRecords, null, 2)
mimeType = 'application/json'
fileExtension = 'json'
break
case 'csv':
exportContent = convertToCSV(filteredRecords, exportFields, includeHeader)
mimeType = 'text/csv'
fileExtension = 'csv'
break
case 'tsv':
exportContent = convertToTSV(filteredRecords, exportFields, includeHeader)
mimeType = 'text/tab-separated-values'
fileExtension = 'tsv'
break
case 'excel':
exportContent = convertToExcel(filteredRecords, exportFields, includeHeader, sheetName)
mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
fileExtension = 'xlsx'
break
default:
throw new Error(`不支持的导出格式: ${format}`)
}
// 创建下载链接
const blob = new Blob([exportContent], { type: mimeType })
const downloadUrl = URL.createObjectURL(blob)
const fullFilename = `${filename}.${fileExtension}`
return {
success: true,
data: filteredRecords,
content: exportContent,
downloadUrl: downloadUrl,
filename: fullFilename,
recordCount: filteredRecords.length,
fieldCount: exportFields.length
}
} catch (error) {
throw error
}
}
// 辅助函数:转换为CSV格式
function convertToCSV(records, fields, includeHeader) {
const lines = []
// 添加表头
if (includeHeader) {
lines.push(fields.map(field => `"${field}"`).join(','))
}
// 添加数据行
records.forEach(record => {
const values = fields.map(field => {
const value = record[field] || ''
// TODO: CSV格式需要转义双引号
// return `"${String(value).replace(/"/g, '""')}"`
})
lines.push(values.join(','))
})
return lines.join('\n')
}
// 辅助函数:转换为TSV格式
function convertToTSV(records, fields, includeHeader) {
const lines = []
// 添加表头
if (includeHeader) {
lines.push(fields.join('\t'))
}
// 添加数据行
records.forEach(record => {
const values = fields.map(field => {
const value = record[field] || ''
// TSV格式需要转义制表符和换行符
return String(value).replace(/\t/g, ' ').replace(/\n/g, ' ')
})
lines.push(values.join('\t'))
})
return lines.join('\n')
}
// 辅助函数:转换为Excel格式(.xlsx);前端导出Excel可以使用一些现成的库,这里小编由于某些原因只能使用JS来完成,所以让AI用纯JS撸了一个来满足需求。。。
function convertToExcel(records, fields, includeHeader, sheetName = 'Sheet1') {
// Excel文件的基本XML结构
const xmlHeader = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
// 构建工作表数据
let sheetData = ''
let rowIndex = 1
// 添加表头
if (includeHeader) {
sheetData += `<row r="${rowIndex}">`
fields.forEach((field, colIndex) => {
const cellRef = getCellReference(rowIndex, colIndex + 1)
sheetData += `<c r="${cellRef}" t="inlineStr"><is><t>${escapeXml(field)}</t></is></c>`
})
sheetData += '</row>'
rowIndex++
}
// 添加数据行
records.forEach(record => {
sheetData += `<row r="${rowIndex}">`
fields.forEach((field, colIndex) => {
const cellRef = getCellReference(rowIndex, colIndex + 1)
const value = record[field] || ''
const cellValue = String(value)
// 判断是否为数字
const isNumber = !isNaN(cellValue) && !isNaN(parseFloat(cellValue)) && cellValue.trim() !== ''
if (isNumber) {
sheetData += `<c r="${cellRef}"><v>${cellValue}</v></c>`
} else {
sheetData += `<c r="${cellRef}" t="inlineStr"><is><t>${escapeXml(cellValue)}</t></is></c>`
}
})
sheetData += '</row>'
rowIndex++
})
// 完整的工作表XML
const worksheet = `${xmlHeader}
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
${sheetData}
</sheetData>
</worksheet>`
// 创建ZIP文件结构(简化版Excel文件)
const zipContent = createExcelZip(worksheet, sheetName)
return zipContent
}
// 辅助函数:获取Excel单元格引用(如A1, B2等)
function getCellReference(row, col) {
let colName = ''
while (col > 0) {
col--
colName = String.fromCharCode(65 + (col % 26)) + colName
col = Math.floor(col / 26)
}
return colName + row
}
// 辅助函数:转义XML特殊字符
function escapeXml(text) {
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
// 辅助函数:创建Excel ZIP文件结构
function createExcelZip(worksheet, sheetName) {
// 这里使用简化的方法,实际上Excel文件是一个ZIP包含多个XML文件
// 为了不使用外部库,我们创建一个包含基本结构的XML文件
// 注意:这是一个简化版本,可能不被所有Excel版本完全支持
const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
</Types>`
const workbook = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheets>
<sheet name="${escapeXml(sheetName)}" sheetId="1" r:id="rId1" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
</sheets>
</workbook>`
// 由于不使用外部库,我们返回一个包含所有必要信息的数据结构
// 实际使用时,这需要被正确地打包成ZIP格式
// 这里我们返回一个特殊格式的字符串,包含所有必要的Excel文件内容
return JSON.stringify({
'[Content_Types].xml': contentTypes,
'xl/workbook.xml': workbook,
'xl/worksheets/sheet1.xml': worksheet,
'_format': 'excel-json' // 标识这是Excel JSON格式
})
}
使用示例:
// 导出所有数据为JSON格式
const jsonResult = await exportTableData(table, {
format: 'json',
filename: '完整数据'
});
// 导出指定字段为CSV格式
const csvResult = await exportTableData(table, {
format: 'csv',
fields: ['姓名', '部门', '入职日期'],
filename: '员工基本信息'
});
// 导出符合条件的记录
const filteredResult = await exportTableData(table, {
format: 'tsv',
conditions: [
{ fieldName: '状态', value: '在职', operator: 'equals' },
{ fieldName: '部门', value: '技术', operator: 'contains' }
],
filename: '技术部在职员工'
});
// 导出为Excel格式
const excelResult = await exportTableData(table, {
format: 'excel',
fields: ['姓名', '部门', '入职日期', '状态'],
filename: '员工信息表',
sheetName: '员工数据'
});
// 触发下载
if (csvResult.success) {
const link = document.createElement('a');
link.href = csvResult.downloadUrl;
link.download = csvResult.filename;
link.click();
}
// Excel文件下载(需要特殊处理)
if (excelResult.success) {
// 注意:由于Excel格式的复杂性,实际使用时可能需要额外的处理
// 这里提供的是基础的XML结构,可以被大多数Excel应用程序识别
const link = document.createElement('a');
link.href = excelResult.downloadUrl;
link.download = excelResult.filename;
link.click();
}
🔟 智能字段映射写入
功能说明:结合字段检查、数据解析和批量写入的综合解决方案,实现从文本到表格的一站式智能导入,自动处理字段创建和数据类型转换。
/**
* 智能字段映射写入数据
* @description 综合解决方案:解析文本 → 检查字段 → 创建缺失字段 → 批量写入数据
* @param {Object} tableInstance - 表格实例对象
* @param {string} textData - 要导入的文本数据(制表符分隔)
* @param {Array<Object>} fieldMappings - 字段映射配置
* @param {string} fieldMappings[].name - 目标字段名
* @param {string} fieldMappings[].valueKey - 数据源字段名
* @param {FieldType} fieldMappings[].type - 字段类型
* @param {Object} fieldMappings[].property - 字段属性配置(可选)
* @param {Object} options - 导入选项
* @param {number} options.batchSize - 批次大小,默认50
* @param {boolean} options.autoCreateFields - 是否自动创建缺失字段,默认true
* @param {Function} options.onProgress - 进度回调函数
* @returns {Promise<Object>} 返回导入结果统计
* @example
* const textData = `项目名称\t负责人\t状态\t优先级
* 网站重构\t张三\t进行中\t高
* 移动端开发\t李四\t已完成\t中`;
*
* const fieldMappings = [
* { name: '项目名称', valueKey: '项目名称', type: FieldType.Text },
* { name: '负责人', valueKey: '负责人', type: FieldType.Text },
* {
* name: '状态',
* valueKey: '状态',
* type: FieldType.SingleSelect,
* property: {
* options: [
* { name: '进行中', color: 0 },
* { name: '已完成', color: 1 },
* { name: '待开始', color: 2 }
* ]
* }
* },
* {
* name: '优先级',
* valueKey: '优先级',
* type: FieldType.SingleSelect,
* property: {
* options: [
* { name: '高', color: 0 },
* { name: '中', color: 1 },
* { name: '低', color: 2 }
* ]
* }
* }
* ];
*
* const result = await smartFieldMappingImport(table, textData, fieldMappings, {
* batchSize: 30,
* onProgress: (current, total) => console.log(`导入进度: ${current}/${total}`)
* });
*/
export async function smartFieldMappingImport(tableInstance, textData, fieldMappings, options = {}) {
const {
batchSize = 50,
autoCreateFields = true,
onProgress
} = options
try {
// 第1步:解析文本数据
const parsedData = parseTabularTextToObjects(textData)
if (parsedData.length === 0) {
throw new Error('没有解析到有效数据')
}
// 第2步:检查并创建字段
if (autoCreateFields) {
const fieldConfigs = fieldMappings.map(mapping => ({
name: mapping.name,
type: mapping.type,
property: mapping.property
}))
await ensureFieldsExist(tableInstance, fieldConfigs)
}
// 第3步:准备字段映射配置
const tableFields = fieldMappings.map(mapping => ({
field_name: mapping.name,
field_value: mapping.valueKey
}))
// 第4步:批量写入数据
const recordIds = await chunkedAddRecords(tableInstance, parsedData, tableFields, {
batchSize,
onProgress
})
// 第5步:生成导入报告
const importResult = {
success: true,
totalRecords: parsedData.length,
importedRecords: recordIds.length,
failedRecords: parsedData.length - recordIds.length,
fieldCount: fieldMappings.length,
recordIds: recordIds,
summary: {
parseTime: new Date().toISOString(),
fieldsCreated: autoCreateFields ? fieldMappings.length : 0,
batchSize: batchSize
}
}
return importResult
} catch (error) {
return {
success: false,
error: error.message,
totalRecords: 0,
importedRecords: 0,
failedRecords: 0
}
}
}
使用示例:
import { FieldType } from '@lark-base-open/js-sdk';
// 从剪贴板或文件获取的数据
const projectData = `项目名称负责人状态优先级开始日期
网站重构张三进行中高2024-01-15
移动端开发李四已完成中2024-01-10
数据分析王五待开始低2024-01-20
API开发赵六进行中高2024-01-12`;
// 定义字段映射和类型
const fieldMappings = [
{
name: '项目名称',
valueKey: '项目名称',
type: FieldType.Text
},
{
name: '负责人',
valueKey: '负责人',
type: FieldType.Text
},
{
name: '状态',
valueKey: '状态',
type: FieldType.SingleSelect,
property: {
options: [
{ name: '进行中', color: 0 },
{ name: '已完成', color: 1 },
{ name: '待开始', color: 2 },
{ name: '已暂停', color: 3 }
]
}
},
{
name: '优先级',
valueKey: '优先级',
type: FieldType.SingleSelect,
property: {
options: [
{ name: '高', color: 0 },
{ name: '中', color: 1 },
{ name: '低', color: 2 },
{ name: '紧急', color: 3 }
]
}
},
{
name: '开始日期',
valueKey: '开始日期',
type: FieldType.DateTime
}
];
// 执行智能导入
const importResult = await smartFieldMappingImport(table, projectData, fieldMappings, {
batchSize: 20,
autoCreateFields: true,
onProgress: (current, total) => {
const percent = Math.round((current / total) * 100);
}
});
// 检查导入结果
if (importResult.success) {
console.log('导入详情:', importResult);
} else {
console.error('导入失败:', importResult.error);
}
// 一键导入Excel数据示例
async function quickImportFromClipboard() {
try {
// 从剪贴板读取数据
const clipboardText = await navigator.clipboard.readText();
// 自动识别字段并创建映射
const lines = clipboardText.split('\n');
const headers = lines[0].split('\t');
const autoFieldMappings = headers.map(header => ({
name: header.trim(),
valueKey: header.trim(),
type: FieldType.Text // 默认为文本类型,可根据需要调整
}));
// 执行导入
const result = await smartFieldMappingImport(table, clipboardText, autoFieldMappings);
} catch (error) {
console.error('快速导入失败:', error);
}
}
至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。