This commit is contained in:
Ling53666
2025-08-18 09:11:51 +08:00
commit 02554225da
2516 changed files with 133155 additions and 0 deletions

View File

@ -0,0 +1,75 @@
<view
class="ant-image-upload {{className || ''}}"
style="{{style || ''}}"
>
<slot fileList="{{mixin.value}}">
<block
a:for="{{mixin.value}}"
a:for-index="index"
a:for-item="item"
key="{{item.uid}}"
>
<view class="ant-image-upload-show">
<view
data-uid="{{item.uid}}"
onTap="onRemove"
>
<slot name="removeButton">
<view class="ant-image-upload-close"></view>
</slot>
</view>
<slot
name="image"
value="{{item}}"
>
<view
a:if="{{item.status === 'uploading' || item.status === 'error'}}"
class="ant-image-upload-cover"
>
<view
a:if="{{item.status === 'uploading'}}"
class="ant-image-upload-cover-loading"
>
<loading className="ant-image-upload-cover-loading-icon"></loading>
<view class="ant-image-upload-cover-loading-text">
{{uploadingText}}
</view>
</view>
<view
a:if="{{item.status === 'error'}}"
class="ant-image-upload-cover-error"
>
<icon
className="ant-image-upload-cover-error-icon"
type="CloseCircleOutline"
></icon>
<view class="ant-image-upload-cover-error-text">
{{uploadfailedText}}
</view>
</view>
</view>
<image
class="ant-image-upload-image"
mode="{{imageMode}}"
src="{{item.url || item.path}}"
data-uid="{{item.uid}}"
onTap="onPreview"
></image>
</slot>
</view>
</block>
<view onTap="chooseImage">
<slot name="uploadButton">
<view
a:if="{{showUploadButton}}"
class="ant-image-upload-add-image-wrapper"
>
<icon
type="AddOutline"
className="ant-image-upload-add-image-icon"
></icon>
</view>
</slot>
</view>
</slot>
</view>

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,235 @@
import { __assign, __awaiter, __generator, __spreadArray } from "tslib";
import { Component, triggerEvent, getValueFromProps } from '../_util/simply';
import { UploaderDefaultProps } from './props';
import { chooseImage } from '../_util/jsapi/choose-image';
import createValue from '../mixins/value';
Component(UploaderDefaultProps, {
chooseImage: function () {
return __awaiter(this, void 0, void 0, function () {
var _a, onBeforeUpload, onUpload, fileList, _b, maxCount, sourceType, localFileList, chooseImageRes, err_1, beforeUploadRes, err_2, tasks;
var _this = this;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_a = getValueFromProps(this, [
'onBeforeUpload',
'onUpload',
]), onBeforeUpload = _a[0], onUpload = _a[1];
if (!onUpload) {
throw new Error('need props onUpload');
}
fileList = this.getValue();
_b = getValueFromProps(this, [
'maxCount',
'sourceType',
]), maxCount = _b[0], sourceType = _b[1];
_c.label = 1;
case 1:
_c.trys.push([1, 3, , 4]);
return [4 /*yield*/, chooseImage({
count: typeof maxCount === 'undefined'
? Infinity
: maxCount - fileList.length,
sourceType: sourceType,
})];
case 2:
chooseImageRes = _c.sent();
localFileList = (chooseImageRes.tempFiles ||
chooseImageRes.tempFilePaths ||
chooseImageRes.apFilePaths ||
chooseImageRes.filePaths ||
[])
.map(function (item) {
if (typeof item === 'string') {
return {
path: item,
};
}
if (item.path) {
return {
path: item.path,
size: item.size,
};
}
})
.filter(function (item) { return !!item; });
return [3 /*break*/, 4];
case 3:
err_1 = _c.sent();
triggerEvent(this, 'chooseImageError', err_1);
return [2 /*return*/];
case 4:
if (!onBeforeUpload) return [3 /*break*/, 8];
_c.label = 5;
case 5:
_c.trys.push([5, 7, , 8]);
return [4 /*yield*/, onBeforeUpload(localFileList)];
case 6:
beforeUploadRes = _c.sent();
if (beforeUploadRes === false) {
return [2 /*return*/];
}
if (Array.isArray(beforeUploadRes)) {
localFileList = beforeUploadRes;
}
return [3 /*break*/, 8];
case 7:
err_2 = _c.sent();
return [2 /*return*/];
case 8:
tasks = localFileList.map(function (file) { return _this.uploadFile(file); });
return [4 /*yield*/, Promise.all(tasks)];
case 9:
_c.sent();
return [2 /*return*/];
}
});
});
},
uploadFile: function (localFile) {
return __awaiter(this, void 0, void 0, function () {
var onUpload, uid, tempFileList, url, err_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
onUpload = getValueFromProps(this, 'onUpload');
uid = this.getCount();
tempFileList = __spreadArray(__spreadArray([], this.getValue(), true), [
{
path: localFile.path,
size: localFile.size,
uid: uid,
status: 'uploading',
},
], false);
if (!this.isControlled()) {
this.update(tempFileList);
}
triggerEvent(this, 'change', tempFileList);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, onUpload(localFile)];
case 2:
url = _a.sent();
if (typeof url !== 'string' || !url) {
this.updateFile(uid, {
status: 'error',
});
return [2 /*return*/];
}
this.updateFile(uid, {
status: 'done',
url: url,
});
return [3 /*break*/, 4];
case 3:
err_3 = _a.sent();
this.updateFile(uid, {
status: 'error',
});
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
});
},
updateFile: function (uid, file) {
var fileList = this.getValue();
var tempFileList = fileList.map(function (item) {
if (item.uid === uid) {
return __assign(__assign({}, item), file);
}
return item;
});
if (!this.isControlled()) {
this.update(tempFileList);
}
triggerEvent(this, 'change', tempFileList);
},
onRemove: function (e) {
return __awaiter(this, void 0, void 0, function () {
var fileList, onRemove, uid, file, result, tempFileList;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fileList = this.getValue();
onRemove = getValueFromProps(this, 'onRemove');
uid = e.currentTarget.dataset.uid;
file = fileList.find(function (item) { return item.uid === uid; });
if (!onRemove) return [3 /*break*/, 2];
return [4 /*yield*/, onRemove(file)];
case 1:
result = _a.sent();
if (result === false) {
return [2 /*return*/];
}
_a.label = 2;
case 2:
tempFileList = fileList.filter(function (item) { return item.uid !== uid; });
if (!this.isControlled()) {
this.update(tempFileList);
}
triggerEvent(this, 'change', tempFileList);
return [2 /*return*/];
}
});
});
},
onPreview: function (e) {
var uid = e.currentTarget.dataset.uid;
var fileList = this.getValue();
var file = fileList.find(function (item) { return item.uid === uid; });
triggerEvent(this, 'preview', file);
},
updateShowUploadButton: function () {
var maxCount = getValueFromProps(this, 'maxCount');
this.setData({
showUploadButton: !maxCount || this.getValue().length < maxCount,
});
},
count: 0,
getCount: function () {
// 使用 Date.now() 与 useId 作为前缀,防止每次前缀都相同
this.count = (this.count || 0) + 1;
// 使用 Date.now() 与 useId 作为前缀,防止每次前缀都相同
var id = this.id;
id = this.$id;
var prefix = id + '-' + Date.now();
return "".concat(prefix, "-").concat(this.count);
},
}, null, [
createValue({
defaultValueKey: 'defaultFileList',
valueKey: 'fileList',
transformValue: function (fileList) {
var _this = this;
if (fileList === void 0) { fileList = []; }
return {
needUpdate: true,
value: (fileList || []).map(function (item) {
var file = __assign({}, item);
if (typeof item.url === 'undefined') {
file.url = '';
}
if (typeof item.uid === 'undefined') {
file.uid = _this.getCount();
}
if (typeof item.status === 'undefined') {
file.status = 'done';
}
return file;
}),
};
},
}),
], {
didMount: function () {
this.updateShowUploadButton();
},
didUpdate: function (prevProps, prevData) {
if (!this.isEqualValue(prevData)) {
this.updateShowUploadButton();
}
},
});

View File

@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"icon": "../Icon/index",
"loading": "../Loading/index"
}
}

View File

@ -0,0 +1,86 @@
@import (reference) './variable.less';
@imageUploadPrefix: ant-image-upload;
.@{imageUploadPrefix} {
display: flex;
flex-wrap: wrap;
background: @COLOR_CARD;
&-show {
position: relative;
}
&-image {
width: 160 * @rpx;
height: 160 * @rpx;
margin: @image-upload-common-margin;
border-radius: @corner-radius-md;
}
&-close {
width: @image-upload-close-tip-width;
height: @image-upload-close-tip-height;
position: absolute;
z-index: 99;
top: @image-upload-size-base * -1;
right: 0;
background: url(@image-upload-close-tip-url) no-repeat;
background-size: cover;
}
&-add-image-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 160 * @rpx;
height: 160 * @rpx;
margin: @image-upload-add-image-container-margin;
background-color: @COLOR_GREY_CARD;
border-radius: @corner-radius-md;
}
&-add-image-icon {
color: @COLOR_TEXT_ASSIST;
}
&-cover {
position: absolute;
width: 160 * @rpx;
height: 160 * @rpx;
margin: @image-upload-common-margin;
border-radius: @corner-radius-md;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
&-loading {
text-align: center;
&-icon {
width: 48 * @rpx;
height: 48 * @rpx;
}
&-text {
color: @COLOR_WHITE;
font-size: 24 * @rpx;
margin-top: 8 * @rpx;
}
}
&-error {
text-align: center;
&-icon {
font-size: 48rpx;
color: @COLOR_WHITE;
}
&-text {
color: @COLOR_WHITE;
font-size: 24rpx;
margin-top: 8rpx;
}
}
}
}

View File

@ -0,0 +1,80 @@
import { IBaseProps } from '../_util/base';
export type Status = 'uploading' | 'done' | 'error';
export interface File {
/**
* @description 唯一标识符,不设置时会自动生成
*/
uid?: string;
/**
* @description 图片的资源地址
*/
url: string;
/**
* @description 上传状态
*/
status?: Status;
}
export interface LocalFile {
path: string;
size?: number;
}
export interface IUploaderProps extends IBaseProps {
/**
* @description 默认已经上传的文件列表
* @default []
*/
defaultFileList: File[];
/**
* @description 已经上传的文件列表(受控)
*/
fileList: File[];
/**
* @description 上传图片的最大数量
*/
maxCount: number;
/**
* @description 图片缩放模式和裁剪模式
* @default 'scaleToFill'
*/
imageMode: 'scaleToFill' | 'aspectFit' | 'aspectFill' | 'widthFix' | 'heightFix' | 'top' | 'bottom' | 'center' | 'left' | 'right' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
/**
* @description 视频选择的来源
* @default ['album', 'camera']
*/
sourceType: ['album'] | ['camera'] | ['camera', 'album'];
/**
* @description 图片上传前的回调函数,返回 false 可终止图片上传,支持返回 Promise
*/
onBeforeUpload?: (localFileList: LocalFile[]) => boolean | Promise<LocalFile[]>;
/**
* @description 选择图片失败回调
*/
onChooseImageError?: (err: any) => void;
/**
* @description 已上传的文件列表变化时触发
*/
onChange?: (v: Array<File>) => void;
/**
* @description 删除当前列表中的图片时触发,包括上传成功和上传失败的图片,如果返回 false 表示阻止删除,支持返回 Promise
*/
onRemove?: (v: File) => boolean | Promise<boolean>;
/**
* @description 点击图片进行预览时触发,会覆盖默认的预览功能
*/
onPreview?: (v: Array<File>) => void;
/**
* @description 自定义上传方式只在不存在action字段时生效
*/
onUpload?: (localFile: LocalFile) => Promise<string>;
/**
* @description 上传中文案
* @default "上传中……"
*/
uploadingText?: string;
/**
* @description 上传失败文案
* @default "上传失败"
*/
uploadfailedText?: string;
}
export declare const UploaderDefaultProps: Partial<IUploaderProps>;

View File

@ -0,0 +1,12 @@
export var UploaderDefaultProps = {
defaultFileList: [],
fileList: null,
maxCount: null,
imageMode: 'scaleToFill',
sourceType: ['camera', 'album'],
onUpload: null,
onBeforeUpload: null,
onRemove: null,
uploadingText: '上传中',
uploadfailedText: '上传失败',
};

View File

@ -0,0 +1,18 @@
@import (reference) '../style/themes/index.less';
@image-upload-size-base: 4 * @rpx;
@image-upload-margin-size-base: 4 * @rpx;
@image-upload-margin-size-1: @image-upload-margin-size-base * 1;
@image-upload-margin-size-2: @image-upload-margin-size-base * 2;
@image-upload-margin-size-3: @image-upload-margin-size-base * 3;
@image-upload-common-margin: @image-upload-margin-size-1
@image-upload-margin-size-2;
@image-upload-add-image-container-margin: @image-upload-margin-size-1
@image-upload-margin-size-2 @image-upload-margin-size-3;
@image-upload-close-tip-width: @image-upload-size-base * 10;
@image-upload-close-tip-height: @image-upload-size-base * 10;
@image-upload-close-tip-url: 'https://gw.alipayobjects.com/mdn/rms_226d75/afts/img/A*_Az1QavR4OsAAAAAAAAAAAAAARQnAQ';