- 统一从 utils 目录导入工具函数,而非具体的子文件 - 更新了 uploadFile, buildUrl, formatCurrency 等函数的导入路径 - 修改 CustomTabBar 组件接收 userRoleVO 对象而非 role 字符串 - 调整金额格式化相关工具函数的引用方式 - 更新文档中项目结构和费用管理相关说明 - 优化用户角色权限相关的数据传递逻辑
7.2 KiB
7.2 KiB
| trigger |
|---|
| manual |
文件上传功能实现指南
概述
文件上传是系统中常见的功能,用于上传图片、文档等文件。本指南将详细介绍如何在系统中实现文件上传功能。
技术栈
系统使用的是基于 Taro 的 React 框架,上传组件来自 @nutui/nutui-react-taro 库,文件上传通过 Taro 的 uploadFile API 实现。
基本用法
1. 导入相关组件和工具
import { Uploader, UploaderFileItem } from "@nutui/nutui-react-taro";
import { uploadFile } from "@/utils";
2. 基本属性
Uploader 组件常用属性包括:
value: 当前已上传的文件列表(UploaderFileItem[])onChange: 文件列表变化时的回调函数upload: 自定义上传函数sourceType: 选择图片的来源(["album", "camera"])maxCount: 最大上传数量multiple: 是否支持多选uploadIcon: 自定义上传图标uploadLabel: 自定义上传标签文本
3. 基本结构
<Uploader
value={fileList}
onChange={handleFileChange}
sourceType={["album", "camera"]}
maxCount={1}
upload={uploadFile}
multiple
/>
实现步骤
1. 状态管理
定义文件列表状态:
const [fileList, setFileList] = useState<UploaderFileItem[]>([]);
2. 实现上传工具函数
系统中使用统一的上传工具函数 utils/uploader.ts:
import Taro from "@tarojs/taro";
import { Toast } from "@nutui/nutui-react-taro";
export const uploadFile = async (file: File | string) => {
console.log("file", file);
const res = await Taro.uploadFile({
url: process.env.TARO_API_DOMAIN + `/auth/upload`,
// @ts-ignore
filePath: file.tempFilePath || file,
name: "file",
header: {
saToken: Taro.getStorageSync("saToken"),
"Content-Type": "multipart/form-data",
},
});
if (res.errMsg == "uploadFile:ok") {
const data = JSON.parse(res.data);
if (data.errCode == "401") {
Taro.removeStorageSync("user");
Toast.show("toast", {
icon: "warn",
title: "",
content: "超时请重试",
});
return Promise.reject();
} else {
return {
url: data?.data,
};
}
} else {
Toast.show("toast", {
icon: "fail",
title: "",
content: "上传失败",
});
return Promise.reject();
}
};
3. 处理文件变化
实现 onChange 回调函数处理文件变化:
const handleFileChange = (files: UploaderFileItem[]) => {
setFileList(files);
// 处理上传后的逻辑
if (files.length > 0 && files[0].status === 'success') {
// 文件上传成功,可以保存URL到业务数据中
const fileUrls = files.map(file => file.url).filter(url => url) as string[];
// 保存到业务状态中
setBusinessData(fileUrls);
}
};
4. 初始化已上传文件
如果有已上传的文件需要显示,需要将其转换为 UploaderFileItem 格式:
useEffect(() => {
if (existingFileUrls && existingFileUrls.length > 0) {
const fileList = existingFileUrls.map((url, index) => ({
url: url,
name: `file-${index}`,
status: 'success'
}));
setFileList(fileList);
}
}, [existingFileUrls]);
常见使用场景
1. 单图片上传
用于头像上传等只需要一张图片的场景:
<Uploader
value={avatarList}
onChange={handleAvatarChange}
sourceType={["album", "camera"]}
maxCount={1}
upload={uploadFile}
/>
2. 多图片上传
用于需要上传多张图片的场景,如合同、发票等:
<Uploader
value={contractImgList}
onChange={handleContractImgChange}
sourceType={["album", "camera"]}
uploadIcon={<Icon name={"camera"} size={36} />}
uploadLabel={
<View className={"flex flex-col items-center"}>
<View className="text-sm">拍照上传合同</View>
</View>
}
maxCount={5}
upload={uploadFile}
multiple
/>
3. 不同类型文件上传
系统中常见的文件上传场景包括:
- 空磅照片上传
- 总磅照片上传
- 发票照片上传
- 合同照片上传
- 头像上传
最佳实践
1. 状态管理
- 使用 useState 管理文件列表状态
- 将上传成功的文件URL保存到业务数据中
- 在组件卸载时清理相关状态
2. 用户体验
- 提供清晰的上传指引说明
- 显示上传进度和状态
- 支持图片预览功能
- 合理设置最大上传数量
3. 错误处理
- 在上传工具函数中统一处理上传错误
- 提供友好的错误提示信息
- 处理网络异常情况
4. 性能优化
- 对大文件上传提供进度提示
- 支持断点续传(如果后端支持)
- 合理设置图片压缩参数
注意事项
- 确保在组件卸载时清理相关状态和副作用
- 注意文件大小和格式限制
- 处理上传失败的重试机制
- 考虑移动端网络环境的适配
- 遵循系统统一的上传接口规范
示例代码
import { View } from "@tarojs/components";
import { Uploader, UploaderFileItem } from "@nutui/nutui-react-taro";
import { useState, useEffect } from "react";
import { uploadFile } from "@/utils";
export default function UploadExample() {
// 头像上传状态
const [avatarList, setAvatarList] = useState<UploaderFileItem[]>([]);
// 合同照片上传状态
const [contractImgList, setContractImgList] = useState<UploaderFileItem[]>([]);
// 初始化已上传的文件
useEffect(() => {
// 假设从服务器获取到已上传的文件URL
const existingAvatarUrl = "https://example.com/avatar.jpg";
if (existingAvatarUrl) {
setAvatarList([{
url: existingAvatarUrl,
name: 'avatar',
status: 'success'
}]);
}
}, []);
// 处理头像上传变化
const handleAvatarChange = (files: UploaderFileItem[]) => {
setAvatarList(files);
// 如果上传成功,保存URL到业务数据
if (files.length > 0 && files[0].status === 'success' && files[0].url) {
// 保存头像URL到用户信息中
saveAvatarUrl(files[0].url);
}
};
// 处理合同照片上传变化
const handleContractImgChange = (files: UploaderFileItem[]) => {
setContractImgList(files);
// 保存所有成功上传的文件URL
const urls = files
.filter(file => file.status === 'success' && file.url)
.map(file => file.url!) as string[];
// 保存到业务数据中
saveContractImgUrls(urls);
};
return (
<View className="p-4">
<View className="mb-6">
<View className="mb-2 text-sm font-medium">头像上传</View>
<Uploader
value={avatarList}
onChange={handleAvatarChange}
sourceType={["album", "camera"]}
maxCount={1}
upload={uploadFile}
/>
</View>
<View>
<View className="mb-2 text-sm font-medium">合同照片上传</View>
<Uploader
value={contractImgList}
onChange={handleContractImgChange}
sourceType={["album", "camera"]}
maxCount={5}
upload={uploadFile}
multiple
/>
</View>
</View>
);
}