夜雨聆风学习资料网

ARTICLE · 1269674

WPS多维表-链接转附件,可用来下图片,wps文档

WPS多维表-链接转附件,可用来下图片,wps文档
不多做介绍了,请看下面动图看效果。
支持多平台图片链接转附件
“在我主页或给我发「列表」二字,可用该功能
字符设置
字段名
字段类型
字段设置

链接

文本
无
附件
图片和件
无
转附件
按钮
与脚本关联
自动化设置
py脚本:
#!/usr/bin/env python3# -*- coding: utf-8 -*-import base64        # Base64 编码(图片转 dataUrl)import io            # 字节流缓冲区(图片转 dataUrl 前的临时存储)import json          # JSON 序列化(webhook 调用与日志)import re            # 文件名安全清理import requests      # HTTP 请求(下载图片 + 调用 AirScript webhook)import pandas as pd  # 金山内置:dbt() 返回的 DataFrame 判空用# ===================== 【配置区:webhook 脚本信息】=====================# 与「生成码」同一文档(「图片压缩」多维表),webhook 令牌通用;# 只需把新创建的「链接转附件写」脚本 ID 填进下方占位。文件ID = "******"                # ★ 本文档 ID(脚本 webhook 链接中提取,同 generate_code.py)脚本令牌 = "****"    # ★ AirScript 脚本令牌(盾牌图标生成)链接转附件写脚本ID = "V2-*****"  # ★「链接转附件写」脚本 ID(webhook 中提取)# =============================================================# ===================== 【参数配置区:表与字段】=====================数据表名 = "链接转附件"        # 多维表数据表名(与表名完全一致)链接字段 = "链接"        # 链接 URL 字段名(MultiLineText)附件字段 = "附件"        # 附件结果字段名(Attachment)入参_记录ID = "记录ID"    # 自动化流程中「记录ID」入参的键名# =============================================================# 下载时的浏览器指纹(防盗链站点通常检查 UA/Referer)下载请求头 = {    "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "                   "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"),    "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",    "Accept-Language": "zh-CN,zh;q=0.9",}# ===================== 【平台防盗链规则表】=====================# 主机名后缀 → 推荐 Referer(None = 该平台无需 Referer,直连即可)# 匹配规则:按 URL 域名后缀从前往后匹配,第一个命中即用平台防盗链规则 = [    ("mmbiz.qpic.cn", "https://mp.weixin.qq.com/"),   # 微信公众号文章图片    ("qpic.cn", "https://mp.weixin.qq.com/"),         # 微信系其他图片(头像/表情等)    ("sinaimg.cn", "https://weibo.com/"),             # 微博图片    ("baidu.com", "https://image.baidu.com/"),        # 百度图片搜索页    ("zhimg.com", "https://www.zhihu.com/"),          # 知乎图片    ("hdslb.com", "https://www.bilibili.com/"),       # B 站图片    ("xhscdn.com", "https://www.xiaohongshu.com/"),   # 小红书(多数仍要求登录态,仅尽力而为)    ("doubanio.com", "https://www.douban.com/"),      # 豆瓣    ("jianshu.io", "https://www.jianshu.com/"),       # 简书    ("byteimg.com", None),                            # 字节系(签名+过期链接,无需 Referer 直连)    ("aliyuncs.com", None),                           # 阿里云 OSS(通常直连)    ("myqcloud.com", None),                           # 腾讯云 COS(通常直连)    ("qcloudimg.com", None),                          # 腾讯云图片 CDN(通常直连)]# =============================================================# ===================== 【入参兼容处理】=====================# 编辑器内直接引用 Context 会报"未定义",但自动化运行时可用;此处兜底避免手动运行直接崩溃try:    Contextexcept NameError:    Context = {"argv": {}}def 取入参(主键名):    """从 Context['argv'] 取入参值:兼容数组形式(取第一个非空元素)。"""    if 主键名 not in Context["argv"]:        return None    值 = Context["argv"][主键名]    # 金山传参可能是数组(多值),取第一个非空元素    if isinstance(值, list):        return 值[0] if 值 else None    return 值def 读字段(数据表, 记录ID, 字段名):    """安全读取记录字段:字段在表中才读取,字段不存在返回 None 不报错。"""    if 字段名 not in 数据表.columns:        return None    return 数据表.loc[记录ID, 字段名]def 调用脚本(脚本ID, 入参, 脚本名="airscript"):    """    通过 APIToken 同步调用 AirScript 脚本(POST sync_task,与 generate_code.py 同款)    入参:        脚本ID   脚本 ID(webhook 路径中的 V2-xxxx)        入参     argv 字典(脚本的 Context.argv)        脚本名   用于报错提示的名称    返回:        脚本返回的 result(dict;若为字符串则尝试 JSON 解析)    """    接口地址 = "https://www.kdocs.cn/api/v3/ide/file/{}/script/{}/sync_task".format(文件ID, 脚本ID)    请求头 = {"Content-Type": "application/json", "AirScript-Token": 脚本令牌}    请求体 = {"Context": {"argv": 入参}}    响应 = requests.post(接口地址, json=请求体, headers=请求头, timeout=300)    响应.raise_for_status()    返回体 = 响应.json()    # 脚本执行出错 → 抛异常(含错误详情)    if 返回体.get("error"):        错误详情 = json.dumps(返回体.get("error_details") or {}, ensure_ascii=False)[:300]        raise RuntimeError("{} 执行出错: {} {}".format(脚本名, 返回体.get("error"), 错误详情))    if 返回体.get("status") != "finished":        raise RuntimeError("{} 未执行完毕".format(脚本名))    结果 = (返回体.get("data") or {}).get("result")    if isinstance(结果, dict):        return 结果    文本 = (结果 or "").strip()    try:        return json.loads(文本)    except Exception:        return {"ok": True, "raw": 文本}def 规范化URL(图片URL):    """    修正 URL 中 ? 的误用:百度图片链接常出现 f=JPEG?w=740 这种畸形写法    (? 应作参数分隔符 &)。若第一个 ? 之前已含 = 或 &(说明 ? 出现在参数中间),    把该 ? 替换为 & → f=JPEG&w=740。正常 URL(如 /path?id=1)不受影响。    """    问号位置 = 图片URL.find("?")    if 问号位置 > 0 and ("&" in 图片URL[:问号位置] or "=" in 图片URL[:问号位置]):        return 图片URL[:问号位置] + "&" + 图片URL[问号位置 + 1:]    return 图片URLdef 魔数识别图片类型(图片字节):    """    按文件头魔数识别真实图片格式(不依赖 Content-Type)。    fmt=auto 等场景下服务器返回格式多变,Content-Type 可能缺失或为 octet-stream,    用魔数兜底最可靠。返回 (mime, 后缀);识别不出返回 None。    """    if 图片字节[:8] == b'\x89PNG\r\n\x1a\n':        return ("image/png", "png")    if 图片字节[:3] == b'\xff\xd8\xff':        return ("image/jpeg", "jpg")    if 图片字节[:6] in (b'GIF87a', b'GIF89a'):        return ("image/gif", "gif")    if 图片字节[:4] == b'RIFF' and 图片字节[8:12] == b'WEBP':        return ("image/webp", "webp")    if 图片字节[:2] == b'BM':        return ("image/bmp", "bmp")    return None# 非图片文件:Content-Type → 扩展名(用于通用文件直链下载)文件类型映射 = {    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",    "application/vnd.ms-excel": "xls",    "application/pdf": "pdf",    "application/msword": "doc",    "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",    "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",    "application/vnd.ms-powerpoint": "ppt",    "application/zip": "zip",    "application/x-zip-compressed": "zip",    "text/csv": "csv",    "text/plain": "txt",}# 通用文件扩展名白名单:URL 带这些扩展名且响应类型未知时按扩展名命名通用文件扩展名 = {"xlsx", "xls", "pdf", "doc", "docx", "ppt", "pptx",                  "zip", "rar", "7z", "csv", "txt", "mp3", "mp4"}# 金山系域名:分享/文档网页链接(非文件直链)金山分享域名 = ("kdocs.cn", "wps.cn", "wps365.com")def 是金山分享链接(链接):    """    判断链接是否为金山文档/多维表/知识库的网页分享链接(如 www.kdocs.cn/l/xxx)。    这类链接无需下载:直接作为 DBCellValue 的 fileData(云文档链接)写入附件,    金山后端自行解析分享链接挂载(source=cloud 云引用,点开跳转文档)。    与签名直链的区别:签名直链(带 KSSAccessKeyId)会下载成实体文件;    分享网页链接落为云引用,无实体字节。    """    from urllib.parse import urlparse    try:        解析 = urlparse(链接)    except Exception:        return False    主机 = (解析.netloc or "").lower()    if not any(主机.endswith(域) for 域 in 金山分享域名):        return False    # 分享/文档页路径特征:/l/ /view/ /wiki/ /d/ /docx /sheet /ppt /x/ /w/ 等    路径 = (解析.path or "").lower()    return 路径.startswith(("/l/", "/view/", "/wiki/", "/d/", "/docx",                            "/sheet", "/ppt", "/x/", "/w/", "/api/", "/k/"))def 是金山签名直链(链接):    """    判断链接是否为金山对象存储的签名下载直链(带 KSSAccessKeyId + Signature)。    这类直链免登录可直接下载,也可作为 DBCellValue 的 fileData(云文档链接)直写附件,    无需下载转 base64。来源:WPS 客户端/网页「下载文件」导出的链接、    或开放平台 download 接口返回的直链(有效期通常 10 分钟,需及时写入)。    """    from urllib.parse import urlparse, parse_qs    try:        解析 = urlparse(链接)    except Exception:        return False    主机 = (解析.netloc or "").lower()    if not any(主机.endswith(域) for 域 in 金山分享域名):        return False    查询 = parse_qs(解析.query)    return "KSSAccessKeyId" in 查询 and "Signature" in 查询def 从签名直链取文件名(链接):    """    从签名直链的 response-content-disposition 参数解析文件名(如 filename*=utf-8''xxx.xlsx)。    解析失败则回退 URL 最后一段。返回带扩展名的文件名。    """    from urllib.parse import urlparse, parse_qs, unquote    try:        查询 = parse_qs(urlparse(链接).query)        cd = 查询.get("response-content-disposition", [""])[0]        if "filename*=" in cd:            return unquote(cd.split("filename*=")[1].split("''")[-1].split(";")[0])    except Exception:        pass    末尾 = 链接.rstrip("/").rsplit("/", 1)[-1].split("?")[0]    return 末尾 or "下载文件.bin"def 从金山分享链接取文件名(链接):    """    免登录识别金山分享链接对应的真实文件名(含扩展名,如 xxx.xlsx / xxx.docx / xxx.pdf)。    通道:GET https://drive.kdocs.cn/api/v5/links/{sid}(公开分享免登录可读,返回 fileinfo.fname)    分享链接可能是 Excel / Word / PDF 等任意类型,金山后端按 fileName 后缀记录附件 type,    所以必须拿到真实扩展名,避免类型错乱。    失败(网络异常/非公开分享)返回 None,由调用方兜底。    """    from urllib.parse import urlparse    try:        sid = (urlparse(链接).path or "").rstrip("/").rsplit("/", 1)[-1]        if not sid:            return None        响应 = requests.get("https://drive.kdocs.cn/api/v5/links/{}".format(sid),                          headers=下载请求头, timeout=15)        if 响应.status_code != 200:            return None        数据 = 响应.json()        文件名 = ((数据.get("fileinfo") or {}).get("fname") or "").strip()        return 文件名 or None    except Exception:        return Nonedef 下载文件(链接):    """    下载链接内容(图片 + 通用文件直链)→ (字节内容, 后缀)    - 金山分享网页链接(kdocs.cn /l/ 等)→ 提前识别并报"需登录态"    - 图片:平台防盗链规则 + URL 规范化 + 魔数识别(原 下载图片 全部能力)    - 非图片文件(xlsx/pdf/docx 等直链):按 Content-Type 或 URL 扩展名命名    防盗链应对:平台规则表命中 → 平台 Referer;未命中 → www.<主域>/<主域>/URL自身    返回: (bytes, 后缀);全部尝试失败抛 RuntimeError(含原因汇总)    """    from urllib.parse import urlparse    # 0) 金山分享网页链接预检(无法自动下载,明确提示原因)    if 是金山分享链接(链接):        raise RuntimeError(            "金山分享/文档链接需要登录态,云端脚本无法直接下载。"            "请改为文件直链(如 xlsx 的下载直链),或让豆包用浏览器辅助下载落表")    # 1) 图片后缀映射    图片后缀映射 = {        "image/png": "png", "image/jpeg": "jpg", "image/jpg": "jpg",        "image/webp": "webp", "image/gif": "gif", "image/bmp": "bmp",    }    # 2) 按 URL 域名生成候选 Referer:    #    平台规则表命中 → 平台专用 Referer 优先(None 表示无需 Referer)    #    未命中 → 通用:www.<主域> / <主域> / 图片 URL 自身    主机 = (urlparse(链接).netloc or "").lower()    主机部分 = 主机.split(".")    主域 = ".".join(主机部分[-2:]) if len(主机部分) >= 2 else 主机    平台Referer = None    for 域名后缀, 推荐Referer in 平台防盗链规则:        if 主机.endswith(域名后缀):            平台Referer = 推荐Referer            break    if 平台Referer is not None:        Referer候选 = [平台Referer, 链接]    else:        Referer候选 = [            "https://www.{}/".format(主域),            "https://{}/".format(主域),            链接,        ]    # 3) URL 变体:原样 + 规范化(畸形 ? 修正)    URL变体 = [链接, 规范化URL(链接)]    失败原因集合 = []    for 变体 in URL变体:        for Referer in Referer候选:            try:                当前头 = dict(下载请求头)                if Referer:                    当前头["Referer"] = Referer                响应 = requests.get(变体, headers=当前头, timeout=20)                if 响应.status_code != 200:                    失败原因集合.append("HTTP {}".format(响应.status_code))                    continue                内容字节 = 响应.content                内容类型 = (响应.headers.get("Content-Type") or "").lower().split(";")[0]                # 4) 图片判定:Content-Type → 魔数                if 内容类型 in 图片后缀映射:                    return 内容字节, 图片后缀映射[内容类型]                魔数结果 = 魔数识别图片类型(内容字节)                if 魔数结果:                    return 内容字节, 魔数结果[1]                # 5) 文件判定:Content-Type 映射 → URL 扩展名白名单                if 内容类型 in 文件类型映射:                    return 内容字节, 文件类型映射[内容类型]                # URL 扩展名兜底(octet-stream 等未知类型时)                URL末尾 = 变体.rstrip("/").rsplit("/", 1)[-1].split("?")[0].split("#")[0]                URL扩展名 = URL末尾.rsplit(".", 1)[-1].lower() if "." in URL末尾 else ""                if URL扩展名 in 通用文件扩展名:                    return 内容字节, URL扩展名                失败原因集合.append("响应类型无法识别为图片或文件(Content-Type={})".format(内容类型 or "未知"))            except Exception as 异常:                失败原因集合.append(str(异常)[:80])    # 6) 去重原因后汇总报错    原因去重 = list(dict.fromkeys(失败原因集合))[:4]    raise RuntimeError("下载失败(已尝试 {} 种组合):{}".format(        len(URL变体) * len(Referer候选), ";".join(原因去重)))def 文件转DataUrl(字节, 后缀):    """文件/图片字节 → data URI 字符串(mime 按后缀查表,供写附件脚本使用)"""    mime表 = {        # 图片        "png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg",        "webp": "image/webp", "gif": "image/gif", "bmp": "image/bmp",        # 办公文档        "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",        "xls": "application/vnd.ms-excel",        "pdf": "application/pdf",        "doc": "application/msword",        "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",        "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",        "ppt": "application/vnd.ms-powerpoint",        # 压缩包 / 文本 / 音频视频        "zip": "application/zip", "rar": "application/x-rar-compressed", "7z": "application/x-7z-compressed",        "csv": "text/csv", "txt": "text/plain",        "mp3": "audio/mpeg", "mp4": "video/mp4",    }    mime = mime表.get(后缀, "application/octet-stream")    return "data:{};base64,{}".format(mime, base64.b64encode(字节).decode("ascii"))def 清理文件名(名称):    """去掉文件名中的非法字符(Windows 不允许 \\ / : * ? \" < > |)"""    return re.sub(r'[\\/:*?"<>|]', '_', 名称)def 拆分链接列表(文本):    """    把「链接」字段文本拆成多个图片 URL。    分隔符自动识别(全部兼容,可混用):      ① 换行(每行一个链接)—— 推荐,MultiLineText 天然支持多行      ② 英文逗号 ,  / 中文逗号 ,      ③ 英文分号 ; / 中文分号 ;      ④ 空白(空格/Tab)    只保留 http(s) 开头的有效链接,其余片段(如说明文字)自动忽略。    """    链接们 = []    for 行 in str(文本).replace("\r", "\n").split("\n"):        # 每行再按常见符号拆分(逗号/分号/空白)        片段们 = re.split(r'[,;,;\s]+', 行.strip())        for 片段 in 片段们:            片段 = 片段.strip()            if 片段.startswith("http://") or 片段.startswith("https://"):                链接们.append(片段)    return 链接们def 主函数():    """读取入参 → 定位记录 → 拆分多链接 → 逐个下载转 dataUrl → 调写脚本一次落多附件。"""    # 第一步:读取自动化入参(仅记录ID,链接从记录读取)    记录ID = 取入参(入参_记录ID)    if not 记录ID:        return {"错误": "未收到「%s」入参,请在自动化流程脚本入参中配置" % 入参_记录ID}    print("入参:记录ID=%s" % 记录ID)    # 第二步:按记录ID 定位该行,读取「链接」字段(可含多个图片 URL)    数据表 = dbt(sheet_name=数据表名)    if 记录ID not in 数据表.index:        return {"错误": "记录ID %s 在表「%s」中不存在" % (记录ID, 数据表名)}    链接值 = 读字段(数据表, 记录ID, 链接字段)    if pd.isna(链接值) or str(链接值).strip() == "":        return {"错误": "记录 %s 的「%s」为空,请先填写图片链接" % (记录ID, 链接字段)}    # 拆分多链接:换行/逗号/分号/空格 自动识别    链接列表 = 拆分链接列表(链接值)    if not 链接列表:        return {"错误": "「%s」字段中未识别到有效 http(s) 链接,请检查填写格式" % 链接字段}    print("识别到 %d 个链接:%s" % (len(链接列表), " / ".join(链接列表)))    # 第三步:逐个下载图片(防盗链多重尝试;单个失败不中断,收集明细)    成功项 = []      # [{fileData(dataUrl), fileName}]    失败明细 = []    已用文件名 = set()  # 同批重名时追加序号,避免相互覆盖    for 序号, 图片URL in enumerate(链接列表, start=1):        # 分支一:金山签名直链 → 作为 fileData(云文档链接)直写,无需下载转 base64        if 是金山签名直链(图片URL):            try:                文件名 = 从签名直链取文件名(图片URL)                dataUrl = 图片URL  # 直链即 fileData                图片后缀 = 文件名.rsplit(".", 1)[-1].lower() if "." in 文件名 else "bin"                print("[%d/%d] 金山签名直链,直写:%s → %s" % (序号, len(链接列表), 图片URL[:80], 文件名))            except Exception as 异常:                失败明细.append({"链接": 图片URL, "原因": "直链解析失败:" + str(异常)[:80]})                print("[%d/%d] 直链解析失败:%s" % (序号, len(链接列表), str(异常)[:80]))                continue        # 分支二:金山分享网页链接 → 直接作为 fileData(云引用)直写,        #        金山后端自行解析分享链接挂载附件(source=cloud 云引用,点开跳转文档)        elif 是金山分享链接(图片URL):            真实文件名 = 从金山分享链接取文件名(图片URL)            if 真实文件名:                文件名 = 清理文件名(真实文件名)  # 真实名带正确扩展名(.xlsx/.docx/.pdf)                print("[%d/%d] 金山分享链接,云引用直写(识别类型:%s)" %                      (序号, len(链接列表), 文件名))            else:                # 识别失败兜底:无后缀命名(避免误导 type),金山后端仍按链接挂载                from urllib.parse import urlparse                sid = (urlparse(图片URL).path or "").rstrip("/").rsplit("/", 1)[-1] or "分享文档"                文件名 = "金山分享文档-{}".format(sid)                print("[%d/%d] 金山分享链接,云引用直写(类型识别失败,兜底命名)" %                      (序号, len(链接列表)))            dataUrl = 图片URL  # 分享链接即 fileData        # 分支三:普通链接 → 下载后转 dataUrl(图片/文件通用)        else:            try:                图片字节, 图片后缀 = 下载文件(图片URL)            except Exception as 异常:                失败明细.append({"链接": 图片URL, "原因": str(异常)[:120]})                print("[%d/%d] 下载失败:%s(%s)" % (序号, len(链接列表), 图片URL, str(异常)[:80]))                continue            # 转 data URI + 文件名(去重扩展名;同批重名加序号)            dataUrl = 文件转DataUrl(图片字节, 图片后缀)            基础名 = 图片URL.rstrip("/").rsplit("/", 1)[-1].split("?")[0]            基础名去扩展 = 基础名.rsplit(".", 1)[0] if "." in 基础名 else 基础名            候选名 = 清理文件名("{}.{}".format(基础名去扩展 or "图片", 图片后缀))            文件名 = 候选名            print("[%d/%d] 已下载:%s → %s(%d 字节)" %                  (序号, len(链接列表), 图片URL, 文件名, len(图片字节)))        # 同批重名加序号,避免相互覆盖        序号计数 = 2        最终文件名 = 文件名        while 最终文件名 in 已用文件名:            根名 = 文件名.rsplit(".", 1)[0] if "." in 文件名 else 文件名            扩展名 = 文件名.rsplit(".", 1)[1] if "." in 文件名 else ""            最终文件名 = "{}_{}.{}".format(根名, 序号计数, 扩展名) if 扩展名 else "{}_{}".format(根名, 序号计数)            序号计数 += 1        已用文件名.add(最终文件名)        成功项.append({"fileData": dataUrl, "fileName": 最终文件名})    # 全部失败 → 明确报错    if not 成功项:        return {"错误": "所有链接下载失败",                "detail": ";".join("%s: %s" % (项["链接"][:60], 项["原因"]) for 项 in 失败明细)[:300]}    # 第四步:调「链接转附件写」脚本:一次写入全部附件(表「替换」写「附件」字段)    if not 链接转附件写脚本ID:        return {"错误": "未配置「链接转附件写」脚本ID",                "detail": "请在多维表脚本 IDE 创建 airscript_link_to_attachment_write.js 并填入配置区"}    写入入参 = {"record_id": 记录ID, "files": 成功项}    try:        写入结果 = 调用脚本(链接转附件写脚本ID, 写入入参, "链接转附件写")    except Exception as 异常:        return {"错误": "写入附件失败", "detail": "链接转附件写 脚本调用异常:" + str(异常)[:200]}    if not 写入结果 or not 写入结果.get("ok"):        return {"错误": "写入附件失败", "detail": json.dumps(写入结果, ensure_ascii=False)[:200]}    写入数 = 写入结果.get("count") or len(成功项)    print("已写入 %d 个附件到「%s」字段" % (写入数, 附件字段))    # 返回结果字典,自动化流程日志可查看    return {        "记录ID": 记录ID,        "识别链接数": len(链接列表),        "成功下载数": len(成功项),        "失败数": len(失败明细),        "失败明细": 失败明细[:5],        "写入附件数": 写入数,    }# 脚本直接运行时执行主函数(被 import 时不执行)if __name__ == "__main__":    print(主函数())
airscript脚本
// =========================================================================// AirScript「链接转附件写」v1:把 PY 传入的图片 dataUrl 写回「附件」字段// (由「生成码写」v1 改造:表名换为「替换」,目标字段换为「附件」)// 入参(PY 通过 sync_task 传入,接口键保持英文)://   record_id : 记录 ID//   files     : [{fileData, fileName}, ...](一次写回「附件」附件字段)// 返回(接口键保持英文):{ ok, count }// 兼容:1.0 环境全局 DBCellValue 可用;2.0 需 Application.DBCellValue → 自动适配// =========================================================================function main() {  // ---------- 读取入参 ----------  // 金山运行时注入全局 Context;本地调试/直接运行可能没有 → 兜底空对象  var 运行上下文 = (typeof Context !== 'undefined' && Context.argv) ? Context.argv : {};  // 入参:记录 ID(兼容数组形式,取第一个元素)  var 记录ID原始 = 运行上下文.record_id;  var 记录ID = Array.isArray(记录ID原始) ? 记录ID原始[0] : 记录ID原始;  // 没有记录 ID → 直接返回错误  if (!记录ID) { return { ok: false, error: 'no record_id' }; }  // 入参:文件列表(兼容三种形态:JSON 字符串 / 单个对象 / 数组)  var 文件列表 = 运行上下文.files;  // 字符串 → 尝试 JSON 解析;解析失败按单对象处理  if (typeof 文件列表 === 'string') {    try { 文件列表 = JSON.parse(文件列表); } catch (错误) { 文件列表 = [文件列表]; }  }  // 非数组 → 包成数组(单图场景)  if (!Array.isArray(文件列表)) { 文件列表 = [文件列表]; }  // ---------- 清理文件列表:只保留 {fileData, fileName} ----------  // 无文件数据的项直接丢弃(防止空 Base64 写坏附件)  var 干净列表 = [];  for (var 索引 = 0; 索引 < 文件列表.length; 索引++) {    // 当前项(兼容空项 null)    var 单项 = 文件列表[索引] || {};    // 兼容「fileData / dataUrl」两种键    if (单项.fileData || 单项.dataUrl) {      干净列表.push({        fileData: 单项.fileData || 单项.dataUrl,  // Base64 dataUrl 字符串        fileName: 单项.fileName || '图片.png'      // 附件文件名(缺省兜底)      });    }  }  // 没有有效数据 → 返回错误(避免写空附件)  if (干净列表.length === 0) { return { ok: false, error: 'no valid fileData' }; }  // ---------- 写回图片 ----------  // 自动适配 DBCellValue:1.0 环境全局函数;2.0 需 Application 前缀  var 写附件函数 = (typeof DBCellValue === 'function') ? DBCellValue : Application.DBCellValue;  // 打开数据表「替换」(链接转附件专用表名)  var 数据表 = Application.Sheets('链接转附件');  // 写回「附件」附件字段(一次写入全部图片)  数据表.RecordRange(记录ID, '@附件').Value = 写附件函数(干净列表);  // ---------- 返回结果给 PY(接口键保持英文) ----------  return {    ok: true,                  // 是否成功    count: 干净列表.length      // 写入的图片数量  };}return main();

相关学习资料