Skip to content

Commit

Permalink
feat(upload): upload组件支持宽高设置和快速替换配置
Browse files Browse the repository at this point in the history
  • Loading branch information
hitozhang committed Jan 22, 2025
1 parent ece797c commit 29d9e79
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 69 deletions.
102 changes: 60 additions & 42 deletions tdesign-component/example/lib/page/td_upload_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,39 @@ class TDUploadPage extends StatefulWidget {
class TDUploadState extends State<TDUploadPage> {
final List<TDUploadFile> files1 = [];
final List<TDUploadFile> files2 = [
TDUploadFile(
key: 1,
remotePath:
'https://tdesign.gtimg.com/demo/images/example1.png'),
TDUploadFile(
key: 2,
remotePath:
'https://tdesign.gtimg.com/demo/images/example2.png'),
TDUploadFile(
key: 3,
remotePath:
'https://tdesign.gtimg.com/demo/images/example3.png'),
TDUploadFile(key: 1, remotePath: 'https://tdesign.gtimg.com/demo/images/example1.png'),
TDUploadFile(key: 2, remotePath: 'https://tdesign.gtimg.com/demo/images/example2.png'),
TDUploadFile(key: 3, remotePath: 'https://tdesign.gtimg.com/demo/images/example3.png'),
];
final List<TDUploadFile> files3 = [
TDUploadFile(
key: 1,
status: TDUploadFileStatus.loading,
loadingText: '上传中...',
remotePath:
'https://tdesign.gtimg.com/demo/images/example1.png'),
remotePath: 'https://tdesign.gtimg.com/demo/images/example1.png'),
TDUploadFile(
key: 2,
status: TDUploadFileStatus.loading,
progress: 68,
remotePath:
'https://tdesign.gtimg.com/demo/images/example1.png'),
remotePath: 'https://tdesign.gtimg.com/demo/images/example1.png'),
];
final List<TDUploadFile> files4 = [
TDUploadFile(
key: 1,
status: TDUploadFileStatus.retry,
retryText: '重新上传',
remotePath:
'https://tdesign.gtimg.com/demo/images/example1.png'),
remotePath: 'https://tdesign.gtimg.com/demo/images/example1.png'),
];
final List<TDUploadFile> files5 = [
TDUploadFile(
key: 1,
status: TDUploadFileStatus.error,
errorText: '上传失败',
remotePath:
'https://tdesign.gtimg.com/demo/images/example4.png'),
remotePath: 'https://tdesign.gtimg.com/demo/images/example4.png'),
];
final List<TDUploadFile> files6 = [];

void onValueChanged(List<TDUploadFile> fileList, List<TDUploadFile> value,
TDUploadType event) {
void onValueChanged(List<TDUploadFile> fileList, List<TDUploadFile> value, TDUploadType event) {
switch (event) {
case TDUploadType.add:
setState(() {
Expand All @@ -71,6 +58,15 @@ class TDUploadState extends State<TDUploadPage> {
fileList.removeWhere((element) => element.key == value[0].key);
});
break;
case TDUploadType.replace:
setState(() {
final firstReplaceFile = value.first;
final index = fileList.indexWhere((file) => file.key == firstReplaceFile.key);
if (index != -1) {
fileList[index] = firstReplaceFile;
}
});
break;
}
}

Expand All @@ -85,26 +81,31 @@ class TDUploadState extends State<TDUploadPage> {
@override
Widget build(BuildContext context) {
return ExamplePage(
title: tdTitle(),
exampleCodeGroup: 'upload',
desc: '用于相册读取或拉起拍照的图片上传功能。${PlatformUtil.isWeb ? "Web端不支持读取本地图片,请前往移动端体验。" : ""}',
children: [
ExampleModule(
title: '组件类型',
children: [
ExampleItem(desc: '单选上传', builder: _uploadSingle),
ExampleItem(desc: '多选上传', builder: _uploadMultiple),
],
),
ExampleModule(
title: '组件状态',
children: [
ExampleItem(desc: '加载状态', builder: _uploadLoading),
ExampleItem(desc: '重新上传', builder: _uploadRetry),
ExampleItem(desc: '上传失败', builder: _uploadError),
],
),
]);
title: tdTitle(),
exampleCodeGroup: 'upload',
desc: '用于相册读取或拉起拍照的图片上传功能。${PlatformUtil.isWeb ? "Web端不支持读取本地图片,请前往移动端体验。" : ""}',
children: [
ExampleModule(
title: '组件类型',
children: [
ExampleItem(desc: '单选上传', builder: _uploadSingle),
ExampleItem(desc: '单选上传(替换)', builder: _uploadSingleWithReplace),
ExampleItem(desc: '多选上传', builder: _uploadMultiple),
],
),
ExampleModule(
title: '组件状态',
children: [
ExampleItem(desc: '加载状态', builder: _uploadLoading),
ExampleItem(desc: '重新上传', builder: _uploadRetry),
ExampleItem(desc: '上传失败', builder: _uploadError),
],
),
],
test: [
ExampleItem(ignoreCode: true, desc: '单选快速替换, 大小和图形测试', builder: _uploadSingleWithReplace),
],
);
}

Widget wrapDemoContainer(String title, {required Widget child}) {
Expand Down Expand Up @@ -140,6 +141,23 @@ class TDUploadState extends State<TDUploadPage> {
));
}

@Demo(group: 'upload')
Widget _uploadSingleWithReplace(BuildContext context) {
return wrapDemoContainer('单选上传(替换)',
child: TDUpload(
files: files6,
width: 60,
height: 60,
type: TDUploadBoxType.circle,
enabledReplaceType: true,
onClick: onClick,
onCancel: onCancel,
onError: print,
onValidate: print,
onChange: ((files, type) => onValueChanged(files6, files, type)),
));
}

@Demo(group: 'upload')
Widget _uploadMultiple(BuildContext context) {
return wrapDemoContainer('多选上传',
Expand Down
137 changes: 111 additions & 26 deletions tdesign-component/lib/src/components/upload/td_upload.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ enum TDUploadFileStatus {
enum TDUploadType {
add, // 添加
remove, // 删除
replace, // 替换
}

enum TDUploadBoxType {
roundedSquare, // 圆角方形
circle, // 圆形
}

class TDUploadFile {
Expand Down Expand Up @@ -70,7 +76,11 @@ class TDUpload extends StatefulWidget {
this.onClick,
required this.files,
this.onChange,
this.multiple = false})
this.multiple = false,
this.width = 80.0,
this.height = 80.0,
this.type = TDUploadBoxType.roundedSquare,
this.enabledReplaceType = false})
: super(key: key);

/// 控制展示的文件列表
Expand Down Expand Up @@ -100,9 +110,21 @@ class TDUpload extends StatefulWidget {
/// 监听点击图片位
final TDUploadClickEvent? onClick;

/// 监听添加或删除照片
/// 监听添加, 删除和替换media事件
final TDUploadValueChangedEvent? onChange;

/// 图片宽度
final double? width;

/// 图片高度
final double? height;

/// Box类型
final TDUploadBoxType type;

/// 是否启用replace功能
final bool? enabledReplaceType;

@override
State<TDUpload> createState() => _TDUploadState();
}
Expand All @@ -113,22 +135,28 @@ class _TDUploadState extends State<TDUpload> {
bool get canUpload => widget.multiple ? (widget.max == 0 ? true : fileList.length < widget.max) : fileList.isEmpty;
final ImagePicker _picker = ImagePicker();

// 类型映射
final Map<TDUploadBoxType, TDImageType> _imageTypeMap = {
TDUploadBoxType.roundedSquare: TDImageType.roundedSquare,
TDUploadBoxType.circle: TDImageType.circle,
};

@override
initState() {
super.initState();
fileList = widget.files;
}

// 获取相册照片或视频
Future<List<XFile>> getMediaFromPicker() async {
if (!canUpload || widget.mediaType.isEmpty) {
Future<List<XFile>> getMediaFromPicker(bool isMultiple) async {
if (widget.mediaType.isEmpty) {
return [];
}

List<XFile> medias;

try {
if (widget.multiple) {
if (isMultiple) {
if (widget.mediaType.length == 1 && widget.mediaType.contains(TDUploadMediaType.image)) {
medias = await _picker.pickMultiImage();
} else {
Expand Down Expand Up @@ -194,11 +222,39 @@ class _TDUploadState extends State<TDUpload> {
}
}

// 替换资源
void replaceMedia(List<XFile> files, TDUploadFile oldFile) async {
if (files.isEmpty || files.length != 1) {
return;
}

var result = await validateResources(files);

if (result != null) {
if (widget.onValidate != null) {
widget.onValidate!(result);
}
return;
}

var newFile = TDUploadFile(key: oldFile.key, file: File(files[0].path), assetPath: files[0].path);

if (widget.onChange != null) {
widget.onChange!([newFile], TDUploadType.replace);
}
}

// 校验资源
Future<TDUploadValidatorError?> validateResources(List<XFile> files) async {
Future<TDUploadValidatorError?> validateResources(List<XFile> files, [bool? multiple]) async {
TDUploadValidatorError? error;

if (widget.multiple && widget.max > 0) {
// 多选逻辑,优选从参数获取
var isMultiple = widget.multiple;
if (multiple != null) {
isMultiple = multiple;
}

if (isMultiple && widget.max > 0) {
var remain = widget.max - fileList.length;

if (files.length > remain) {
Expand Down Expand Up @@ -238,7 +294,11 @@ class _TDUploadState extends State<TDUpload> {
children: [
...fileList.map((file) => _buildImageBox(context, file)).toList(),
_buildUploadBox(context, shouldDisplay: canUpload, onTap: () async {
final files = await getMediaFromPicker();
if (!canUpload) {
return;
}

final files = await getMediaFromPicker(widget.multiple);
extractImageList(files);
}),
],
Expand All @@ -252,9 +312,14 @@ class _TDUploadState extends State<TDUpload> {
child: GestureDetector(
onTap: onTap,
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(color: TDTheme.of(context).grayColor1, borderRadius: BorderRadius.circular(6)),
width: widget.width,
height: widget.height,
decoration: widget.type == TDUploadBoxType.circle
? BoxDecoration(
shape: BoxShape.circle,
color: TDTheme.of(context).grayColor1,
)
: BoxDecoration(color: TDTheme.of(context).grayColor1, borderRadius: BorderRadius.circular(6)),
child: const Center(
child: Icon(
TDIcons.add,
Expand All @@ -266,19 +331,26 @@ class _TDUploadState extends State<TDUpload> {

Widget _buildImageBox(BuildContext context, TDUploadFile file) {
return GestureDetector(
onTap: () {
onTap: () async {
if (widget.onClick != null) {
widget.onClick!(file.key);
}
// 替换资源
if (widget.enabledReplaceType ?? false) {
final files = await getMediaFromPicker(false);
replaceMedia(files, file);
}
},
child: Stack(
children: [
TDImage(
width: 80,
height: 80,
key: Key(file.assetPath ?? ''),
width: widget.width,
height: widget.height,
imgUrl: file.remotePath,
// assetUrl: file.assetPath,
imageFile: file.file,
type: _imageTypeMap[widget.type] ?? TDImageType.roundedSquare,
),
Visibility(visible: file.status != TDUploadFileStatus.success, child: _buildShadowBox(file)),
Visibility(
Expand All @@ -293,10 +365,15 @@ class _TDUploadState extends State<TDUpload> {
child: Container(
width: 20,
height: 20,
decoration: const BoxDecoration(
color: Color.fromRGBO(0, 0, 0, 0.6),
borderRadius:
BorderRadius.only(bottomLeft: Radius.circular(6), topRight: Radius.circular(6))),
decoration: widget.type == TDUploadBoxType.circle
? const BoxDecoration(
shape: BoxShape.circle,
color: Color.fromRGBO(0, 0, 0, 0.6),
)
: const BoxDecoration(
color: Color.fromRGBO(0, 0, 0, 0.6),
borderRadius:
BorderRadius.only(bottomLeft: Radius.circular(6), topRight: Radius.circular(6))),
child: const Center(
child: Icon(
TDIcons.close,
Expand Down Expand Up @@ -326,14 +403,19 @@ class _TDUploadState extends State<TDUpload> {
}

return Container(
width: 80,
height: 80,
decoration: BoxDecoration(color: const Color.fromRGBO(0, 0, 0, 0.4), borderRadius: BorderRadius.circular(6)),
width: widget.width,
height: widget.height,
decoration: widget.type == TDUploadBoxType.circle
? const BoxDecoration(
shape: BoxShape.circle,
color: Color.fromRGBO(0, 0, 0, 0.4),
)
: BoxDecoration(color: const Color.fromRGBO(0, 0, 0, 0.4), borderRadius: BorderRadius.circular(6)),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Visibility(
visible: file.status == TDUploadFileStatus.loading,
Expand All @@ -350,10 +432,13 @@ class _TDUploadState extends State<TDUpload> {
size: 24,
color: Colors.white,
)),
TDText(
displayText,
textColor: Colors.white,
style: const TextStyle(fontSize: 12, height: 1.67),
Padding(
padding: const EdgeInsets.only(top: 4),
child: TDText(
displayText,
textColor: Colors.white,
style: const TextStyle(fontSize: 12, height: 1.67),
),
),
],
),
Expand Down
Loading

0 comments on commit 29d9e79

Please sign in to comment.