移除 extrafile下废弃的文件

This commit is contained in:
Wang Kejun 2023-11-11 21:37:29 +08:00
parent 2eac154a18
commit 1a7547bb52
58 changed files with 142 additions and 2646 deletions

View File

@ -1,6 +0,0 @@
/// <reference types="react" />
import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "extraFile", false, {
mode: string;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -1,29 +0,0 @@
export default OakComponent({
entity: 'extraFile',
isList: false,
projection: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
extension: 1,
type: 1,
entity: 1,
entityId: 1,
},
formData: ({ data: extraFile, features }) => {
return {
url: features.extraFile.getUrl(extraFile),
};
},
wechatMp: {
externalClasses: ['oak-class'],
},
properties: {
// 图片显示模式
mode: 'aspectFit',
},
});

View File

@ -1 +0,0 @@
{}

View File

@ -1,11 +0,0 @@
/** index.wxss **/
.image {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1rpx solid #eee;
border-radius: 4rpx;
}

View File

@ -1,2 +0,0 @@
<!-- index.wxml -->
<image src="{{url}}" mode="{{mode}}" class="image oak-class"/>

View File

@ -1 +0,0 @@
export default function render(): import("react/jsx-runtime").JSX.Element;

View File

@ -1,4 +0,0 @@
import { jsx as _jsx } from "react/jsx-runtime";
export default function render() {
return (_jsx("div", { children: "\u672A\u5B9E\u73B0" }));
}

View File

@ -1,8 +0,0 @@
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

View File

@ -1,34 +1,16 @@
import { EntityDict } from '../../../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
type SourceType = 'album' | 'camera';
type Theme = 'file' | 'image' | 'image-flow' | 'custom';
type ImgMode = 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'widthFix' | "heightFix" | 'top' | 'bottom' | 'left' | 'right' | 'center' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
removeLater: boolean;
autoUpload: boolean;
maxNumber: number;
extension: string[];
selectCount: number;
sourceType: SourceType[];
mediaType: ('image' | 'video')[];
mode: ImgMode;
size: number;
showUploadList: boolean;
accept: string;
disablePreview: boolean;
disableDelete: boolean;
disableAdd: boolean;
disableDownload: boolean;
disabled: boolean;
type: string;
origin: string;
tag1: string;
tag2: string;
entity: keyof ED2;
entityId: string;
theme: Theme;
showUploadProgress: boolean;
children?: React.ReactNode;
style: string;
}>) => React.ReactElement;
export default _default;

View File

@ -1,6 +1,4 @@
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { assert } from 'oak-domain/lib/utils/assert';
import Dialog from '../../../utils/dialog/index';
;
export default OakComponent({
entity: 'extraFile',
isList: true,
@ -22,26 +20,33 @@ export default OakComponent({
isBridge: 1,
uploadState: 1,
},
formData({ data: originalFiles, features }) {
let files = originalFiles
?.filter((ele) => !ele.$$deleteAt$$)
.sort((ele1, ele2) => ele1.sort - ele2.sort);
features: ['extraFile'],
formData({ data, features }) {
let files = data?.sort((ele1, ele2) => ele1.sort - ele2.sort);
if (this.props.tag1) {
files = files?.filter((ele) => ele?.tag1 === this.props.tag1);
}
if (this.props.tag2) {
files = files?.filter((ele) => ele?.tag2 === this.props.tag2);
}
const files2 = files.map((ele) => {
const url = features.extraFile.getUrl(ele);
const thumbUrl = features.extraFile.getUrl(ele, this.props.style);
const fileName = features.extraFile.getFileName(ele);
return {
files,
disableInsert: this.props.maxNumber === 0 ||
files?.length >= this.props.maxNumber,
url,
thumbUrl,
fileName,
...ele,
};
});
return {
files: files2,
};
},
data: {
// 根据 size 不同,计算的图片显示大小不同
itemSizePercentage: '',
fileList: {},
},
wechatMp: {
externalClasses: ['oak-class', 'oak-item-class'],
@ -62,48 +67,17 @@ export default OakComponent({
},
],
properties: {
removeLater: true,
autoUpload: false,
maxNumber: 20,
extension: [],
selectCount: 1,
sourceType: ['album', 'camera'],
mediaType: ['image'],
mode: 'aspectFit',
size: 3,
showUploadList: true,
showUploadProgress: false,
accept: 'image/*',
disablePreview: false,
disableDelete: false,
disableAdd: false,
disableDownload: false,
disabled: false,
type: '',
origin: '',
tag1: '',
tag2: '',
entity: '',
entityId: '',
theme: 'image',
style: '',
},
methods: {
getUrl(extraFile) {
const { fileList } = this.state;
if (fileList[extraFile?.id]) {
const url = this.features.extraFile.getUrl(Object.assign({}, extraFile, {
extra1: fileList[extraFile?.id],
}));
return url;
}
return this.features.extraFile.getUrl(extraFile);
},
getFileName(extraFile) {
return this.features.extraFile.getFileName(extraFile);
},
formatBytes(value) {
return this.features.extraFile.formatBytes(value);
},
/**
* 获取组件内部节点位置信息单个
* @param component 组件实例
@ -129,378 +103,21 @@ export default OakComponent({
const windowWidth = wx.getSystemInfoSync().windowWidth;
return (750 / windowWidth) * px;
},
async chooseMediaByMp() {
const { selectCount, mediaType, sourceType } = this.props;
try {
const { errMsg, tempFiles } = await wx.chooseMedia({
count: selectCount,
mediaType,
sourceType,
});
if (errMsg !== 'chooseMedia:ok') {
this.triggerEvent('error', {
level: 'warning',
msg: errMsg,
});
}
else {
await Promise.all(tempFiles.map(async (tempExtraFile) => {
const { tempFilePath, thumbTempFilePath, fileType, size, } = tempExtraFile;
const filePath = tempFilePath || thumbTempFilePath;
const fileFullName = filePath.match(/[^/]+(?!.*\/)/g)[0];
await this.pushExtraFile({
name: fileFullName,
fileType,
size,
extra1: filePath,
});
}));
}
}
catch (err) {
console.error(err);
if (err.errMsg !== 'chooseMedia:fail cancel') {
this.triggerEvent('error', {
level: 'error',
msg: err.errMsg,
});
}
}
},
async chooseFileByMp() {
const { selectCount, extension } = this.props;
try {
const { errMsg, tempFiles } = await wx.chooseMessageFile({
count: selectCount,
type: 'all',
extension,
});
if (errMsg !== 'chooseMessageFile:ok') {
this.triggerEvent('error', {
level: 'warning',
msg: errMsg,
});
}
else {
await Promise.all(tempFiles.map(async (tempExtraFile) => {
const { path, type, size, name } = tempExtraFile;
await this.pushExtraFile({
name,
fileType: type,
size,
extra1: path,
});
}));
}
}
catch (err) {
console.error(err);
if (err.errMsg !== 'chooseMessageFile:fail cancel') {
this.triggerEvent('error', {
level: 'error',
msg: err.errMsg,
});
}
}
},
onPickByMp() {
const { theme } = this.props;
if (['image', 'image-flow'].includes(theme)) {
this.chooseMediaByMp();
}
else {
this.chooseFileByMp();
}
},
async onPickByWeb(uploadFiles, callback) {
const { files } = this.state;
const currentSort = files?.length
? files[files.length - 1].sort
: 0;
await Promise.all(uploadFiles.map(async (uploadFile, index) => {
const { name, type, size, originFileObj } = uploadFile;
await this.pushExtraFile({
name,
fileType: type,
size,
extra1: originFileObj,
sort: currentSort + (index + 1) * 100,
}, callback);
}));
},
async pushExtraFile(options, callback) {
const { type, origin, tag1, tag2, entity, entityId, autoUpload } = this.props;
const { name, extra1, fileType, size, sort } = options;
const extension = name.substring(name.lastIndexOf('.') + 1);
const filename = name.substring(0, name.lastIndexOf('.'));
assert(entity, '必须传入entity');
assert(origin === 'qiniu', '目前只支持七牛上传'); // 目前只支持七牛上传
const updateData = {
origin,
type: type || 'file',
tag1,
tag2,
objectId: generateNewId(),
entity,
filename,
size,
extension,
fileType,
entityId,
sort,
};
// autoUpload为true, 选择直接上传七牛再提交extraFile
if (autoUpload) {
if (callback) {
callback(updateData, 'uploading');
}
try {
this.addItem(updateData);
await this.execute();
if (callback) {
callback(updateData, 'success');
}
}
catch (error) {
if (callback) {
callback(updateData, 'failed');
}
throw error;
}
}
else {
const id = this.addItem(updateData);
this.setState({
fileList: Object.assign(this.state.fileList, {
[id]: extra1,
}),
});
}
},
async onItemTapped(event) {
const { files } = this.state;
async onPreviewByMp(event) {
const files = this.state.files;
const { index } = event.currentTarget.dataset;
const imageUrl = this.features.extraFile.getUrl(files[index]);
const urls = files
?.filter((ele) => !!ele)
.map((ele) => this.features.extraFile.getUrl(ele));
const detail = {
all: files,
index,
urls,
current: imageUrl,
};
this.triggerEvent('tap', detail);
const imageUrl = files[index].url;
const urls = files?.filter((ele) => !!ele).map((ele) => ele.url);
// 预览图片
if (!this.props.disablePreview) {
const result = await wx.previewImage({
urls: urls,
current: imageUrl,
});
this.triggerEvent('preview', detail);
}
},
async onDeleteByMp(event) {
const { value } = event.currentTarget.dataset;
const { id, bucket, origin, uploadState } = value;
const { removeLater } = this.props;
const { fileList } = this.state;
if (removeLater || !uploadState) {
this.removeItem(id);
Object.assign(fileList, {
[id]: null,
});
this.setState({
fileList,
});
}
else {
const result = await wx.showModal({
title: '确认删除吗',
content: '删除现有文件',
});
const { confirm } = result;
if (confirm) {
this.removeItem(id);
Object.assign(fileList, {
id: null,
});
this.setState({
fileList,
});
await this.execute();
}
}
},
async onDeleteByWeb(value) {
const { id, bucket, origin, uploadState } = value;
const { removeLater = true } = this.props;
const { fileList } = this.state;
// 如果 removeLater为true 或 origin === 'qiniu' 且 bucket不存在
if (removeLater || !uploadState) {
this.removeItem(id);
Object.assign(fileList, {
id: null,
});
this.setState({
fileList,
});
}
else {
const confirm = Dialog.confirm({
title: '确认删除当前文件?',
content: '删除后,文件不可恢复',
cancelText: '取消',
okText: '确定',
onOk: async (e) => {
this.removeItem(id);
Object.assign(fileList, {
id: null,
});
this.setState({
fileList,
});
await this.execute();
confirm.destroy();
},
onCancel: (e) => {
confirm.destroy();
},
});
}
},
async onDownloadByMp(event) {
const { value } = event.currentTarget.dataset;
const fileUrl = this.features.extraFile.getUrl(value);
const name = this.features.extraFile.getFileName(value);
const that = this;
wx.showLoading({
title: '下载请求中...',
});
wx.downloadFile({
url: fileUrl,
success: function (res) {
const filePath = res.tempFilePath || res.filePath;
const fs = wx.getFileSystemManager();
fs.saveFile({
tempFilePath: filePath,
success: (res) => {
wx.hideLoading();
const savedFilePath = res.savedFilePath;
// 打开文件
wx.openDocument({
filePath: savedFilePath,
showMenu: true,
success: function (res) {
// console.log('打开文档成功');
},
fail: function (res) {
const { errMsg } = res;
if (errMsg.includes('fail filetype not supported')) {
that.setMessage({
type: 'error',
content: '该文件类型不支持打开',
});
return;
}
that.setMessage({
type: 'error',
content: '该文件类型打开失败',
});
},
});
},
fail: function (res) {
wx.hideLoading();
that.setMessage({
type: 'error',
content: '保存文件失败',
});
},
});
},
fail: function (res) {
wx.hideLoading();
that.setMessage({
type: 'error',
content: '下载文件失败',
});
},
complete: function (res) { },
});
},
async onOpenByMp(event) {
const { value } = event.currentTarget.dataset;
const fileUrl = this.features.extraFile.getUrl(value);
const that = this;
let extension = value.extension.toLowerCase();
let extensions = [
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'pdf',
]; //openDocument fileType目前只支持范围
if (!extensions.includes(extension)) {
this.setMessage({
type: 'error',
content: `目前仅支持打开${extensions.join(',')}类型的文件`,
});
return;
}
wx.showLoading({
title: '下载请求中...',
});
wx.downloadFile({
url: fileUrl,
success: function (res) {
const filePath = res.tempFilePath || res.filePath;
wx.hideLoading();
wx.openDocument({
//打开文件
filePath: filePath,
fileType: extension,
showMenu: true,
success: function () {
//console.log(`打开文件成功`);
},
fail: function (res) {
const { errMsg } = res;
if (errMsg.includes('fail filetype not supported')) {
that.setMessage({
type: 'error',
content: '该文件类型不支持打开',
});
return;
}
that.setMessage({
type: 'error',
content: '该文件类型打开失败',
});
},
});
},
fail: function (res) {
wx.hideLoading();
that.setMessage({
type: 'error',
content: '下载文件失败',
});
},
complete: function (res) { },
});
},
},
listeners: {
maxNumber(prev, next) {
if (this.state.oakFullpath) {
if (prev.maxNumber !== next.maxNumber) {
this.reRender();
}
}
},
async size(prev, next) {
if (process.env.OAK_PLATFORM === 'wechatMp') {
const size = next.size;

View File

@ -1,8 +1,4 @@
{
"component": true,
"usingComponents": {
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"oak-display": "../display/index"
}
"usingComponents": {}
}

View File

@ -59,94 +59,3 @@ each(range(2, 10), {
align-items: center;
justify-content: space-around;
}
.file-list__remove {
position: absolute;
right: 10rpx;
top: 10rpx;
height: 40rpx;
width: 40rpx;
border-radius: 50%;
background: rgb(0 0 0 / 40%);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.file-list__item--add {
border: 1rpx solid #eee;
border-radius: 4rpx;
background-color: white;
}
.file-list__image--add {
visibility: hidden;
position: absolute;
width: 50%;
height: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
align-items: center;
justify-content: center;
display: flex;
}
.file-list__item-slot-wrapper {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.file-list__item-slot-wrapper:empty+.file-list__image--add {
visibility: visible;
}
.file-list-flow__container {
position: relative;
display: flex;
flex-direction: column;
}
.file-list-flow__item {
display: flex;
flex-direction: row;
align-items: center;
margin: 10rpx 10rpx 10rpx 0;
}
.file-list-flow__item--name {
display: flex;
flex-direction: row;
fleX: 1;
font-size: 28rpx;
}
.file-list-flow__item--btns {
display: flex;
flex-direction: row;
}
.file-list-flow__download {
width: 48rpx;
height: 48rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.file-list-flow__remove {
width: 48rpx;
height: 48rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

View File

@ -1,46 +1,9 @@
<block wx:if="{{ theme === 'image' || theme === 'image-flow' }}">
<view class="file-list__container oak-class">
<view class="file-list__container oak-class">
<block wx:for="{{files}}" wx:key="index">
<block wx:if="{{item}}">
<view class="file-list__item file-list__item--{{size}} oak-item-class" style="{{itemSizePercentage?'width:'+itemSizePercentage+'padding-bottom:'+itemSizePercentage:'xxx'}}">
<oak-display data-index="{{index}}" bind:tap="onItemTapped" mode="{{mode}}" oakId="{{item.id}}" oakAutoUnmount="{{true}}" oakPath="{{oakFullpath}}.{{item.id}}" />
<view wx:if="{{!disableDelete}}" mut-bind:tap="onDeleteByMp" class="file-list__remove" data-value="{{item}}">
<l-icon name="close" color="#ffffff" size="18" />
</view>
</view>
</block>
</block>
<view class="file-list__item file-list__item--add file-list__item--{{size}} oak-item-class" style="{{itemSizePercentage?'width:'+itemSizePercentage+'padding-bottom:'+itemSizePercentage:''}}" wx:if="{{!disableInsert && !disableAdd}}" bind:tap="onPickByMp">
<view class="file-list__item-slot-wrapper">
<slot />
</view>
<view class="file-list__image--add">
<l-icon name="add" size="80" />
</view>
</view>
</view>
</block>
<block wx:else >
<view class="file-list-flow__container oak-class">
<view class="file-list-flow__item--add oak-item-add-class" wx:if="{{!disableInsert && !disableAdd}}">
<l-button bind:lintap="onPickByMp" plain="{{true}}" type="default">选择文件</l-button>
</view>
<block wx:for="{{files}}" wx:key="index">
<block wx:if="{{item}}">
<view class="file-list-flow__item">
<view class="file-list-flow__item--name" mut-bind:tap="onOpenByMp" data-value="{{item}}">
{{ item.filename }}{{ item.extension ? '.' + item.extension : '' }}
</view>
<view class="file-list-flow__item--btns">
<!-- <view wx:if="{{!disableDownload}}" mut-bind:tap="onDownloadByMp" class="file-list-flow__download" data-value="{{item}}">
<l-icon name="download" size="36" />
</view> -->
<view wx:if="{{!disableDelete}}" mut-bind:tap="onDeleteByMp" class="file-list-flow__remove" data-value="{{item}}">
<l-icon name="delete" size="36" />
</view>
<view>
<image data-index="{{index}}" bind:tap="onPreviewByMp" src="{{item.thumbUrl}}" mode="{{mode}}" class="file-list__image"/>
</view>
</block>
</block>
</view>
</block>

View File

@ -1,5 +1 @@
{
"waiting": "等待上传",
"success": "上传成功",
"uploading": "上传中"
}
{}

View File

@ -1,37 +1,18 @@
import { UploadFile } from "antd";
import { WebComponentProps } from "oak-frontend-base";
import { EntityDict } from "../../../oak-app-domain";
interface NewUploadFile extends UploadFile {
id?: string;
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
type ExtraFile = EntityDict['extraFile']['OpSchema'];
interface EnhancedExtraFile extends ExtraFile {
url: string;
thumbUrl: string;
fileFullName: string;
}
type Theme = "file" | "image" | "image-flow" | "custom";
export default function render(props: WebComponentProps<EntityDict, 'extraFile', true, {
accept?: string;
maxNumber?: number;
multiple?: boolean;
draggable?: boolean;
theme?: Theme;
tips?: string;
beforeUpload?: (file: File) => Promise<boolean> | boolean;
disabled?: boolean;
files: EnhancedExtraFile[];
style?: Record<string, string>;
className?: string;
directory?: boolean;
onPreview?: (file: UploadFile<any>) => void;
onDownload?: (file: UploadFile<any>) => void;
onDownload?: (file: EnhancedExtraFile) => void;
showUploadList?: boolean;
children?: JSX.Element;
files?: EntityDict['extraFile']['OpSchema'][];
disableInsert?: boolean;
disableAdd?: boolean;
disableDownload?: boolean;
disableDelete?: boolean;
disablePreview?: boolean;
}, {
onPickByWeb: (files: UploadFile[], callback?: (file: NewUploadFile, status: string) => void) => void;
onDeleteByWeb: (file: UploadFile) => void;
getUrl: (extraFile: EntityDict['extraFile']['OpSchema']) => string;
getFileName: (extraFile: EntityDict['extraFile']['OpSchema']) => string;
formatBytes: (value: number) => string;
}>): import("react/jsx-runtime").JSX.Element;
}, {}>): import("react/jsx-runtime").JSX.Element;
export {};

View File

@ -1,238 +1,15 @@
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React, { useState, useEffect, useCallback } from "react";
import { Space, Upload, Tag, Button, Table } from "antd";
import { PlusOutlined } from "@ant-design/icons";
import { file2Obj } from "antd/es/upload/utils";
import classNames from "classnames";
import Style from "./web.module.less";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import { isPc } from "oak-frontend-base/es/utils/utils";
function getListType(theme) {
const themeMap = {
file: "text",
image: "picture-card",
"image-flow": "picture",
custom: "text",
};
return themeMap[theme];
}
const type = 'DraggableUploadList';
const DraggableUploadListItem = ({ originNode, moveRow, file, fileList, }) => {
const ref = React.useRef(null);
const index = fileList.indexOf(file);
const [{ isOver, dropClassName }, drop] = useDrop({
accept: type,
collect: (monitor) => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName: dragIndex < index
? ' drop-over-downward'
: ' drop-over-upward',
};
},
drop: (item) => {
moveRow(item.index, index);
},
});
const [, drag] = useDrag({
type,
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (_jsx("div", { ref: ref, className: `ant-upload-draggable-list-item ${isOver ? dropClassName : ''}`, style: { cursor: 'move', height: '100%' }, children: originNode }));
};
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useRef } from 'react';
import { Image, ImageViewer, Space } from 'antd-mobile';
export default function render(props) {
const { accept = 'image/*', maxNumber = 20, multiple = maxNumber !== 1, draggable = false, theme = 'image', tips, beforeUpload, disabled, style, className, directory = false, onPreview, onDownload, children, showUploadList = true, files, disableInsert = false, disableAdd = false, disableDownload = false, disableDelete = false, disablePreview = false, } = props.data;
const { onPickByWeb, onDeleteByWeb, updateItem, t, getFileName, getUrl, formatBytes, } = props.methods;
const [newFiles, setNewFiles] = useState([]);
const [newUploadFiles, setNewUploadFiles] = useState([]);
const listType = getListType(theme);
useEffect(() => {
if (files && files.length > 0) {
setNewFiles(files);
}
else {
setNewFiles([]);
}
}, [files]);
const extraFileToUploadFile = (extraFile) => {
let status = undefined;
switch (extraFile.uploadState) {
case 'uploading': {
status = 'uploading';
break;
}
case 'failed': {
status = 'error';
break;
}
case 'success': {
status = 'done';
break;
}
}
return Object.assign({}, extraFile, {
id: extraFile.id,
url: getUrl(extraFile),
thumbUrl: getUrl(extraFile),
name: getFileName(extraFile),
fileName: getFileName(extraFile),
size: extraFile.size,
type: extraFile.fileType,
uid: extraFile.id,
status,
percent: status === 'uploading' ? 50 : undefined,
});
};
const setNewUploadFilesByStatus = (file, status) => {
const { fileName, size, id } = file;
const file2 = newUploadFiles.find((ele) => ele.name?.includes(fileName) && ele.size === size);
if (file2) {
Object.assign(file2, {
status,
id,
});
}
setNewUploadFiles(newUploadFiles);
};
const customDelete = (index) => {
const arr = [...newUploadFiles];
arr.splice(index, 1);
setNewUploadFiles(arr);
};
const getUploadButton = () => {
if (children) {
return children;
}
if (listType === 'picture-card') {
return (_jsxs("div", { children: [_jsx(PlusOutlined, {}), _jsx("div", { style: { marginTop: 8 }, children: "\u8BF7\u9009\u62E9\u56FE\u7247" })] }));
}
return _jsx(Button, { type: "default", children: "\u9009\u62E9\u6587\u4EF6" });
};
const checkLimit = (num) => {
const pattern = /^\d+\.(?:9+)$/;
return pattern.test(num.toString());
};
const moveRow = useCallback((dragIndex, hoverIndex) => {
let dragRow = newFiles[dragIndex];
let sort;
if (hoverIndex === dragIndex) {
return;
}
else if (hoverIndex > dragIndex) {
if (hoverIndex === newFiles.length - 1) {
sort = newFiles[hoverIndex].sort + 100;
}
else {
sort =
(newFiles[hoverIndex].sort +
newFiles[hoverIndex + 1].sort) /
2;
}
}
else {
if (hoverIndex === 0) {
sort = newFiles[hoverIndex].sort / 2;
}
else {
sort =
(newFiles[hoverIndex].sort +
newFiles[hoverIndex - 1].sort) /
2;
}
}
if (checkLimit(sort)) {
alert('当前的sort值为:' + sort);
return;
}
updateItem({ sort }, dragRow.id);
}, [newFiles]);
return (_jsxs(Space, { direction: "vertical", className: Style['oak-upload'], style: { width: '100%' }, children: [_jsx(DndProvider, { backend: isPc ? HTML5Backend : TouchBackend, children: _jsx(Upload, { className: classNames(Style['oak-upload__upload'], className), style: style, disabled: disabled, directory: directory, showUploadList: showUploadList
? {
showPreviewIcon: !disablePreview,
showRemoveIcon: !disableDelete,
showDownloadIcon: !disableDownload,
}
: false, beforeUpload: async (file) => {
if (typeof beforeUpload === 'function') {
const result = await beforeUpload(file);
if (result) {
return false;
}
}
return false;
}, multiple: multiple, maxCount: maxNumber, accept: accept, listType: listType, fileList: theme === 'custom'
? []
: newFiles?.map((ele) => extraFileToUploadFile(ele)), onChange: ({ file, fileList, event }) => {
// id不存在就是file对象
if (!file.id) {
if (theme !== 'custom') {
onPickByWeb([file2Obj(file)]);
}
else {
setNewUploadFiles(fileList);
}
}
}, onRemove: onDeleteByWeb, onPreview: onPreview, onDownload: onDownload, itemRender: (originNode, currentFile, currentFileList) => {
return (_jsx(DraggableUploadListItem, { originNode: originNode, file: currentFile, fileList: currentFileList, moveRow: moveRow }));
}, children: !disableInsert && !disableAdd ? getUploadButton() : null }) }), tips && (_jsx("small", { className: Style['oak-upload__tips'], children: tips })), theme === 'custom' && (_jsxs(_Fragment, { children: [_jsx(Table, { dataSource: newUploadFiles || [], rowKey: "id", columns: [
{
align: 'center',
dataIndex: 'tableIndex',
title: '#',
render: (value, record, index) => index + 1,
width: 50,
},
{
dataIndex: 'name',
title: '文件名',
},
{
dataIndex: 'size',
title: '文件大小',
render: (value, record, index) => {
return formatBytes(value);
},
},
{
dataIndex: 'status',
title: '状态',
render: (value, record, index) => {
switch (value) {
case 'success':
return (_jsx(Tag, { color: "success", children: t('success') }));
case 'uploading':
return (_jsx(Tag, { color: "processing", children: t('uploading') }));
default:
return (_jsx(Tag, { color: "warning", children: t('waiting') }));
}
},
},
{
dataIndex: 'op',
width: 300,
title: '操作',
align: 'center',
render: (value, record, index) => {
// 只处理state的文件 这时候可以直接删除
return (_jsx(_Fragment, { children: !record.id && (_jsx(Button, { type: "link", onClick: () => {
customDelete(index);
}, children: "\u5220\u9664" })) }));
},
fixed: 'right',
},
] }), _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end' }, children: _jsxs(Space, { children: [_jsx(Button, { danger: true, type: "default", onClick: () => setNewUploadFiles([]), children: "\u6E05\u7A7A" }), _jsx(Button, { type: "primary", onClick: () => {
onPickByWeb(newUploadFiles, (file, status) => {
setNewUploadFilesByStatus(file, status);
});
}, children: "\u4E0A\u4F20" })] }) })] }))] }));
const { style, className, onDownload, files = [], disableDownload = false, disablePreview = false, } = props.data;
const { t } = props.methods;
const [visible, setVisible] = useState(false);
const imageViewerMultiRef = useRef(null);
return (_jsxs(_Fragment, { children: [_jsx(Space, { children: files?.map((ele, index) => (_jsx(Image, { src: ele.thumbUrl, width: 100, height: 100, fit: "contain", onClick: !disablePreview ? () => {
setVisible(true);
imageViewerMultiRef.current.swipeTo(index);
} : undefined }))) }), _jsx(ImageViewer.Multi, { ref: imageViewerMultiRef, images: files?.map((ele) => ele.url) || [], visible: visible, onClose: () => {
setVisible(false);
} })] }));
}

View File

@ -1,7 +1,3 @@
.oak-upload {
&__tips {
display: block;
color: var(--oak-text-color-placeholder);
margin-top: 8px;
}
.container {
display: flex;
}

View File

@ -1,16 +0,0 @@
import { EntityDict } from '../../../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
type ImgMode = 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'widthFix' | "heightFix" | 'top' | 'bottom' | 'left' | 'right' | 'center' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
mode: ImgMode;
size: number;
disablePreview: boolean;
disableDownload: boolean;
tag1: string;
tag2: string;
entity: keyof ED2;
entityId: string;
style: string;
}>) => React.ReactElement;
export default _default;

View File

@ -1,139 +0,0 @@
;
export default OakComponent({
entity: 'extraFile',
isList: true,
projection: {
id: 1,
tag1: 1,
tag2: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
extension: 1,
type: 1,
entity: 1,
entityId: 1,
fileType: 1,
sort: 1,
isBridge: 1,
uploadState: 1,
},
features: ['extraFile'],
formData({ data, features }) {
let files = data?.sort((ele1, ele2) => ele1.sort - ele2.sort);
if (this.props.tag1) {
files = files?.filter((ele) => ele?.tag1 === this.props.tag1);
}
if (this.props.tag2) {
files = files?.filter((ele) => ele?.tag2 === this.props.tag2);
}
const files2 = files.map((ele) => {
const url = features.extraFile.getUrl(ele);
const thumbUrl = features.extraFile.getUrl(ele, this.props.style);
const fileName = features.extraFile.getFileName(ele);
return {
url,
thumbUrl,
fileName,
...ele,
};
});
return {
files: files2,
};
},
data: {
// 根据 size 不同,计算的图片显示大小不同
itemSizePercentage: '',
},
wechatMp: {
externalClasses: ['oak-class', 'oak-item-class'],
},
filters: [
{
filter() {
const { tag1, tag2 } = this.props;
const filter1 = {};
if (tag1) {
Object.assign(filter1, { tag1 });
}
if (tag2) {
Object.assign(filter1, { tag2 });
}
return filter1;
},
},
],
properties: {
mode: 'aspectFit',
size: 3,
disablePreview: false,
disableDownload: false,
tag1: '',
tag2: '',
entity: '',
entityId: '',
style: '',
},
methods: {
/**
* 获取组件内部节点位置信息单个
* @param component 组件实例
* @param selector {String} css选择器
* @returns boundingClientRect() 回调函数的值
*/
async getNodeRectFromComponent(component, selector) {
return await new Promise((resolve) => {
component
.createSelectorQuery()
.select(selector)
.boundingClientRect((res) => {
resolve(res);
})
.exec();
});
},
/**
// * px 转 rpx
// * @param px 像素值
// */
px2rpx(px) {
const windowWidth = wx.getSystemInfoSync().windowWidth;
return (750 / windowWidth) * px;
},
async onPreviewByMp(event) {
const files = this.state.files;
const { index } = event.currentTarget.dataset;
const imageUrl = files[index].url;
const urls = files?.filter((ele) => !!ele).map((ele) => ele.url);
// 预览图片
if (!this.props.disablePreview) {
const result = await wx.previewImage({
urls: urls,
current: imageUrl,
});
}
},
},
listeners: {
async size(prev, next) {
if (process.env.OAK_PLATFORM === 'wechatMp') {
const size = next.size;
if (!size) {
this.setState({ itemSizePercentage: '' });
return;
}
// 获取 .file-list__container 容器宽度
const res = await this.getNodeRectFromComponent(this, '.file-list__container');
const widthRpx = this.px2rpx(res.right - res.left);
// 根据容器宽度计算单张图片宽度百分比
const itemSizePercentage = (10 / size) * 10 - (20 / widthRpx) * 100 + '%;';
this.setState({
itemSizePercentage: itemSizePercentage,
});
}
},
},
});

View File

@ -1,4 +0,0 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -1,61 +0,0 @@
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.file-list__container {
position: relative;
display: flex;
flex-wrap: wrap;
}
.file-list__item {
position: relative;
width: 220rpx;
padding-bottom: 220rpx;
height: 0;
}
// size 不同时,对应的图片间距设置
// size 仅支持 1-10
each(range(2, 10), {
@valuePlusOne : @value+1;
.file-list__item--@{value}:nth-of-type(n+@{valuePlusOne}) {
margin-top : 20rpx;
}
.file-list__item--@{value}:not(:nth-of-type(@{value}n+1)) {
margin-left : 20rpx;
}
}) // 当 size 为 null每行会显示 3 张图片
.file-list__item--null:nth-of-type(n+4) {
margin-top: 20rpx;
}
.file-list__item--null:not(:nth-of-type(3n+1)) {
margin-left: 20rpx;
}
.file-list__image {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1rpx solid #eee;
border-radius: 4rpx;
}
.file-list__item--selected {
width: 100%;
height: 100%;
z-index: 10;
background-color: #000;
filter: Alpha(Opacity=50);
opacity: 0.5;
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}

View File

@ -1,9 +0,0 @@
<view class="file-list__container oak-class">
<block wx:for="{{files}}" wx:key="index">
<block wx:if="{{item}}">
<view class="file-list__item file-list__item--{{size}} oak-item-class" style="{{itemSizePercentage?'width:'+itemSizePercentage+'padding-bottom:'+itemSizePercentage:'xxx'}}">
<image data-index="{{index}}" bind:tap="onPreviewByMp" src="{{item.thumbUrl}}" mode="{{mode}}" class="file-list__image"/>
</view>
</block>
</block>
</view>

View File

@ -1,18 +0,0 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
type ExtraFile = EntityDict['extraFile']['OpSchema'];
interface EnhancedExtraFile extends ExtraFile {
url: string;
thumbUrl: string;
fileFullName: string;
}
export default function render(props: WebComponentProps<EntityDict, 'extraFile', true, {
files: EnhancedExtraFile[];
style?: Record<string, string>;
className?: string;
onDownload?: (file: EnhancedExtraFile) => void;
showUploadList?: boolean;
disableDownload?: boolean;
disablePreview?: boolean;
}, {}>): import("react/jsx-runtime").JSX.Element;
export {};

View File

@ -1,15 +0,0 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useRef } from 'react';
import { Image, ImageViewer, Space } from 'antd-mobile';
export default function render(props) {
const { style, className, onDownload, files = [], disableDownload = false, disablePreview = false, } = props.data;
const { t } = props.methods;
const [visible, setVisible] = useState(false);
const imageViewerMultiRef = useRef(null);
return (_jsxs(_Fragment, { children: [_jsx(Space, { children: files?.map((ele, index) => (_jsx(Image, { src: ele.thumbUrl, width: 100, height: 100, fit: "contain", onClick: !disablePreview ? () => {
setVisible(true);
imageViewerMultiRef.current.swipeTo(index);
} : undefined }))) }), _jsx(ImageViewer.Multi, { ref: imageViewerMultiRef, images: files?.map((ele) => ele.url) || [], visible: visible, onClose: () => {
setVisible(false);
} })] }));
}

View File

@ -1,3 +0,0 @@
.container {
display: flex;
}

View File

@ -1,4 +1,4 @@
import { generateNewId } from 'oak-domain';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { assert } from 'oak-domain/lib/utils/assert';
;
export default OakComponent({

View File

@ -70,18 +70,6 @@ const i18ns = [
language: "zh-CN",
module: "oak-general-business",
position: "src/components/extraFile/gallery",
data: {
"waiting": "等待上传",
"success": "上传成功",
"uploading": "上传中"
}
},
{
id: "d30e4a47c1d768d74efae1fade371595",
namespace: "oak-general-business-c-extraFile-gallery2",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/extraFile/gallery2",
data: {}
},
{

View File

@ -72,18 +72,6 @@ const i18ns = [
language: "zh-CN",
module: "oak-general-business",
position: "src/components/extraFile/gallery",
data: {
"waiting": "等待上传",
"success": "上传成功",
"uploading": "上传中"
}
},
{
id: "d30e4a47c1d768d74efae1fade371595",
namespace: "oak-general-business-c-extraFile-gallery2",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/extraFile/gallery2",
data: {}
},
{

View File

@ -1,2 +0,0 @@
{
}

View File

@ -1,11 +0,0 @@
/** index.wxss **/
.image {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1rpx solid #eee;
border-radius: 4rpx;
}

View File

@ -1,33 +0,0 @@
import { EntityDict } from '../../../oak-app-domain';
export default OakComponent({
entity: 'extraFile',
isList: false,
projection: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
extension: 1,
type: 1,
entity: 1,
entityId: 1,
},
formData: ({ data: extraFile, features }) => {
return {
url: features.extraFile.getUrl(
extraFile as EntityDict['extraFile']['Schema']
),
};
},
wechatMp: {
externalClasses: ['oak-class'],
},
properties: {
// 图片显示模式
mode: 'aspectFit' as string,
},
});

View File

@ -1,2 +0,0 @@
<!-- index.wxml -->
<image src="{{url}}" mode="{{mode}}" class="image oak-class"/>

View File

@ -1,8 +0,0 @@
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

View File

@ -1,9 +0,0 @@
import React, { Component } from 'react';
export default function render() {
return (
<div>
</div>
);
}

View File

@ -1,8 +1,4 @@
{
"component": true,
"usingComponents": {
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index",
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"oak-display": "../display/index"
}
"usingComponents": {}
}

View File

@ -59,94 +59,3 @@ each(range(2, 10), {
align-items: center;
justify-content: space-around;
}
.file-list__remove {
position: absolute;
right: 10rpx;
top: 10rpx;
height: 40rpx;
width: 40rpx;
border-radius: 50%;
background: rgb(0 0 0 / 40%);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.file-list__item--add {
border: 1rpx solid #eee;
border-radius: 4rpx;
background-color: white;
}
.file-list__image--add {
visibility: hidden;
position: absolute;
width: 50%;
height: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
align-items: center;
justify-content: center;
display: flex;
}
.file-list__item-slot-wrapper {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.file-list__item-slot-wrapper:empty+.file-list__image--add {
visibility: visible;
}
.file-list-flow__container {
position: relative;
display: flex;
flex-direction: column;
}
.file-list-flow__item {
display: flex;
flex-direction: row;
align-items: center;
margin: 10rpx 10rpx 10rpx 0;
}
.file-list-flow__item--name {
display: flex;
flex-direction: row;
fleX: 1;
font-size: 28rpx;
}
.file-list-flow__item--btns {
display: flex;
flex-direction: row;
}
.file-list-flow__download {
width: 48rpx;
height: 48rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.file-list-flow__remove {
width: 48rpx;
height: 48rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

View File

@ -1,12 +1,15 @@
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { assert } from 'oak-domain/lib/utils/assert';
import Dialog from '../../../utils/dialog/index';
import { EntityDict } from '../../../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
type SourceType = 'album' | 'camera';
type Theme = 'file' | 'image' | 'image-flow' | 'custom';
type ExtraFile = EntityDict['extraFile']['OpSchema'];
interface EnhancedExtraFile extends ExtraFile {
url: string;
thumbUrl: string;
fileName: string;
};
type ImgMode = 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'widthFix' | "heightFix" | 'top' | 'bottom' | 'left'
| 'right' | 'center' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
@ -31,29 +34,37 @@ export default OakComponent({
isBridge: 1,
uploadState: 1,
},
formData({ data: originalFiles, features }) {
let files = (
originalFiles as Array<EntityDict['extraFile']['OpSchema']>
)
?.filter((ele) => !ele.$$deleteAt$$)
.sort((ele1, ele2) => ele1.sort! - ele2.sort!);
features: ['extraFile'],
formData({ data, features }) {
let files = data?.sort((ele1, ele2) => ele1.sort! - ele2.sort!);
if (this.props.tag1) {
files = files?.filter((ele) => ele?.tag1 === this.props.tag1);
}
if (this.props.tag2) {
files = files?.filter((ele) => ele?.tag2 === this.props.tag2);
}
const files2 = files.map((ele) => {
const url = features.extraFile.getUrl(ele as ExtraFile);
const thumbUrl = features.extraFile.getUrl(
ele as ExtraFile,
this.props.style
);
const fileName = features.extraFile.getFileName(ele as ExtraFile);
return {
files,
disableInsert:
this.props.maxNumber === 0 ||
files?.length >= this.props.maxNumber!,
url,
thumbUrl,
fileName,
...ele,
} as EnhancedExtraFile;
});
return {
files: files2,
};
},
data: {
// 根据 size 不同,计算的图片显示大小不同
itemSizePercentage: '',
fileList: {} as Record<string, File | string>,
},
wechatMp: {
externalClasses: ['oak-class', 'oak-item-class'],
@ -74,51 +85,18 @@ export default OakComponent({
},
],
properties: {
removeLater: true,
autoUpload: false,
maxNumber: 20,
extension: [] as string[], //小程序独有 chooseMessageFile
selectCount: 1, // 每次打开图片时,可选中的数量 小程序独有
sourceType: ['album', 'camera'] as SourceType[], // 小程序独有 chooseMedia
mediaType: ['image'] as ('image' | 'video')[], // 小程序独有 chooseMedia
mode: 'aspectFit' as ImgMode, // 图片显示模式
size: 3, // 每行可显示的个数 小程序独有
showUploadList: true, //web独有
showUploadProgress: false, // web独有
accept: 'image/*', // web独有
disablePreview: false, // 图片是否可预览
disableDelete: false, // 图片是否可删除
disableAdd: false, // 上传按钮隐藏
disableDownload: false, // 下载按钮隐藏
disabled: false,
type: '',
origin: '',
tag1: '',
tag2: '',
entity: '' as keyof EntityDict,
entityId: '',
theme: 'image' as Theme,
style: '',
},
methods: {
getUrl(extraFile: EntityDict['extraFile']['OpSchema']) {
const { fileList } = this.state;
if (fileList[extraFile?.id]) {
const url = this.features.extraFile.getUrl(
Object.assign({}, extraFile, {
extra1: fileList[extraFile?.id],
})
);
return url;
}
return this.features.extraFile.getUrl(extraFile);
},
getFileName(extraFile: EntityDict['extraFile']['OpSchema']) {
return this.features.extraFile.getFileName(extraFile);
},
formatBytes(value: number) {
return this.features.extraFile.formatBytes(value);
},
/**
*
* @param component
@ -144,407 +122,22 @@ export default OakComponent({
const windowWidth = wx.getSystemInfoSync().windowWidth;
return (750 / windowWidth) * px;
},
async chooseMediaByMp() {
const { selectCount, mediaType, sourceType } = this.props;
try {
const { errMsg, tempFiles } = await wx.chooseMedia({
count: selectCount,
mediaType,
sourceType,
});
if (errMsg !== 'chooseMedia:ok') {
this.triggerEvent('error', {
level: 'warning',
msg: errMsg,
});
} else {
await Promise.all(
tempFiles.map(async (tempExtraFile) => {
const {
tempFilePath,
thumbTempFilePath,
fileType,
size,
} = tempExtraFile;
const filePath = tempFilePath || thumbTempFilePath;
const fileFullName =
filePath.match(/[^/]+(?!.*\/)/g)![0];
await this.pushExtraFile({
name: fileFullName,
fileType,
size,
extra1: filePath,
});
})
);
}
} catch (err: any) {
console.error(err);
if (err.errMsg !== 'chooseMedia:fail cancel') {
this.triggerEvent('error', {
level: 'error',
msg: err.errMsg,
});
}
}
},
async chooseFileByMp() {
const { selectCount, extension } = this.props;
try {
const { errMsg, tempFiles } = await wx.chooseMessageFile({
count: selectCount!,
type: 'all',
extension,
});
if (errMsg !== 'chooseMessageFile:ok') {
this.triggerEvent('error', {
level: 'warning',
msg: errMsg,
});
} else {
await Promise.all(
tempFiles.map(async (tempExtraFile) => {
const { path, type, size, name } = tempExtraFile;
await this.pushExtraFile({
name,
fileType: type,
size,
extra1: path,
});
})
);
}
} catch (err: any) {
console.error(err);
if (err.errMsg !== 'chooseMessageFile:fail cancel') {
this.triggerEvent('error', {
level: 'error',
msg: err.errMsg,
});
}
}
},
onPickByMp() {
const { theme } = this.props;
if (['image', 'image-flow'].includes(theme!)) {
this.chooseMediaByMp();
} else {
this.chooseFileByMp();
}
},
async onPickByWeb(
uploadFiles: any[],
callback?: (file: any, status: string) => void
) {
const { files } = this.state;
const currentSort = files?.length
? files[files.length - 1].sort
: 0;
await Promise.all(
uploadFiles.map(async (uploadFile, index) => {
const { name, type, size, originFileObj } = uploadFile;
await this.pushExtraFile(
{
name,
fileType: type,
size,
extra1: originFileObj,
sort: currentSort + (index + 1) * 100,
},
callback
);
})
);
},
async pushExtraFile(
options: {
name: string;
extra1: any;
fileType: string;
size: number;
sort: number;
},
callback?: (file: any, status: string) => void
) {
const { type, origin, tag1, tag2, entity, entityId, autoUpload } =
this.props;
const { name, extra1, fileType, size, sort } = options;
const extension = name.substring(name.lastIndexOf('.') + 1);
const filename = name.substring(0, name.lastIndexOf('.'));
assert(entity, '必须传入entity');
assert(origin === 'qiniu', '目前只支持七牛上传'); // 目前只支持七牛上传
const updateData = {
origin,
type: type || 'file',
tag1,
tag2,
objectId: generateNewId(),
entity,
filename,
size,
extension,
fileType,
entityId,
sort,
} as EntityDict['extraFile']['CreateSingle']['data'];
// autoUpload为true, 选择直接上传七牛再提交extraFile
if (autoUpload) {
if (callback) {
callback(updateData, 'uploading');
}
try {
this.addItem(updateData);
await this.execute();
if (callback) {
callback(updateData, 'success');
}
} catch (error) {
if (callback) {
callback(updateData, 'failed');
}
throw error;
}
} else {
const id = this.addItem(updateData);
this.setState({
fileList: Object.assign(this.state.fileList, {
[id]: extra1,
}),
});
}
},
async onItemTapped(event: WechatMiniprogram.Touch) {
const { files } = this.state;
const { index } = event.currentTarget.dataset;
const imageUrl = this.features.extraFile.getUrl(files[index]);
const urls = files
?.filter((ele: EntityDict['extraFile']['Schema']) => !!ele)
.map((ele: EntityDict['extraFile']['Schema']) =>
this.features.extraFile.getUrl(ele)
);
const detail = {
all: files,
index,
urls,
current: imageUrl,
};
this.triggerEvent('tap', detail);
async onPreviewByMp(event: WechatMiniprogram.Touch) {
const files = this.state.files as EnhancedExtraFile[];
const { index } = event.currentTarget.dataset;
const imageUrl = files[index].url;
const urls = files?.filter((ele) => !!ele).map((ele) => ele.url);
// 预览图片
if (!this.props.disablePreview) {
const result = await wx.previewImage({
urls: urls,
current: imageUrl,
});
this.triggerEvent('preview', detail);
}
},
async onDeleteByMp(event: WechatMiniprogram.Touch) {
const { value } = event.currentTarget.dataset;
const { id, bucket, origin, uploadState } = value;
const { removeLater } = this.props;
const { fileList } = this.state;
if (removeLater || !uploadState) {
this.removeItem(id);
Object.assign(fileList, {
[id]: null,
});
this.setState({
fileList,
});
} else {
const result = await wx.showModal({
title: '确认删除吗',
content: '删除现有文件',
});
const { confirm } = result;
if (confirm) {
this.removeItem(id);
Object.assign(fileList, {
id: null,
});
this.setState({
fileList,
});
await this.execute();
}
}
},
async onDeleteByWeb(value: any) {
const { id, bucket, origin, uploadState } = value;
const { removeLater = true } = this.props;
const { fileList } = this.state;
// 如果 removeLater为true 或 origin === 'qiniu' 且 bucket不存在
if (removeLater || !uploadState) {
this.removeItem(id);
Object.assign(fileList, {
id: null,
});
this.setState({
fileList,
});
} else {
const confirm = Dialog.confirm({
title: '确认删除当前文件?',
content: '删除后,文件不可恢复',
cancelText: '取消',
okText: '确定',
onOk: async (e: any) => {
this.removeItem(id);
Object.assign(fileList, {
id: null,
});
this.setState({
fileList,
});
await this.execute();
confirm.destroy();
},
onCancel: (e: any) => {
confirm.destroy();
},
});
}
},
async onDownloadByMp(event: WechatMiniprogram.Touch) {
const { value } = event.currentTarget.dataset;
const fileUrl = this.features.extraFile.getUrl(value);
const name = this.features.extraFile.getFileName(value);
const that = this;
wx.showLoading({
title: '下载请求中...',
});
wx.downloadFile({
url: fileUrl,
success: function (res) {
const filePath = res.tempFilePath || res.filePath;
const fs = wx.getFileSystemManager();
fs.saveFile({
tempFilePath: filePath,
success: (res) => {
wx.hideLoading();
const savedFilePath = res.savedFilePath;
// 打开文件
wx.openDocument({
filePath: savedFilePath,
showMenu: true,
success: function (res) {
// console.log('打开文档成功');
},
fail: function (res) {
const { errMsg } = res;
if (
errMsg.includes(
'fail filetype not supported'
)
) {
that.setMessage({
type: 'error',
content: '该文件类型不支持打开',
});
return;
}
that.setMessage({
type: 'error',
content: '该文件类型打开失败',
});
},
});
},
fail: function (res) {
wx.hideLoading();
that.setMessage({
type: 'error',
content: '保存文件失败',
});
},
});
},
fail: function (res) {
wx.hideLoading();
that.setMessage({
type: 'error',
content: '下载文件失败',
});
},
complete: function (res) {},
});
},
async onOpenByMp(event: WechatMiniprogram.Touch) {
const { value } = event.currentTarget.dataset;
const fileUrl = this.features.extraFile.getUrl(value);
const that = this;
let extension = value.extension.toLowerCase();
let extensions = [
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'pdf',
]; //openDocument fileType目前只支持范围
if (!extensions.includes(extension)) {
this.setMessage({
type: 'error',
content: `目前仅支持打开${extensions.join(',')}类型的文件`,
});
return;
}
wx.showLoading({
title: '下载请求中...',
});
wx.downloadFile({
url: fileUrl,
success: function (res) {
const filePath = res.tempFilePath || res.filePath;
wx.hideLoading();
wx.openDocument({
//打开文件
filePath: filePath,
fileType: extension,
showMenu: true, // 是否显示右上角菜单按钮 默认为false(看自身需求,可要可不要。后期涉及到右上角分享功能)
success: function () {
//console.log(`打开文件成功`);
},
fail: function (res) {
const { errMsg } = res;
if (
errMsg.includes('fail filetype not supported')
) {
that.setMessage({
type: 'error',
content: '该文件类型不支持打开',
});
return;
}
that.setMessage({
type: 'error',
content: '该文件类型打开失败',
});
},
});
},
fail: function (res) {
wx.hideLoading();
that.setMessage({
type: 'error',
content: '下载文件失败',
});
},
complete: function (res) {},
});
},
},
listeners: {
maxNumber(prev, next) {
if (this.state.oakFullpath) {
if (prev.maxNumber !== next.maxNumber) {
this.reRender();
}
}
},
async size(prev, next) {
if (process.env.OAK_PLATFORM === 'wechatMp') {
const size = next.size;
@ -575,37 +168,19 @@ export default OakComponent({
T2,
true,
{
removeLater: boolean;
autoUpload: boolean;
maxNumber: number;
extension: string[];
selectCount: number;
sourceType: SourceType[];
mediaType: ('image' | 'video')[];
// 图片显示模式
mode: ImgMode;
// 每行可显示的个数
size: number;
showUploadList: boolean;
accept: string;
// 图片是否可预览
disablePreview: boolean;
// 图片是否可删除
disableDelete: boolean;
// 上传按钮隐藏
disableAdd: boolean;
// 下按按钮隐藏
disableDownload: boolean;
disabled: boolean;
type: string;
origin: string;
tag1: string;
tag2: string;
entity: keyof ED2;
entityId: string;
theme: Theme;
showUploadProgress: boolean;
children?: React.ReactNode;
style: string;
}
>
) => React.ReactElement;

View File

@ -1,46 +1,9 @@
<block wx:if="{{ theme === 'image' || theme === 'image-flow' }}">
<view class="file-list__container oak-class">
<view class="file-list__container oak-class">
<block wx:for="{{files}}" wx:key="index">
<block wx:if="{{item}}">
<view class="file-list__item file-list__item--{{size}} oak-item-class" style="{{itemSizePercentage?'width:'+itemSizePercentage+'padding-bottom:'+itemSizePercentage:'xxx'}}">
<oak-display data-index="{{index}}" bind:tap="onItemTapped" mode="{{mode}}" oakId="{{item.id}}" oakAutoUnmount="{{true}}" oakPath="{{oakFullpath}}.{{item.id}}" />
<view wx:if="{{!disableDelete}}" mut-bind:tap="onDeleteByMp" class="file-list__remove" data-value="{{item}}">
<l-icon name="close" color="#ffffff" size="18" />
</view>
</view>
</block>
</block>
<view class="file-list__item file-list__item--add file-list__item--{{size}} oak-item-class" style="{{itemSizePercentage?'width:'+itemSizePercentage+'padding-bottom:'+itemSizePercentage:''}}" wx:if="{{!disableInsert && !disableAdd}}" bind:tap="onPickByMp">
<view class="file-list__item-slot-wrapper">
<slot />
</view>
<view class="file-list__image--add">
<l-icon name="add" size="80" />
</view>
</view>
</view>
</block>
<block wx:else >
<view class="file-list-flow__container oak-class">
<view class="file-list-flow__item--add oak-item-add-class" wx:if="{{!disableInsert && !disableAdd}}">
<l-button bind:lintap="onPickByMp" plain="{{true}}" type="default">选择文件</l-button>
</view>
<block wx:for="{{files}}" wx:key="index">
<block wx:if="{{item}}">
<view class="file-list-flow__item">
<view class="file-list-flow__item--name" mut-bind:tap="onOpenByMp" data-value="{{item}}">
{{ item.filename }}{{ item.extension ? '.' + item.extension : '' }}
</view>
<view class="file-list-flow__item--btns">
<!-- <view wx:if="{{!disableDownload}}" mut-bind:tap="onDownloadByMp" class="file-list-flow__download" data-value="{{item}}">
<l-icon name="download" size="36" />
</view> -->
<view wx:if="{{!disableDelete}}" mut-bind:tap="onDeleteByMp" class="file-list-flow__remove" data-value="{{item}}">
<l-icon name="delete" size="36" />
</view>
<view>
<image data-index="{{index}}" bind:tap="onPreviewByMp" src="{{item.thumbUrl}}" mode="{{mode}}" class="file-list__image"/>
</view>
</block>
</block>
</view>
</block>

View File

@ -1,6 +1,2 @@
{
"waiting": "等待上传",
"success": "上传成功",
"uploading": "上传中"
}
{}

View File

@ -1,7 +1,3 @@
.oak-upload {
&__tips {
display: block;
color: var(--oak-text-color-placeholder);
margin-top: 8px;
}
.container {
display: flex;
}

View File

@ -1,463 +1,73 @@
import React, { useState, useEffect, useCallback } from "react";
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { Space, Upload, UploadFile, Tag, Button, Table } from "antd";
import { Html5Filled, PlusOutlined } from "@ant-design/icons";
import { file2Obj } from "antd/es/upload/utils";
import { RcFile } from "antd/es/upload/interface";
import classNames from "classnames";
import Style from "./web.module.less";
import { WebComponentProps } from "oak-frontend-base";
import { EntityDict } from "../../../oak-app-domain";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import { isPc } from "oak-frontend-base/es/utils/utils";
import { Image, ImageViewer, Space } from 'antd-mobile';
import classNames from 'classnames';
import Style from './mobile.module.less';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
interface NewUploadFile extends UploadFile {
id?: string;
type ExtraFile = EntityDict['extraFile']['OpSchema'];
interface EnhancedExtraFile extends ExtraFile {
url: string;
thumbUrl: string;
fileFullName: string;
}
type Theme = "file" | "image" | "image-flow" | "custom";
type ListType = "text" | "picture" | "picture-card";
function getListType(theme: Theme): ListType {
const themeMap: Record<Theme, ListType> = {
file: "text",
image: "picture-card",
"image-flow": "picture",
custom: "text",
};
return themeMap[theme];
}
const type = 'DraggableUploadList';
const DraggableUploadListItem = ({
originNode,
moveRow,
file,
fileList,
}: any) => {
const ref = React.useRef(null);
const index = fileList.indexOf(file);
const [{ isOver, dropClassName }, drop] = useDrop({
accept: type,
collect: (monitor: any) => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName:
dragIndex < index
? ' drop-over-downward'
: ' drop-over-upward',
};
},
drop: (item: any) => {
moveRow(item.index, index);
},
});
const [, drag] = useDrag({
type,
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (
<div
ref={ref}
className={`ant-upload-draggable-list-item ${
isOver ? dropClassName : ''
}`}
style={{ cursor: 'move', height: '100%' }}
>
{originNode}
</div>
);
};
export default function render(
props: WebComponentProps<
EntityDict,
'extraFile',
true,
{
accept?: string;
maxNumber?: number;
multiple?: boolean;
draggable?: boolean;
theme?: Theme;
tips?: string;
beforeUpload?: (file: File) => Promise<boolean> | boolean;
disabled?: boolean;
files: EnhancedExtraFile[];
style?: Record<string, string>;
className?: string;
directory?: boolean;
onPreview?: (file: UploadFile<any>) => void;
onDownload?: (file: UploadFile<any>) => void;
onDownload?: (file: EnhancedExtraFile) => void;
showUploadList?: boolean;
children?: JSX.Element;
files?: EntityDict['extraFile']['OpSchema'][];
disableInsert?: boolean;
disableAdd?: boolean;
disableDownload?: boolean;
disableDelete?: boolean;
disablePreview?: boolean;
},
{
onPickByWeb: (
files: UploadFile[],
callback?: (file: NewUploadFile, status: string) => void
) => void;
onDeleteByWeb: (file: UploadFile) => void;
getUrl: (extraFile: EntityDict['extraFile']['OpSchema']) => string;
getFileName: (
extraFile: EntityDict['extraFile']['OpSchema']
) => string;
formatBytes: (value: number) => string;
}
{}
>
) {
const {
accept = 'image/*',
maxNumber = 20,
multiple = maxNumber !== 1,
draggable = false,
theme = 'image',
tips,
beforeUpload,
disabled,
style,
className,
directory = false,
onPreview,
onDownload,
children,
showUploadList = true,
files,
disableInsert = false,
disableAdd = false,
files = [],
disableDownload = false,
disableDelete = false,
disablePreview = false,
} = props.data;
const {
onPickByWeb,
onDeleteByWeb,
updateItem,
t,
getFileName,
getUrl,
formatBytes,
} = props.methods;
const [newFiles, setNewFiles] = useState<
EntityDict['extraFile']['OpSchema'][]
>([]);
const [newUploadFiles, setNewUploadFiles] = useState<NewUploadFile[]>([]);
const { t } = props.methods;
const [visible, setVisible] = useState(false);
const imageViewerMultiRef = useRef(null);
const listType = getListType(theme);
useEffect(() => {
if (files && files.length > 0) {
setNewFiles(files);
} else {
setNewFiles([]);
}
}, [files]);
const extraFileToUploadFile = (
extraFile: EntityDict['extraFile']['OpSchema']
): NewUploadFile => {
let status = undefined as NewUploadFile['status'];
switch (extraFile.uploadState) {
case 'uploading': {
status = 'uploading';
break;
}
case 'failed': {
status = 'error';
break;
}
case 'success': {
status = 'done';
break;
}
}
return Object.assign({}, extraFile, {
id: extraFile.id,
url: getUrl(extraFile),
thumbUrl: getUrl(extraFile),
name: getFileName(extraFile)!,
fileName: getFileName(extraFile)!,
size: extraFile.size!,
type: extraFile.fileType!,
uid: extraFile.id, //upload 组件需要uid来维护fileList
status,
percent: status === 'uploading' ? 50 : undefined,
});
};
const setNewUploadFilesByStatus = (file: NewUploadFile, status: string) => {
const { fileName, size, id } = file;
const file2 = newUploadFiles.find(
(ele: NewUploadFile) =>
ele.name?.includes(fileName!) && ele.size === size
);
if (file2) {
Object.assign(file2, {
status,
id,
});
}
setNewUploadFiles(newUploadFiles);
};
const customDelete = (index: number) => {
const arr = [...newUploadFiles];
arr.splice(index, 1);
setNewUploadFiles(arr);
};
const getUploadButton = () => {
if (children) {
return children;
}
if (listType === 'picture-card') {
return (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}></div>
</div>
);
}
return <Button type="default"></Button>;
};
const checkLimit = (num: number) => {
const pattern = /^\d+\.(?:9+)$/;
return pattern.test(num.toString());
};
const moveRow = useCallback(
(dragIndex: number, hoverIndex: number) => {
let dragRow = newFiles[dragIndex];
let sort;
if (hoverIndex === dragIndex) {
return;
} else if (hoverIndex > dragIndex) {
if (hoverIndex === newFiles.length - 1) {
sort = newFiles[hoverIndex]!.sort! + 100;
} else {
sort =
(newFiles[hoverIndex]!.sort! +
newFiles[hoverIndex + 1]!.sort!) /
2;
}
} else {
if (hoverIndex === 0) {
sort = newFiles[hoverIndex]!.sort! / 2;
} else {
sort =
(newFiles[hoverIndex]!.sort! +
newFiles[hoverIndex - 1]!.sort!) /
2;
}
}
if (checkLimit(sort)) {
alert('当前的sort值为:' + sort);
return;
}
updateItem({ sort }, dragRow.id);
},
[newFiles]
);
return (
<Space
direction="vertical"
className={Style['oak-upload']}
style={{ width: '100%' }}
>
<DndProvider backend={isPc ? HTML5Backend : TouchBackend}>
<Upload
className={classNames(
Style['oak-upload__upload'],
className
)}
style={style}
disabled={disabled}
directory={directory}
showUploadList={
showUploadList
? {
showPreviewIcon: !disablePreview,
showRemoveIcon: !disableDelete,
showDownloadIcon: !disableDownload,
}
: false
}
beforeUpload={async (file) => {
if (typeof beforeUpload === 'function') {
const result = await beforeUpload(file);
if (result) {
return false;
}
}
return false;
}}
multiple={multiple}
maxCount={maxNumber}
accept={accept}
listType={listType}
fileList={
theme === 'custom'
? []
: newFiles?.map((ele) => extraFileToUploadFile(ele))
}
onChange={({ file, fileList, event }) => {
// id不存在就是file对象
if (!(file as NewUploadFile).id) {
if (theme !== 'custom') {
onPickByWeb([file2Obj(file as RcFile)]);
} else {
setNewUploadFiles(fileList);
}
}
}}
onRemove={onDeleteByWeb}
onPreview={onPreview}
onDownload={onDownload}
itemRender={(originNode, currentFile, currentFileList) => {
return (
<DraggableUploadListItem
originNode={originNode}
file={currentFile}
fileList={currentFileList}
moveRow={moveRow}
/>
);
}}
>
{!disableInsert && !disableAdd ? getUploadButton() : null}
</Upload>
</DndProvider>
{tips && (
<small className={Style['oak-upload__tips']}>{tips}</small>
)}
{theme === 'custom' && (
<>
<Table
dataSource={newUploadFiles || []}
rowKey="id"
columns={[
{
align: 'center',
dataIndex: 'tableIndex',
title: '#',
render: (value, record, index) => index + 1,
width: 50,
},
{
dataIndex: 'name',
title: '文件名',
},
{
dataIndex: 'size',
title: '文件大小',
render: (value, record, index) => {
return formatBytes(value as number);
},
},
{
dataIndex: 'status',
title: '状态',
render: (value, record, index) => {
switch (value) {
case 'success':
return (
<Tag color="success">
{t('success')}
</Tag>
);
case 'uploading':
return (
<Tag color="processing">
{t('uploading')}
</Tag>
);
default:
return (
<Tag color="warning">
{t('waiting')}
</Tag>
);
}
},
},
{
dataIndex: 'op',
width: 300,
title: '操作',
align: 'center',
render: (value, record, index) => {
// 只处理state的文件 这时候可以直接删除
return (
<>
{!record.id && (
<Button
type="link"
onClick={() => {
customDelete(index);
}}
>
</Button>
)}
</>
);
},
fixed: 'right',
},
]}
/>
<div
style={{ display: 'flex', justifyContent: 'flex-end' }}
>
<Space>
<Button
danger
type="default"
onClick={() => setNewUploadFiles([])}
>
</Button>
<Button
type="primary"
onClick={() => {
onPickByWeb(
newUploadFiles,
(
file: NewUploadFile,
status: string
) => {
setNewUploadFilesByStatus(
file,
status
);
}
);
{files?.map((ele, index) => (
<Image
src={ele.thumbUrl}
width={100}
height={100}
fit="contain"
onClick={!disablePreview ? () => {
setVisible(true);
(imageViewerMultiRef.current as any).swipeTo(index);
} : undefined}
/>
))}
</Space>
<ImageViewer.Multi
ref={imageViewerMultiRef}
images={files?.map((ele) => ele.url) || []}
visible={visible}
onClose={() => {
setVisible(false);
}}
>
</Button>
</Space>
</div>
/>
</>
)}
</Space>
);
}

View File

@ -1,4 +0,0 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -1,61 +0,0 @@
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.file-list__container {
position: relative;
display: flex;
flex-wrap: wrap;
}
.file-list__item {
position: relative;
width: 220rpx;
padding-bottom: 220rpx;
height: 0;
}
// size 不同时,对应的图片间距设置
// size 仅支持 1-10
each(range(2, 10), {
@valuePlusOne : @value+1;
.file-list__item--@{value}:nth-of-type(n+@{valuePlusOne}) {
margin-top : 20rpx;
}
.file-list__item--@{value}:not(:nth-of-type(@{value}n+1)) {
margin-left : 20rpx;
}
}) // 当 size 为 null每行会显示 3 张图片
.file-list__item--null:nth-of-type(n+4) {
margin-top: 20rpx;
}
.file-list__item--null:not(:nth-of-type(3n+1)) {
margin-left: 20rpx;
}
.file-list__image {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1rpx solid #eee;
border-radius: 4rpx;
}
.file-list__item--selected {
width: 100%;
height: 100%;
z-index: 10;
background-color: #000;
filter: Alpha(Opacity=50);
opacity: 0.5;
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}

View File

@ -1,186 +0,0 @@
import { EntityDict } from '../../../oak-app-domain';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
type ExtraFile = EntityDict['extraFile']['OpSchema'];
interface EnhancedExtraFile extends ExtraFile {
url: string;
thumbUrl: string;
fileName: string;
};
type ImgMode = 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'widthFix' | "heightFix" | 'top' | 'bottom' | 'left'
| 'right' | 'center' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
export default OakComponent({
entity: 'extraFile',
isList: true,
projection: {
id: 1,
tag1: 1,
tag2: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
extension: 1,
type: 1,
entity: 1,
entityId: 1,
fileType: 1,
sort: 1,
isBridge: 1,
uploadState: 1,
},
features: ['extraFile'],
formData({ data, features }) {
let files = data?.sort((ele1, ele2) => ele1.sort! - ele2.sort!);
if (this.props.tag1) {
files = files?.filter((ele) => ele?.tag1 === this.props.tag1);
}
if (this.props.tag2) {
files = files?.filter((ele) => ele?.tag2 === this.props.tag2);
}
const files2 = files.map((ele) => {
const url = features.extraFile.getUrl(ele as ExtraFile);
const thumbUrl = features.extraFile.getUrl(
ele as ExtraFile,
this.props.style
);
const fileName = features.extraFile.getFileName(ele as ExtraFile);
return {
url,
thumbUrl,
fileName,
...ele,
} as EnhancedExtraFile;
});
return {
files: files2,
};
},
data: {
// 根据 size 不同,计算的图片显示大小不同
itemSizePercentage: '',
},
wechatMp: {
externalClasses: ['oak-class', 'oak-item-class'],
},
filters: [
{
filter() {
const { tag1, tag2 } = this.props;
const filter1 = {};
if (tag1) {
Object.assign(filter1, { tag1 });
}
if (tag2) {
Object.assign(filter1, { tag2 });
}
return filter1;
},
},
],
properties: {
mode: 'aspectFit' as ImgMode, // 图片显示模式
size: 3, // 每行可显示的个数 小程序独有
disablePreview: false, // 图片是否可预览
disableDownload: false, // 下载按钮隐藏
tag1: '',
tag2: '',
entity: '' as keyof EntityDict,
entityId: '',
style: '',
},
methods: {
/**
*
* @param component
* @param selector {String} css选择器
* @returns boundingClientRect()
*/
async getNodeRectFromComponent(component: any, selector: any) {
return await new Promise((resolve) => {
component
.createSelectorQuery()
.select(selector)
.boundingClientRect((res: any) => {
resolve(res);
})
.exec();
});
},
/**
// * px 转 rpx
// * @param px 像素值
// */
px2rpx(px: number) {
const windowWidth = wx.getSystemInfoSync().windowWidth;
return (750 / windowWidth) * px;
},
async onPreviewByMp(event: WechatMiniprogram.Touch) {
const files = this.state.files as EnhancedExtraFile[];
const { index } = event.currentTarget.dataset;
const imageUrl = files[index].url;
const urls = files?.filter((ele) => !!ele).map((ele) => ele.url);
// 预览图片
if (!this.props.disablePreview) {
const result = await wx.previewImage({
urls: urls,
current: imageUrl,
});
}
},
},
listeners: {
async size(prev, next) {
if (process.env.OAK_PLATFORM === 'wechatMp') {
const size = next.size;
if (!size) {
this.setState({ itemSizePercentage: '' });
return;
}
// 获取 .file-list__container 容器宽度
const res: any = await this.getNodeRectFromComponent(
this,
'.file-list__container'
);
const widthRpx = this.px2rpx(res.right - res.left);
// 根据容器宽度计算单张图片宽度百分比
const itemSizePercentage =
(10 / size) * 10 - (20 / widthRpx) * 100 + '%;';
this.setState({
itemSizePercentage: itemSizePercentage,
});
}
},
},
}) as <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(
props: ReactComponentProps<
ED2,
T2,
true,
{
// 图片显示模式
mode: ImgMode;
// 每行可显示的个数
size: number;
// 图片是否可预览
disablePreview: boolean;
// 下按按钮隐藏
disableDownload: boolean;
tag1: string;
tag2: string;
entity: keyof ED2;
entityId: string;
style: string;
}
>
) => React.ReactElement;

View File

@ -1,9 +0,0 @@
<view class="file-list__container oak-class">
<block wx:for="{{files}}" wx:key="index">
<block wx:if="{{item}}">
<view class="file-list__item file-list__item--{{size}} oak-item-class" style="{{itemSizePercentage?'width:'+itemSizePercentage+'padding-bottom:'+itemSizePercentage:'xxx'}}">
<image data-index="{{index}}" bind:tap="onPreviewByMp" src="{{item.thumbUrl}}" mode="{{mode}}" class="file-list__image"/>
</view>
</block>
</block>
</view>

View File

@ -1,3 +0,0 @@
.container {
display: flex;
}

View File

@ -1,73 +0,0 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { Image, ImageViewer, Space } from 'antd-mobile';
import classNames from 'classnames';
import Style from './mobile.module.less';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
type ExtraFile = EntityDict['extraFile']['OpSchema'];
interface EnhancedExtraFile extends ExtraFile {
url: string;
thumbUrl: string;
fileFullName: string;
}
export default function render(
props: WebComponentProps<
EntityDict,
'extraFile',
true,
{
files: EnhancedExtraFile[];
style?: Record<string, string>;
className?: string;
onDownload?: (file: EnhancedExtraFile) => void;
showUploadList?: boolean;
disableDownload?: boolean;
disablePreview?: boolean;
},
{}
>
) {
const {
style,
className,
onDownload,
files = [],
disableDownload = false,
disablePreview = false,
} = props.data;
const { t } = props.methods;
const [visible, setVisible] = useState(false);
const imageViewerMultiRef = useRef(null);
return (
<>
<Space>
{files?.map((ele, index) => (
<Image
src={ele.thumbUrl}
width={100}
height={100}
fit="contain"
onClick={!disablePreview ? () => {
setVisible(true);
(imageViewerMultiRef.current as any).swipeTo(index);
} : undefined}
/>
))}
</Space>
<ImageViewer.Multi
ref={imageViewerMultiRef}
images={files?.map((ele) => ele.url) || []}
visible={visible}
onClose={() => {
setVisible(false);
}}
/>
</>
);
}

View File

@ -2,7 +2,7 @@ import { EntityDict } from '../../../oak-app-domain';
import { FileState } from '../../../features/extraFile';
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
import { generateNewId } from 'oak-domain';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { assert } from 'oak-domain/lib/utils/assert';
type ExtraFile = EntityDict['extraFile']['OpSchema'];

View File

@ -72,18 +72,6 @@ const i18ns: I18n[] = [
language: "zh-CN",
module: "oak-general-business",
position: "src/components/extraFile/gallery",
data: {
"waiting": "等待上传",
"success": "上传成功",
"uploading": "上传中"
}
},
{
id: "d30e4a47c1d768d74efae1fade371595",
namespace: "oak-general-business-c-extraFile-gallery2",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/extraFile/gallery2",
data: {}
},
{

View File

@ -84,7 +84,7 @@
"**/*.spec.ts",
"package.json",
"tsconfig.json",
"tsconfig.es.json",
"tsconfig.lib.json",
"package-lock.json",
"test",
"scripts"

View File

@ -85,6 +85,7 @@
"package.json",
"tsconfig.json",
"tsconfig.es.json",
"tsconfig.lib.json",
"package-lock.json",
"test",
"scripts"