桌面端文件类组件

系统提供下面5个组件处理文件

  • 附件组件:文件上传、下载和预览
  • Excel 导入组件:上传 Excel 文件,将 Excel 文件中的数据存入数据表或加载到页面上
  • 高级 Excel 导入组件:上传 Excel 文件,将 Excel 文件中的数据存入数据表,提供校验数据、导入主从数据等能力
  • Excel 导出组件:将数据导出为 Excel、Word、PDF 文件,多个文件支持导出 zip 文件,支持使用模板,支持导出主从数据
  • 打印组件:通过定义 Handlebars 语义模板,将数据按照一定的格式生成 pdf 文件,用于打印,支持打印图片

附件组件

使用附件组件上传文件,文件上传到 MinIO 中。MinIO 返回的对象名和文件名组成一个 JSON 对象,存入数据表,JSON 对象格式如下

    [{
        "storeFileName":"",//MinIO 返回的对象名
        "realFileName":""//文件名
    }]

展现方式

附件组件支持文本、图片、图片墙、表格等四种展现方式,如下图所示

上传文件限制

附件组件在上传文件时,提供限制文件大小、文件类型、文件个数的能力。

限制上传文件大小

附件组件提供“文件大小”属性,用于设置允许上传的文件大小,该属性单位为 byte。

例如: 只允许上传512 KB 以下的文件,“文件大小”属性输入512000,如下图所示

选择一个超过512 KB 的文件,显示“超过文件大小限制”的提示信息,如下图所示

限制上传文件类型

附件组件提供“上传类型”属性,用于设置允许上传的文件类型。该属性不仅能选择,也支持输入。文件类型的写法参考 https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept

例如:只允许上传 pdf 文件,“上传类型”属性中输入“.pdf”,如下图所示

例如:只运行上传 word 或 excel 文件,“上传类型”属性中输入“.doc,.docx,.xls,.xlsx”,如下图所示

限制上传文件个数

附件组件提供“文件数”属性,用于设置允许上传的文件个数。上传达到上限后,上传按钮隐藏。

在线预览 Excel、Word 等文件

系统提供“文件预览”服务,在租户中添加该服务后即可使用,用于在线预览 Excel、Word 等文件。设置附件组件的“文件预览服务”属性为是,预览时会通过“文件预览”服务进行预览。

上传前事件

附件组件提供“上传前事件”,可用于上传前压缩图片、添加水印、中断上传。和原生组件的区别是,需要返回一个 file 对象。

压缩图片

示例代码如下

    onUploadImageBeforeUpload = (event) => {
        let {detail:{file, fileList}} = event;
        return new Promise((resolve) => {
            // 创建FileReader读取图片
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = e => {
                const img = new Image();
                img.src = e.target.result;
                img.onload = () => {
                    let quality = 0.7;
                    let maxWidth = 800;
                    let width = img.width;
                    let height = img.height;

                    // 按比例调整尺寸
                    if (width > maxWidth) {
                        height *= maxWidth / width;
                        width = maxWidth;
                    }

                    // 创建canvas用于压缩图片
                    const canvas = document.createElement('canvas');
                    const ctx = canvas.getContext('2d');
                    canvas.width = width;
                    canvas.height = height;
                    ctx.drawImage(img, 0, 0, width, height);

                    canvas.toBlob((result) => {
                        let newFile = new File([result], file.name, { type: file.type });
                        Object.assign(file, { newFile });
                        resolve(newFile);
                    });
                };
            };
        });
    }

添加水印

示例代码如下

    onUploadImageBeforeUpload = (event) => {
        let {detail:{file, fileList}} = event;
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = () => {
                const img = document.createElement('img');
                img.src = reader.result;
                img.onload = () => {
                    const canvas = document.createElement('canvas');
                    canvas.width = img.naturalWidth;
                    canvas.height = img.naturalHeight;
                    const ctx = canvas.getContext('2d');
                    ctx.drawImage(img, 0, 0);
                    ctx.fillStyle = 'red';
                    ctx.textBaseline = 'middle';
                    ctx.font = '33px Arial';
                    ctx.fillText('Ant Design', 20, 20);

                    canvas.toBlob((result) => {
                        let newFile = new File([result], file.name, { type: file.type });
                        Object.assign(file, { newFile });
                        resolve(newFile);
                    });
                };
            };
        });
    }

中断上传文件

选择文件后判断文件的类型、大小等是否满足需求,不满足需求中断文件上传。在附件上传组件的“上传前”事件中写代码,不满足需求时,通过 message 弹出提示信息,并使用下面两行代码中断上传

    event.preventDefault();
    return Upload.LIST_IGNORE;

注意需要添加 message 和 Upload 的引用

react 代码

import { message, Upload } from 'antd';

vue 代码

import { message, Upload } from 'ant-design-vue';

示例代码如下:

    onUpload1BeforeUpload = (event) => {
        let {detail:{file,fileList}} = event;
        let isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
        if (!isJpgOrPng) {
            message.error('You can only upload JPG/PNG file!');
            event.preventDefault();
            return Upload.LIST_IGNORE;
        }
    }

修改文件名

在上传时修改文件名,在“上传前”事件中重写 file 对象中 storeFileName 和 realFileName 的值即可,示例代码如下

let onUpload1BeforeUpload = (event) => {
    let {detail:{file, fileList}} = event;

    let fileName = mainData.getValue("name")+"-"+file.name;

    let _storeFileName = "anoy_" + new UUID() + fileName;
    let date = new Date();
    let [month, day, year] = [
        date.getMonth() + 1,
        date.getDate(),
        date.getFullYear(),
    ];
    file.storeFileName = `/${year}/${month}/${day}` + _storeFileName;
    file.realFileName = fileName;
}

使用 new UUID() 方法需要添加引用 import UUID from '$UI/wxsys/lib/base/uuid';

下载、预览事件

附件组件提供“下载"事件、"预览”事件

  • 在“预览”事件中,将 event.detail.file._previewUrl 赋值为自定义 url,替代附件组件默认的预览 url,实现自定义逻辑
  • 在“下载”事件中,将 event.detail.file.downloadUrl 赋值为自定义 url,替代附件组件默认的下载 url,实现自定义逻辑

可用于在预览和下载前添加水印,具体用法参考《附件文件增加水印

自定义上传 URL

使用附件组件

在源码中设置附件组件的 action、name、data 等属性

  • action:上传请求的 URL
  • name:上传请求中文件参数的参数名
  • data:上传请求的其他参数

react 代码

<antdpro:Upload action="/main/file/upload" name="files" data="{{{"pid":"123"}}}" id="upload9" bind:ref="productData.current.files">
</antdpro:Upload>

vue 代码

<antdv:Upload action="/main/file/upload" name="files" data="{{{"pid":"123"}}}" id="upload7" listType="text" bind:ref="productData.current.files">
</antdv:Upload>

特别说明

  • data 属性值是 JSON
  • JSON 属性值使用3个大花括号括起来

使用原生组件

不使用附件组件,直接使用原生组件。在 JS 文件中,定义一个方法用于渲染原生 Upload 组件,代码如下

react 代码
import { message, Upload, Button, Space } from "antd";
import { UploadOutlined, DownloadOutlined, DeleteOutlined } from '@ant-design/icons';

    uploadRender = () => {
        const props = {
            name: 'files',
            //accept: "application/x-zip-compressed,application/x-gzip",
            action: "/main/file/upload",
            headers: {
                authorization: 'authorization-text',
                Accept: 'application/json'
            },
            data: {
                pid: "123"
            },
            onChange(info) {
                if (info.file.status === 'done') {
                    message.info("上传成功");
                } else if (info.file.status === 'error') {
                    message.error("上传出错!");
                }
            }
        };
        return <Upload maxCount={1} {...props}>
            <Button icon={`<UploadOutlined />`} id="upload" >上传 `</Button>`
        `</Upload>`
    }

在 W 文件中适当的位置添加对于 JS 方法的调用,代码如下

    <antdpro:Div id="div1">{$page.uploadRender()}</antdpro:Div>
vue 代码

使用 vue 原生组件,必须通过给 Upload 起别名的方式,例如下面的代码,将 Upload 重命名为 Upload2,如果不起别名,使用的仍将是系统组件

import { message, Upload as Upload2, Button, Space } from "ant-design-vue";
import { UploadOutlined, DownloadOutlined, DeleteOutlined } from '@ant-design/icons-vue';

let uploadRender=()=>{
    const props = {
        name: 'files',
        //accept: ".jpg, .jpeg, .png",
        action: "/main/file/upload",
        headers: {
            authorization: 'authorization-text',
            Accept: 'application/json'
        },
        data: {
            pid: "123"
        },
        onChange(info) {
            if (info.file.status === 'done') {
                message.info("上传成功");
            } else if (info.file.status === 'error') {
                message.error("上传出错!");
            }
        }
    };
    return <>
        <Upload2 maxCount={2} listType="text" {...props}>
            <Button icon={<UploadOutlined />} id="upload" >上传</Button>
        </Upload2>
    </>
}

在 W 文件中适当的位置添加对于 JS 方法的调用,代码如下

<antdv:Div id="div0" style="padding-top:20px;">{uploadRender()}</antdv:Div>

自定义样式

附件组件分为两个部分,一是上传按钮,二是上传列表

自定义上传按钮

在附件组件中添加一个按钮,这个按钮就成为新的上传按钮

自定义上传列表

在附件组件中点击“定制上传列表”,实现方式支持“插入组件”和“写代码”,下面介绍通过写代码实现如下图的效果

1728555582505

react 代码

import { message, Upload, Button, Space } from "antd";
import React from 'react';
import { UploadOutlined, DownloadOutlined, DeleteOutlined } from '@ant-design/icons';

    upload8ItemRenderRender = (originNode, file, fileList, actions) => {
        return <div className={"defineUpload"}>
            <a style={{ flex: 1 }} onClick={actions.preview}>{file.name}</a>
            <Space>
                <DownloadOutlined onClick={actions.download} />
                <DeleteOutlined onClick={actions.remove} />
            </Space>
        </div>;
    }

css 代码

:global {
    .defineUpload {
        display: flex;
        height: 35px;
        justify-content: center;
        align-items: center;
        padding: 5px;
        background-color: #f5f5f5;
        margin: 5px 0px;
        border:1px solid var(--ant-color-link);
        border-radius: 5px;

        &:hover {
            background-color: var(--ant-color-link);

            &>* {
                color: #fff;
            }
        }
    }
}

vue 代码

import { message, Upload, Button, Space } from "ant-design-vue";
import { UploadOutlined, DownloadOutlined, DeleteOutlined } from '@ant-design/icons-vue';

let upload9ItemRenderRender = ({originNode, file, fileList, actions:{download,preview,remove}}) => {
    return <div class={"defineUpload"}>
        <a style={{ flex: 1 }} onClick={preview}>{file.name}</a>
        <Space>
            <DownloadOutlined onClick={download} />
            <DeleteOutlined onClick={remove} />
        </Space>
    </div>;
}

css 代码

:deep{
    .defineUpload {
        display: flex;
        height: 35px;
        justify-content: center;
        align-items: center;
        padding: 5px;
        background-color: #f5f5f5;
        margin: 5px 0px;
        border:1px solid #1677ff;
        border-radius: 5px;

        &:hover {
            background-color: #1677ff;

            &>* {
                color: #fff;
            }
        }
    }
}

文件 JS SDK

系统提供的文件存储服务 storage 提供三个 API,参考《Java SDK

  • getObject:获取文件
  • postObject:上传文件
  • removeObject:删除文件

文件 fileApi 库将文件存储服务的 API 封装为 JS 方法,提供获取文件存储服务 URL、获取文件 URL 等方法,用于使用 JS 代码下载文件、预览文件

  • getActionUrl:获取文件存储服务 storage 的请求路径
  • getFileUrl:获取文件的请求路径,支持4个参数
    • actionUrl:传入文件存储服务 storage 的请求路径
    • operateType:操作类型
      • upload:上传
      • download:下载
      • preview:预览
      • fileview:通过“文件预览”服务预览
    • storeFileName:存储到 MinIO 中的对象名
    • realFileName:文件名

引用 fileApi,代码如下

    import fileApi from "$UI/wxsys/lib/base/fileApi";

调用 getFileUrl 下载或预览文件,getFileUrl 方法有4个参数

  • actionUrl:调用 getActionUrl 方法获取
  • operateType:根据需要传入
    • upload:上传
    • download:下载
    • preview:预览
    • fileview:通过“文件预览”服务预览
  • storeFileName:从数据表附件列的 JSON 对象中获取
  • realFileName:从数据表附件列的 JSON 对象中获取

下载文件的案例代码如下

    onDownloadBtnClick = (event) => {
        //获取附件列的 JSON 对象
        let str = this.comp("productData").getValue("files");
        let json = JSON.parse(str);

    //获取文件存储服务 URL
        let actionUrl = fileApi.getActionUrl(this);

    //获取下载文件的 URL
        let url = fileApi.getFileUrl({
            actionUrl: actionUrl,
            operateType: "download",
            storeFileName: json[0].storeFileName,
            realFileName: json[0].realFileName
        })

    //下载文件
        window.open(url);
    }

上面代码中 operateType 改为 preview 实现预览文件,改为 fileview 实现通过“文件预览”服务预览文件。

表格附件添加自定义列(48)

附件组件提供 ext 键存储自定义信息。在表格形式的附件中增加列,输入值存储到附件的 json 数组的 ext 键中

1739841793561

在附件组件的表格列渲染事件中,添加列,列中添加输入框组件,输入框的 value 属性绑定 json 中 ext 键中的自定义 key,在输入框的 onChange 事件中将值写入 json 的 ext 键中

例如在附件中增加版本号列,存入 json 的 ext 键的 version 中

react 代码

import React from 'react';
import { Input} from 'antd';

    onUpload0TableColumnRender = (event) => {
        let {detail:{tableColumns}} = event;

        //获取数据集中附件列中的数据
        var data = this.comp("restData0");
        var filesStr = data.getValue("files");
        if(!filesStr){
            return;
        }
        //转为json数组
        let files = JSON.parse(filesStr);
        //定义一个新的列
        let col1 ={
            title: '版本号',
            ellipsis: {
                showTitle: true,
            },
            render: (file,record,index) => (
                <Input placeholder="请输入版本号" value={file.ext?.version} onChange={(value)=>{
                    //遍历json数组,参数file是当前文件的json,和数据中的storeFileName对比,找到当前文件的json对象,将输入框中的值写入json对象
                    for(let oneFile of files){
                        if(oneFile.storeFileName == file.storeFileName){
                            oneFile.ext = {version : value.target.value};
                        }
                    }
                    //保存到数据集
                    data.setValue("files",JSON.stringify(files));
                }}/>
            )
        };
        //加入到表格中
        tableColumns.push(col1);
    }

vue 代码

import { Input as Input2 } from "ant-design-vue";
let mainData = useData("mainData");
let onUpload1TableColumnRender = (event) => {
    let { detail: { tableColumns } } = event;
    //定义一个新的列
    let col1 = {
        title: '版本号',
        ellipsis: {
            showTitle: true,
        },
        width: '120px',
        customRender: (file) => (
            <Input2 placeholder="请输入版本号" value={file.record.ext?.version} onChange={(value) => {
                //获取数据集中附件列中的数据
                let filesStr = mainData.getValue("files");
                if (!filesStr) {
                    return;
                }
                //转为json数组
                let files = JSON.parse(filesStr);
                //遍历json数组,参数file是当前文件的json,和数据中的storeFileName对比,找到当前文件的json对象,将输入框中的值写入json对象
                for (let i = 0; i < files.length; i++) {
                    if (files[i].storeFileName == file.value.storeFileName) {
                        files[i].ext = {version : value.target.value};
                    }
                }

                //保存到数据集
                mainData.setValue("files", JSON.stringify(files));
            }} />
        )
    };
    //加入到表格中
    if(tableColumns[tableColumns.length-1].title != "版本号"){
        tableColumns.push(col1);
    }
}

Excel 导入

Excel 导入组件提供上传 Excel 文件,并将其中的数据导入到数据表(导入DB)或页面的数据组件(导入UI)。

特别说明

  • 导入图片时,图片存储的数据列类型必须是图片类型

导入 UI

Excel 导入组件的“导入到…”属性设置为 UI,“导入数据”属性设置一个数据组件,设计界面如下图所示。数据会导入到这个数据组件中,此时导入的数据尚未存储到数据表中,如需保存,需要调用数据表的保存操作。

Excel 导入组件提供“导入”操作,实现 Excel 导入。

Excel 导入组件提供导入前、导入成功、导入失败三个事件,提供给开发者。导入操作的过程如下:

  1. 触发导入前事件
  2. 弹出文件选择窗口,用户选择文件
  3. 系统读取文件,读取成功,将文件中的数据写入数据组件,触发导入成功事件
  4. 读取不成功,触发导入失败事件

在导入成功事件中,将导入结果 event.response.result 提供给开发者,此时可对导入的数据进行处理。其中

  • event.response.result.mapping 表示 Excel 文件列索引和数据组件列名的对应关系
  • event.response.result.rows 表示导入到数据组件中的数据
  • event.response.result.info 表示导入结果

导入 DB

Excel 导入组件的“导入到…”属性设置为 DB,“导入数据”属性设置一个数据组件,设计界面如下图所示。数据会导入到这个数据组件对应的数据表中,此时导入的数据已经存储到数据表中,界面上如需显示数据,需要调用数据组件的刷新操作。

Excel 导入组件提供“导入”操作,实现 Excel 导入。提供导入前、导入成功、导入失败三个事件,由于是直接导入到数据表,在导入成功事件中,只提供导入结果 event.response.result.info

高级 Excel 导入

高级 Excel 导入组件,提供导入时校验数据、支持导入主从数据、提供导入批次支持多人同时导入,参考《高级 Excel 导入

Excel 导出

Excel 导出组件提供标准导出和模板导出两种方式

  • 标准导出:支持动态设置导出列,可导出 Excel、PDF 文件
  • 模板导出:模板支持 Excel 文件和 Word 文件,支持动态模板、导出主从数据、批量导出。使用 Excel 模板导出 Excel、PDF 文件;使用 Word 模板导出 Word、PDF 文件

参考《Excel 导出

特别说明

  • 具有导出文件能力的还有报表,参考《报表设计

打印组件

打印组件,通过定义 Handlebars 语义模板,将数据按照一定的格式生成 pdf 文件,用于打印,支持打印图片,参考《打印组件

案例位置

桌面-页面-文件处理组件-附件组件.w

桌面-页面-文件处理组件-导入组件.w

桌面-页面-文件处理组件-高级导入组件.w

桌面-页面-文件处理组件-导出组件.w

桌面-页面-文件处理组件-打印组件.w

results matching ""

    No results matching ""