ERPTurbo_Client/.lingma/rules/upload-implementation.md
shenyifei 9213b90d61 feat(app-client): 重构采购审批页面并优化样式配置
- 重构采购审批页面,移除冗余的表单逻辑和校验代码
- 新增基础信息模块自动获取上一车次号功能
- 优化自定义主题配置,统一使用 Taro.pxTransform 处理单位
- 调整页面列表组件的数据加载逻辑,支持分页追加数据
- 优化成本相关组件的价格展示样式,统一字体大小和颜色
- 移除页面中冗余的状态管理和副作用逻辑
- 调整审批页面布局结构,提升用户体验
2025-11-13 11:47:00 +08:00

304 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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/uploader";
```
### 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/uploader";
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>
);
}
```