桌面端文件类组件
系统提供下面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>
自定义样式
附件组件分为两个部分,一是上传按钮,二是上传列表
自定义上传按钮
在附件组件中添加一个按钮,这个按钮就成为新的上传按钮
自定义上传列表
在附件组件中点击“定制上传列表”,实现方式支持“插入组件”和“写代码”,下面介绍通过写代码实现如下图的效果
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 键中
在附件组件的表格列渲染事件中,添加列,列中添加输入框组件,输入框的 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 导入组件提供导入前、导入成功、导入失败三个事件,提供给开发者。导入操作的过程如下:
- 触发导入前事件
- 弹出文件选择窗口,用户选择文件
- 系统读取文件,读取成功,将文件中的数据写入数据组件,触发导入成功事件
- 读取不成功,触发导入失败事件
在导入成功事件中,将导入结果 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