- 统一从 utils 目录导入工具函数,而非具体的子文件 - 更新了 uploadFile, buildUrl, formatCurrency 等函数的导入路径 - 修改 CustomTabBar 组件接收 userRoleVO 对象而非 role 字符串 - 调整金额格式化相关工具函数的引用方式 - 更新文档中项目结构和费用管理相关说明 - 优化用户角色权限相关的数据传递逻辑
305 lines
7.2 KiB
Markdown
305 lines
7.2 KiB
Markdown
---
|
||
trigger: manual
|
||
---
|
||
|
||
# 文件上传功能实现指南
|
||
|
||
## 概述
|
||
|
||
文件上传是系统中常见的功能,用于上传图片、文档等文件。本指南将详细介绍如何在系统中实现文件上传功能。
|
||
|
||
## 技术栈
|
||
|
||
系统使用的是基于 Taro 的 React 框架,上传组件来自 `@nutui/nutui-react-taro` 库,文件上传通过 Taro 的 uploadFile API 实现。
|
||
|
||
## 基本用法
|
||
|
||
### 1. 导入相关组件和工具
|
||
|
||
```tsx
|
||
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. 基本结构
|
||
|
||
```tsx
|
||
<Uploader
|
||
value={fileList}
|
||
onChange={handleFileChange}
|
||
sourceType={["album", "camera"]}
|
||
maxCount={1}
|
||
upload={uploadFile}
|
||
multiple
|
||
/>
|
||
```
|
||
|
||
## 实现步骤
|
||
|
||
### 1. 状态管理
|
||
|
||
定义文件列表状态:
|
||
|
||
```tsx
|
||
const [fileList, setFileList] = useState<UploaderFileItem[]>([]);
|
||
```
|
||
|
||
### 2. 实现上传工具函数
|
||
|
||
系统中使用统一的上传工具函数 [utils/uploader.ts](file:///D:/xinfaleisheng/ERPTurbo_Client/packages/app-client/src/utils/uploader.ts):
|
||
|
||
```tsx
|
||
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 回调函数处理文件变化:
|
||
|
||
```tsx
|
||
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 格式:
|
||
|
||
```tsx
|
||
useEffect(() => {
|
||
if (existingFileUrls && existingFileUrls.length > 0) {
|
||
const fileList = existingFileUrls.map((url, index) => ({
|
||
url: url,
|
||
name: `file-${index}`,
|
||
status: 'success'
|
||
}));
|
||
setFileList(fileList);
|
||
}
|
||
}, [existingFileUrls]);
|
||
```
|
||
|
||
## 常见使用场景
|
||
|
||
### 1. 单图片上传
|
||
|
||
用于头像上传等只需要一张图片的场景:
|
||
|
||
```tsx
|
||
<Uploader
|
||
value={avatarList}
|
||
onChange={handleAvatarChange}
|
||
sourceType={["album", "camera"]}
|
||
maxCount={1}
|
||
upload={uploadFile}
|
||
/>
|
||
```
|
||
|
||
### 2. 多图片上传
|
||
|
||
用于需要上传多张图片的场景,如合同、发票等:
|
||
|
||
```tsx
|
||
<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. 空磅照片上传
|
||
2. 总磅照片上传
|
||
3. 发票照片上传
|
||
4. 合同照片上传
|
||
5. 头像上传
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 状态管理
|
||
|
||
- 使用 useState 管理文件列表状态
|
||
- 将上传成功的文件URL保存到业务数据中
|
||
- 在组件卸载时清理相关状态
|
||
|
||
### 2. 用户体验
|
||
|
||
- 提供清晰的上传指引说明
|
||
- 显示上传进度和状态
|
||
- 支持图片预览功能
|
||
- 合理设置最大上传数量
|
||
|
||
### 3. 错误处理
|
||
|
||
- 在上传工具函数中统一处理上传错误
|
||
- 提供友好的错误提示信息
|
||
- 处理网络异常情况
|
||
|
||
### 4. 性能优化
|
||
|
||
- 对大文件上传提供进度提示
|
||
- 支持断点续传(如果后端支持)
|
||
- 合理设置图片压缩参数
|
||
|
||
## 注意事项
|
||
|
||
1. 确保在组件卸载时清理相关状态和副作用
|
||
2. 注意文件大小和格式限制
|
||
3. 处理上传失败的重试机制
|
||
4. 考虑移动端网络环境的适配
|
||
5. 遵循系统统一的上传接口规范
|
||
|
||
## 示例代码
|
||
|
||
```tsx
|
||
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>
|
||
);
|
||
}
|
||
```
|