【前端倒霉蛋--word导出】

前端导出word,如果时间能重来,我一定交给后端。
这部分的时间我养生养生多好!!!

心得:边查边测试的,想实现的效果与该用什么方法去实现对不上;思路:封装一个方法去获取标签的样式,这里需要注意要和word的渲染格式能对应得上;然后通过对应的标签分别处理;比较恶心的是比如children中写样式,但是居中等却和children平级位置写;富文本内容存在多个标签包裹,有的样式又需要通过标签来标识;以及表格表头有的是在th中,有的却是在td中。

以下代码将就看吧,不会再前端来实现了。
插件使用的是docx

/* eslint-disable */
import {
    Document,
    Packer,
    Paragraph,
    AlignmentType,
    VerticalAlign,
    ShadingType,
    Table,
    TextRun,
    TableCell,
    TableRow,
    HeadingLevel,
    WidthType,
    BorderStyle,
    ImageRun
} from 'docx';
import { message } from 'antd';
import { saveAs } from 'file-saver';

const getCellStyle = (cell) => {
    const style = cell.style; // 获取单元格的样式

    // 获取对齐方式
    let alignment;
    if (style.textAlign) {
        switch (style.textAlign) {
            case 'left':
                alignment = AlignmentType.LEFT;
                break;
            case 'center':
                alignment = AlignmentType.CENTER;
                break;
            case 'right':
                alignment = AlignmentType.RIGHT;
                break;
            default:
                alignment = AlignmentType.LEFT; // 默认对齐
        }
    } else {
        alignment = AlignmentType.LEFT; // 默认对齐
    }

    // 获取字体大小和颜色
    const fontSize = style.fontSize ? parseInt(style.fontSize) : 12; // 默认字体大小
    const color = style.color ? style.color.replace('#', '') : '000000'; // 默认字体颜色

    // 获取是否粗体和下划线
    const isBold = style.fontWeight === 'bold' || style.fontWeight === '700';
    const isUnderline = style.textDecoration === 'underline';

    return {
        alignment,
        fontSize,
        color,
        isBold,
        isUnderline
    };
};

const extractTableRows = (html) => {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const rows = Array.from(doc.querySelectorAll('tbody tr'));

    const headers = Array.from(doc.querySelectorAll('thead th')).map(th => {
        const { alignment, fontSize, color, isBold, isUnderline } = getCellStyle(th); // 获取样式

        return new TableCell({
            children: [
                new Paragraph({
                    text: th.innerText,
                    alignment: alignment,
                    style: {
                        font: {
                            size: fontSize,
                            color: color,
                            bold: isBold,
                            underline: isUnderline
                        },
                    },
                }),
            ],
            verticalAlign: VerticalAlign.CENTER,
        });
    });

    // 创建表头行
    const headerRow = new TableRow({
        children: headers
    });

    let tableRows = []; // 初始化时只加入表头行
    if (headers && headers.length) {
        tableRows = [headerRow];
    }

    // 提取每一行数据
    rows.forEach(row => {
        const cells = Array.from(row.querySelectorAll('td'));

        // 处理可能存在的空行
        if (cells.length === 0) return;

        const tableRow = new TableRow({
            children: cells.map(cell => {
                const text = cell.innerText.trim();
                const { alignment, fontSize, color, isBold, isUnderline } = getCellStyle(cell); // 获取样式

                return new TableCell({
                    children: [
                        new Paragraph({
                            text: text,
                            alignment: alignment,
                            style: {
                                font: {
                                    size: fontSize,
                                    color: color,
                                    bold: isBold,
                                    underline: isUnderline
                                },
                            },
                        }),
                    ],
                    verticalAlign: VerticalAlign.CENTER,
                });
            }),
        });

        // 仅添加数据行
        tableRows.push(tableRow);
    });

    return tableRows;
};

const extractContent = async (html) => {
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const imgElement = doc.querySelector('img');
    const content = [];
    if (imgElement) {
        const currentOrigin = window.location.origin;
        const imageUrl = imgElement.src;
        const updatedImageUrl = imageUrl.replace(图片url, `${currentOrigin}`);
        try {
            const response = await fetch(updatedImageUrl);
            if (!response.ok) {
                throw new Error(`HTTP error! Status: ${response.status}`);
            }
            const imageBlob = await response.blob();
            const reader = new FileReader();

            await new Promise((resolve, reject) => {
                reader.onloadend = () => {
                    const base64Data = reader.result.split(',')[1]; // 取出 Base64 部分
                    if (base64Data) {
                        // 创建 ImageRun 实例并添加到内容中
                        content.push(new ImageRun({
                            data: base64Data,
                            transformation: { width: imgElement?.width || 600, height: imgElement?.height || 200 },
                        }));
                    } else {
                        console.error('Base64 data is empty.');
                    }
                    resolve();
                };
                reader.onerror = (error) => {
                    console.error('Error reading image file:', error);
                    reject(error);
                };
                reader.readAsDataURL(imageBlob);
            });
        } catch (error) {
            console.error('Error fetching image:', error);
        }
    }

    return content;
};

function rgbToHex(rgb) {
    const rgbValues = rgb.match(/\d+/g);
    if (!rgbValues) return null;

    return `#${((1 << 24) + (parseInt(rgbValues[0]) << 16) + (parseInt(rgbValues[1]) << 8) + parseInt(rgbValues[2])).toString(16).slice(1)}`;
}

function extractStylesFromHtml(html) {
    const tempDiv = document.createElement('div');
    tempDiv.appendChild(html);
    document.body.appendChild(tempDiv);
    tempDiv.style.display = 'none';

    let results = {
        color: null,
        backgroundColor: null,
        fontSize: null,
        bold: false,
        underline: false,
        italic: false,
        listType: null, // 用于识别列表类型
        shading: {
            type: "ShadingType.CLEAR",
            fill: "#FFFFFF",
            color: "#FFFFFF"
        },
        textAlign: null,
        indentation: 0,// 默认缩进
        hasIndent: false// 是否有缩进

    };
    function recursiveExtract(element) {
        if (element.nodeType === Node.TEXT_NODE) {
            const textContent = element.textContent.trim();
            if (textContent) {
            }
        } else if (element.nodeType === Node.ELEMENT_NODE) {
            const computedStyles = window.getComputedStyle(element);
            const backgroundColor = rgbToHex(computedStyles.backgroundColor);
            // 如果计算得出的背景颜色是 #000000,并且父元素的背景颜色也不是黑色,则认为是无效的背景色
            const isDefaultBlack = (backgroundColor === '#000000' && rgbToHex(computedStyles.color) !== '#ffffff') ? '#ffffff' : backgroundColor;
            const currentStyle = {
                color: rgbToHex(computedStyles.color) || undefined,
                backgroundColor: isDefaultBlack,
                size: parseInt(parseInt(computedStyles.fontSize) / 1.33) + "pt",
                shading: {
                    type: ShadingType.CLEAR,
                    fill: isDefaultBlack,
                    color: 'FFFFFF',
                },
                textAlign: computedStyles.textAlign,

            };
            results = { ...results, ...currentStyle };
            if (computedStyles.fontWeight === 'bold' || parseInt(computedStyles.fontWeight) > 400 || element.nodeName === 'STRONG') {
                results.bold = true;
            }
            if (computedStyles.fontStyle === 'italic' || element.nodeName === 'EM') {
                results.italic = true;
            }
            if (computedStyles.textDecoration.includes('underline')) {
                results.underline = true;
            }
            if (computedStyles.textIndent) {
                const emValue = parseFloat(computedStyles.textIndent);
                results.indentation = emValue * 16; // 将 em 转换为像素
                results.hasIndent = true; // 有缩进
            }

            // 处理有序和无序列表
            if (element.nodeName === 'UL') {
                results.listType = 'unordered'; // 标记为无序列表
                for (const child of element.children) {
                    if (child.nodeName === 'LI') {
                        // 对每个列表项进行递归提取
                        for (const liChild of child.childNodes) {
                            recursiveExtract(liChild);
                        }
                    }
                }
            } else if (element.nodeName === 'OL') {
                results.listType = 'ordered'; // 标记为有序列表
                for (const child of element.children) {
                    if (child.nodeName === 'LI') {
                        // 对每个列表项进行递归提取
                        for (const liChild of child.childNodes) {
                            recursiveExtract(liChild);
                        }
                    }
                }
            } else {
                for (const child of element.childNodes) {
                    recursiveExtract(child);
                }
            }
        }
    }
    recursiveExtract(tempDiv);
    document.body.removeChild(tempDiv);
    return results;
}

const parseHtmlToParagraphs = (html, type) => {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const paragraphs = Array.from(doc.querySelectorAll(type));
    return paragraphs.map((p) => {
        const htmlStyles = extractStylesFromHtml(p);
        // 设置缩进属性
        const indent = htmlStyles?.hasIndent ? { left: htmlStyles?.indentation, right: 0 } : { left: 0, right: 0 };
        const textRuns = [];
        // 处理每个子节点以构建文本运行
        Array.from(p.childNodes).forEach((child, inx) => {
            if (child.nodeType === Node.TEXT_NODE) {
                textRuns.push(new TextRun(child.nodeValue));
            } else if (child.nodeType === Node.ELEMENT_NODE) {
                // 获取文本内容
                const textContent = child.textContent;
                // 创建 TextRun 对象并设置样式
                const textRun = new TextRun({
                    text: htmlStyles?.listType ? htmlStyles?.listType == "unordered" ? `\u2022${textContent}` : `${inx + 1}.${textContent}` : textContent,
                    ...htmlStyles
                });
                textRuns.push(textRun);
                if (htmlStyles?.listType) {
                    textRuns.push(new TextRun({ text: '', break: true }));
                }
            }
        });

        // 根据 HTML 样式设置段落对齐方式
        let alignment;
        if (htmlStyles?.textAlign) {
            switch (htmlStyles.textAlign) {
                case 'center':
                    alignment = AlignmentType.CENTER;
                    break;
                case 'right':
                    alignment = AlignmentType.RIGHT;
                    break;
                case 'justify':
                    alignment = AlignmentType.JUSTIFIED;
                    break;
                default:
                    alignment = AlignmentType.LEFT; // 默认左对齐
            }
        } else {
            alignment = AlignmentType.LEFT; // 如果没有指定,默认为左对齐
        }

        // 创建包含所有文本运行的段落
        return new Paragraph({
            children: textRuns,
            alignment: alignment,
            indent: indent
        });
    });
};

// 解析 HTML 中的 blockquote
const parseHtmlBlockQuote = (html) => {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    return Array.from(doc.querySelectorAll('blockquote')).map((blockquote) => {
        const textRuns = Array.from(blockquote.childNodes).map((node) => {
            return new TextRun({
                text: node.nodeType === Node.TEXT_NODE ? node.textContent : node.innerText,
                color: "666666", // 字体颜色
                italics: true, // 设置斜体
                // break: true, // 换行
            });
        });

        return new Paragraph({
            children: textRuns,
            alignment: AlignmentType.LEFT, // 左对齐
            border: {
                left: {
                    style: BorderStyle.SINGLE,
                    size: 30,
                    space: 0,
                    color: "CCCCCC",
                },
            },
            shading: {
                fill: "F1F2F3", // 背景颜色
                color: "FFFFFF", // 可选
                type: ShadingType.CLEAR
            }
        });
    });
};

const parseHtmlToTable = (html) => {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const headers = Array.from(doc.querySelectorAll('thead th')).map(th =>
        new TableCell({ children: [new Paragraph(th.innerText)] })
    );
    const rows = Array.from(doc.querySelectorAll('tbody tr')).map(row =>
        new TableRow({
            children: Array.from(row.querySelectorAll('td')).map(cell =>
                new TableCell({ children: [new Paragraph(cell.innerText)] })
            ),
        })
    );

    return new Table({
        rows: [new TableRow({ children: headers })].concat(rows),
    });
};

const parseHtmlToCodeBlock = (html) => {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    return Array.from(doc.querySelectorAll('pre')).map((pre) => {
        const codeContent = pre.innerText || pre.textContent;
        // 创建段落并包含超链接
        return new Paragraph({
            children: [
                new TextRun({
                    text: codeContent,
                    hyperlink: codeContent,
                    color: "0000FF",
                    underline: true
                })
            ],
            spacing: { before: 240, after: 240 },
        });
    });
};

const parseHtmlToDocumentElements = (html) => {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const elements = [];

    doc.body.childNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
            switch (node.tagName) {
                case 'P':
                    elements.push(...parseHtmlToParagraphs(node.outerHTML, 'p'));
                    break;
                case 'DIV':
                    elements.push(...parseHtmlToParagraphs(node.outerHTML, 'div'));
                    break;
                case 'TABLE':
                    elements.push(parseHtmlToTable(node.outerHTML));
                    break;
                case 'H2':
                    elements.push(new Paragraph({
                        text: node.textContent,
                        heading: 'HEADING_2',
                    }));
                    break;
                case 'OL':
                    elements.push(...parseHtmlToParagraphs(node.outerHTML, 'ol'));
                    break;
                case 'UL':
                    elements.push(...parseHtmlToParagraphs(node.outerHTML, 'ul'));
                    break;
                case 'PRE':
                    elements.push(...parseHtmlToCodeBlock(node.outerHTML));
                    break;
                case 'BLOCKQUOTE':
                    elements.push(...parseHtmlBlockQuote(node.outerHTML));
                    break;
                case 'STRONG':
                case 'B':
                    elements.push(new Paragraph({
                        text: node.textContent,
                        bold: true,
                    }));
                    break;
                case 'EM':
                case 'I':
                    elements.push(new Paragraph({
                        text: node.textContent,
                        italics: true,
                    }));
                    break;
                case 'SPAN':
                    elements.push(new Paragraph({
                        text: node.textContent
                    }));
                    break;
                default:
                    // 处理未指定的标签,可以选择忽略或者将内容添加为文本段落
                    elements.push(new Paragraph({
                        text: node.textContent,
                    }));
                    break;
            }
        }
    });

    return elements;
};


export const createWordDocument = async (reportLayoutInfoParamList, reportName = '分析报告') => {
    message.loading('报告文件下载中...', 1);
    const wordData = [];
    const sortedData = reportLayoutInfoParamList.sort((a, b) => a.y - b.y);

    for (const item of sortedData) {
        try {
            const html = item.content?.originalEditorHtml || '';
            if (!html) {
                console.warn('无效内容: 文件为空');
                continue; // 跳过无效内容
            }

            // 检查是否包含 chart-img 来判断是添加图片还是表格
            const imgElement = new DOMParser().parseFromString(html, 'text/html').querySelector('img[alt="chart-img"]');
            if (imgElement || item?.contentType === 'media') {
                // 提取图片
                const imageRuns = await extractContent(html);
                imageRuns.forEach(run => {
                    wordData.push(
                        new Paragraph({
                            children: [run]
                        }),
                        new Paragraph({ text: "" })
                    );
                });
            } else if (item?.contentType === 'text') {
                // 解析 HTML 并获取文档元素
                const documentElements = parseHtmlToDocumentElements(html);
                if (documentElements.length === 0) {
                    console.warn('未提取到文本内容');
                    continue; // 跳过未提取到内容的情况
                }
                documentElements.forEach(element => {
                    wordData.push(element);
                });
            } else {
                // 提取表格行
                const tableRows = extractTableRows(html);
                if (tableRows.length > 0) {
                    wordData.push(
                        new Paragraph({
                            text: "",
                            heading: HeadingLevel.HEADING_1,
                        }),
                        new Table({
                            width: {
                                size: 100,
                                type: WidthType.PERCENTAGE,
                            },
                            rows: tableRows,
                        }),
                        new Paragraph({ text: "" })
                    );
                } else {
                    console.warn('未提取到表格内容');
                }
            }
        } catch (error) {
            console.error('处理条目时发生错误:', error);
        }
    }

    if (wordData.length === 0) {
        message.warning('未生成任何文档内容,无法导出 Word 文件');
        return;
    }

    try {
        const doc = new Document({
            sections: [{
                properties: {},
                children: wordData,
            }],
        });
        // return;
        const blob = await Packer.toBlob(doc);
        saveAs(blob, `${reportName}.docx`);
    } catch (error) {
        message.error('导出 Word 文件时发生错误:' + error);
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900736.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

合约门合同全生命周期管理系统:企业合同管理的数字化转型之道

合约门合同全生命周期管理系统&#xff1a;企业合同管理的数字化转型之道 1. 引言 在现代企业中&#xff0c;合同管理已经不再是简单的文件存储和审批流程&#xff0c;而是企业合规性、风险管理和业务流程的关键环节之一。随着企业规模的扩大和合同数量的增加&#xff0c;传统…

第二单元历年真题整理

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 参考答案 1. A 2. A 3. A 4. D 5. D 6. D 解析&#xff1a; 栈和队列是两个不一样的结构&#xff0c;不能放在一起表示 7. B 8. C 解析&#xff1a; S --> A0 | B1 --> (S1 | 1) 0 | (S0 | 0)1 --> S10 | 10 | S…

51单片机快速入门之 模拟 I2C 用精准中断来控制

51单片机快速入门之 模拟 I2C 用精准中断来控制 首先复习一下51单片机快速入门之定时器和计数器(含中断基础) 再看看之前的I2C操作 51单片机快速入门之 IIC I2C通信 定时器/计数器是51单片机中用于实现精确延时的硬件资源。通过配置定时器的初始值和工作模式&#xff0c;可以…

Unable to open nested entry ‘********.jar‘ 问题解决

今天把现网版本的task的jar拖回来然后用7-zip打开拖了一个jar进去替换mysql-connector-java-5.1.47.jar 为 mysql-connector-java-5.1.27.jar 启动微服务的时候就报错下面的 Exception in thread "main" java.lang.IllegalStateException: Failed to get nested ar…

《Python游戏编程入门》注-第2章2

《Python游戏编程入门》的“2.2.5 绘制线条”中提到了通过pygame库绘制线条的方法。 1 相关函数介绍 通过pygame.draw模块中的line()函数来绘制线条&#xff0c;该函数的格式如下所示。 line(surface, color, start_pos, end_pos, width1) -> Rect 其中&#xff0c;第一…

开源限流组件分析(二):uber-go/ratelimit

文章目录 本系列漏桶限流算法uber的漏桶算法使用mutex版本数据结构获取令牌松弛量 atomic版本数据结构获取令牌测试漏桶的松弛量 总结 本系列 开源限流组件分析&#xff08;一&#xff09;&#xff1a;juju/ratelimit开源限流组件分析&#xff08;二&#xff09;&#xff1a;u…

部署前后端分离若依项目--CentOS7宝塔版

准备&#xff1a; CentOS7服务器一台 通过网盘分享的文件&#xff1a;CentOS 7 h 链接: https://pan.baidu.com/s/17DF8eRSSDuj9VeqselGa_Q 提取码: s7x4 大家有需要可以下载这个&#xff0c;密码61 若依前端编译后文件 通过网盘分享的文件&#xff1a;ruoyi-admin.jar 链…

生信软件39 - GATK最佳实践流程重构,提高17倍分析速度的LUSH流程

1. LUSH流程简介 基因组测序通常用于分子诊断、分期和预后&#xff0c;而大量测序数据在分析时间方面提出了挑战。 对于从FASTQ到VCF的整个流程&#xff0c;LUSH流程在非GVCF和GVCF模式下都大大降低了运行时间&#xff0c;30 X WGS数据耗时不到2 h&#xff0c;从BAM到VCF约需…

【计网】UDP Echo Server与Client实战:从零开始构建简单通信回显程序

目录 前言&#xff1a; 1.实现udpserver类 1.1.创建udp socket 套接字 --- 必须要做的 socket&#xff08;&#xff09;讲解 代码实现&#xff1a;​编辑 代码讲解&#xff1a; 1.2.填充sockaddr_in结构 代码实现&#xff1a; 代码解析&#xff1a; 1.3.bind sockfd和…

linux中级wed服务器(https搭建加密服务器)

一。非对称加密算法&#xff1a; 公钥&#xff1a;公共密钥&#xff0c;开放 私钥&#xff1a;私有密钥&#xff0c;保密 1.发送方用自己的公钥加密&#xff0c;接受方用发送方的私钥解密&#xff1a;不可行 2.发送方用接受方的公钥加密&#xff0c;接受方用自己的私钥解密…

ffmpeg视频滤镜: 色温- colortemperature

滤镜简述 colortemperature 官网链接 》 FFmpeg Filters Documentation 这个滤镜可以调节图片的色温&#xff0c;色温值越大显得越冷&#xff0c;可以参考一下下图&#xff1a; 咱们装修的时候可能会用到&#xff0c;比如选择灯还有地板的颜色的时候&#xff0c;选暖色调还是…

【论文+源码】基于spring boot的垃圾分类网站

创建一个基于Spring Boot的垃圾分类网站涉及多个步骤&#xff0c;包括环境搭建、项目创建、数据库设计、后端服务开发、前端页面设计等。下面我将引导您完成这个过程。 第一步&#xff1a;准备环境 确保您的开发环境中安装了以下工具&#xff1a; Java JDK 8 或更高版本Mav…

Unity URP ShaderGraph 基本设置

先简单了解一下各种渲染管线之间的区别 Unity 从 2019.3 版本开始正式支持通用渲染管线&#xff08;URP&#xff0c;Universal Render Pipeline&#xff09;。URP 是轻量渲染管线&#xff08;LWRP&#xff0c;Lightweight Render Pipeline&#xff09;的升级和重命名版本&…

【解决】使用Hypermark将Markdown文件转化为HTML文件

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、文件准备&#xff08;一&#xff09;HTML模板文件&#xff08;二&#xff09;MD文件夹和储存文件夹 二、文件转…

COSCon'24 志愿者招募令:共创开源新生活!

亲爱的开源爱好者们&#xff0c; 第九届中国开源年会&#xff08;COSCon24&#xff09;即将在北京中关村国家自主创新示范区会议中心于2024年11月2日至3日隆重举行。今年的主题是“Open Source, Open Life&#xff5c;开源新生活”&#xff0c;旨在探索开源技术如何在各个领域推…

日常记录,使用springboot,vue2,easyexcel使实现字段的匹配导入

目前的需求是数据库字段固定&#xff0c;而excel的字段不固定&#xff0c;需要实现excel导入到一个数据库内。 首先是前端的字段匹配&#xff0c;显示数据库字段和表头字段 读取表头字段&#xff1a; 我这里实现的是监听器导入&#xff0c;需要新建一个listen类。 读Excel …

uniApp 加载google地图 并规划路线

uniApp 加载google地图 并规划路线 备注:核心代码实例 备注: 打开谷歌地图失败的话 参考google开发文档 https://developers.google.com/maps/documentation/urls/ios-urlscheme?hlzh-cn#swift核心代码 mounted() {this.loadGoogleMapsScript(); }, methods: {//加载loadGo…

AI服务器HBA卡的国产PCIe4.0/5.0 switch信号完整性设计与实现,支持定制(二)

表 &#xff12; 展示了 &#xff30;&#xff23;&#xff22; 板所选介质材料 &#xff30;&#xff33;&#xff32;&#xff14;&#xff10;&#xff10;&#xff10;&#xff21;&#xff35;&#xff33;&#xff17;&#xff10;&#xff13; &#xff0c; &#xff3…

解决Redis缓存穿透(缓存空对象、布隆过滤器)

文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库 常见的解决方案有两种&#xff0c;分别…

使用DolphinScheduler接口实现批量导入工作流并上线

使用DS接口实现批量导入工作量并上线脚本 前面实现了批量生成DS的任务&#xff0c;当导入时发现只能逐个导入&#xff0c;因此通过接口实现会更方便。 DS接口文档 DS是有接口文档的地址是 http://IP:12345/dolphinscheduler/swagger-ui/index.html?languagezh_CN&lang…