{
  "Name": "读取 Excel 并写入数据列表",
  "Description": "读取第一张工作表，根据列映射批量新增易企办记录，并返回确认写入的数量。",
  "ImplType": 0,
  "DraftCode": "// 保留原模板的输入和输出 ID，方便现有流程继续映射。\nfunction parseMappings(input) {\n  if (typeof input !== \"string\" || !input.trim()) throw new Error(\"请提供列映射 JSON。\");\n  let config;\n  try { config = JSON.parse(input); } catch { throw new Error(\"列映射不是有效的 JSON。\"); }\n  const entries = Array.isArray(config) ? config : config?.mappings;\n  if (!Array.isArray(entries) || !entries.length) throw new Error(\"请提供至少一条列映射。\");\n\n  const pick = (entry, keys) => keys.map((key) => entry?.[key])\n    .find((value) => typeof value === \"string\" && value.trim())?.trim();\n  const targets = new Set();\n  return entries.map((entry, index) => {\n    const column = pick(entry, [\"excelColumn\", \"column\", \"source\", \"sourceColumn\", \"ExcelColumn\"]);\n    const field = pick(entry, [\"yeeflowFieldName\", \"fieldName\", \"target\", \"targetField\", \"YeeflowFieldName\"]);\n    if (!column || !field || [\"__proto__\", \"constructor\", \"prototype\"].includes(field)) {\n      throw new Error(`第 ${index + 1} 条映射需要有效的 Excel 列名和目标字段。`);\n    }\n    if (targets.has(field)) throw new Error(`第 ${index + 1} 条映射重复指定目标字段。`);\n    targets.add(field);\n    const mapping = { column, field };\n    return mapping;\n  });\n}\n\nfunction isWritable(field) {\n  let rules = field?.Rules;\n  if (typeof rules === \"string\" && rules.trim()) {\n    try { rules = JSON.parse(rules); } catch { return false; }\n  }\n  return field?.ReadOnly !== true && field?.IsReadOnly !== true && rules?.readonly !== true && rules?.readOnly !== true;\n}\n\nfunction countAdded(result, requested) {\n  const data = result?.data;\n  const arrays = [data?.createdItems, result?.createdItems, data?.items, result?.items, data];\n  const created = arrays.find(Array.isArray);\n  const count = [result?.successCount, data?.successCount].find((value) => typeof value === \"number\") ?? created?.length;\n  if (result?.ok === false || !Number.isSafeInteger(count) || count < 0 || count > requested) {\n    throw new Error(\"无法确认写入数量。请先核对目标列表，再决定是否重试。\");\n  }\n  return count;\n}\n\nfunction textValue(value) {\n  if (value == null) return \"\";\n  if (value instanceof Date) {\n    if (!Number.isFinite(value.getTime())) return \"\";\n    // Excel 单元格使用工作簿中的本地日期与时间。\n    const pad = (part) => String(part).padStart(2, \"0\");\n    const date = `${String(value.getFullYear()).padStart(4, \"0\")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;\n    const time = [value.getHours(), value.getMinutes(), value.getSeconds()].map(pad).join(\":\");\n    return time === \"00:00:00\" ? date : `${date} ${time}`;\n  }\n  return String(value).trim();\n}\n\nexport async function main({ params, modules }) {\n  const fileInput = params?.[\"Excel file\"];\n  const files = Array.isArray(fileInput) ? fileInput : [fileInput];\n  const fileId = files.length === 1 && typeof files[0]?.id === \"string\" ? files[0].id.trim() : \"\";\n  if (!fileId) throw new Error(\"请上传一个有效的 Excel 文件。\");\n\n  const inputId = params?.[\"Data List ID\"];\n  const listId = typeof inputId === \"string\" ? inputId.trim() : \"\";\n  if (!/^[1-9]\\d{0,18}$/.test(listId)) throw new Error(\"请以文本传入有效的目标数据列表 ID。\");\n  const mappings = parseMappings(params?.[\"Mapping JSON\"]);\n\n  let file;\n  try { file = await modules.yeeSDKClient.files.getContent(fileId); }\n  catch { throw new Error(\"无法读取上传的 Excel 文件。请检查文件和访问权限。\"); }\n  if (!file?.data?.byteLength) throw new Error(\"Excel 文件内容为空。\");\n\n  // 沿用原模板所用的固定 SheetJS 版本。\n  try { await modules.loadScript(\"https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js\"); }\n  catch { throw new Error(\"无法加载 Excel 解析组件。请检查服务运行环境的访问设置。\"); }\n  const xlsx = globalThis.XLSX;\n  if (!xlsx?.read || !xlsx?.utils?.sheet_to_json) throw new Error(\"Excel 解析组件未就绪。\");\n\n  let grid;\n  try {\n    const workbook = xlsx.read(file.data, { type: \"array\", cellDates: true });\n    const firstSheet = workbook?.Sheets?.[workbook?.SheetNames?.[0]];\n    if (!firstSheet) throw new Error();\n    grid = xlsx.utils.sheet_to_json(firstSheet, { header: 1, raw: true, defval: \"\", blankrows: false });\n  } catch { throw new Error(\"无法读取第一张工作表。请检查 Excel 文件格式。\"); }\n  if (!Array.isArray(grid) || grid.length < 2) throw new Error(\"第一张工作表需要表头和至少一行数据。\");\n  const [first, ...rows] = grid;\n  const headers = first.map(textValue);\n  const positions = mappings.map(({ column }, index) => {\n    const position = headers.indexOf(column);\n    if (position < 0 || position !== headers.lastIndexOf(column)) {\n      throw new Error(`第 ${index + 1} 条映射对应的列名缺失或重复。`);\n    }\n    return position;\n  });\n\n  let fieldsResponse;\n  try { fieldsResponse = await modules.yeeSDKClient.lists.getFields(listId); }\n  catch { throw new Error(\"无法读取目标列表的字段。请检查列表 ID 和权限。\"); }\n  const metadata = fieldsResponse?.data;\n  const fields = Array.isArray(metadata) ? metadata : metadata?.fields ?? fieldsResponse?.fields;\n  if (!Array.isArray(fields) || !fields.length) throw new Error(\"未取得目标列表的字段定义。\");\n  const writable = new Set(fields.filter((field) => typeof field?.FieldName === \"string\" && isWritable(field))\n    .map((field) => field.FieldName.trim()));\n  for (const [index, { field }] of mappings.entries()) {\n    if (!writable.has(field)) throw new Error(`第 ${index + 1} 条映射的目标字段不存在或不可写。`);\n  }\n\n  const items = rows.map((row) => {\n    const pairs = mappings.map(({ field }, i) => [field, textValue(row[positions[i]])]);\n    return Object.fromEntries(pairs.filter(([, value]) => value !== \"\"));\n  }).filter((item) => Object.keys(item).length);\n  if (!items.length) throw new Error(\"映射列中没有可导入的数据。\");\n\n  let result;\n  try { result = await modules.yeeSDKClient.lists.createItemsBatch({ listId, items }); }\n  catch { throw new Error(\"批量写入失败或响应中断。请先核对目标列表，再决定是否重试。\"); }\n  const addedNumber = countAdded(result, items.length);\n  return { \"Added Number\": addedNumber };\n}\n",
  "DraftConfig": "{\"params\":[{\"id\":\"Excel file\",\"type\":\"file\",\"desc\":\"一个上传的 Excel 文件。\"},{\"id\":\"Data List ID\",\"type\":\"text\",\"desc\":\"目标列表 ID，保留为文本。\"},{\"id\":\"Mapping JSON\",\"type\":\"text\",\"desc\":\"Excel 列名到易企办内部字段名的 JSON 映射。\"}],\"connections\":[],\"outputs\":[{\"id\":\"Added Number\",\"type\":\"number\",\"desc\":\"确认新增的记录数。\"}]}",
  "ExtData": null
}
