FEAT => 本次更新项目为:完善上传组件使用说明,增加数据回显功能示例,优化代码结构,移除冗余代码
This commit is contained in:
@@ -1,22 +1,28 @@
|
|||||||
# Upload 组件使用说明
|
# Upload 组件使用说明
|
||||||
|
|
||||||
## MainImgUpload 主图封面上传组件
|
## 组件概述
|
||||||
|
|
||||||
### 功能特点
|
本项目提供了多个专门的上传组件,所有组件都支持编辑时的数据回显功能,确保在编辑模式下能够正确显示已上传的文件。
|
||||||
|
|
||||||
|
## 组件列表
|
||||||
|
|
||||||
|
### 1. MainImgUpload 主图封面上传组件
|
||||||
|
|
||||||
|
#### 功能特点
|
||||||
- 只支持上传一张图片作为主图封面
|
- 只支持上传一张图片作为主图封面
|
||||||
- 上传后右上角显示删除按钮
|
- 上传后右上角显示删除按钮
|
||||||
- 支持图片预览功能
|
- 支持图片预览功能
|
||||||
- 响应式设计,适配移动端
|
- 响应式设计,适配移动端
|
||||||
- 样式参考VideoUpload组件风格
|
- 16:9宽高比,宽度高度自适应
|
||||||
|
- **支持数据回显**:编辑时自动显示已上传的图片
|
||||||
|
|
||||||
### 使用方法
|
#### 使用方法
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import MainImgUpload from "@/components/Upload/MainImgUpload";
|
import MainImgUpload from '@/components/Upload/MainImgUpload';
|
||||||
|
|
||||||
const MyComponent = () => {
|
const MyComponent = () => {
|
||||||
const [mainImage, setMainImage] = useState<string>("");
|
const [mainImage, setMainImage] = useState<string>('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainImgUpload
|
<MainImgUpload
|
||||||
@@ -30,39 +36,230 @@ const MyComponent = () => {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Props 参数
|
#### 编辑模式数据回显
|
||||||
|
```tsx
|
||||||
|
// 编辑模式下,传入已有的图片URL
|
||||||
|
const [mainImage, setMainImage] = useState<string>('https://example.com/image.jpg');
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
<MainImgUpload
|
||||||
| ----------- | --------------------- | ------ | ---------------- |
|
value={mainImage} // 会自动显示已上传的图片
|
||||||
| value | string | '' | 当前图片URL |
|
onChange={setMainImage}
|
||||||
| onChange | (url: string) => void | - | 图片URL变化回调 |
|
/>
|
||||||
| disabled | boolean | false | 是否禁用 |
|
|
||||||
| className | string | - | 自定义样式类名 |
|
|
||||||
| maxSize | number | 5 | 最大文件大小(MB) |
|
|
||||||
| showPreview | boolean | true | 是否显示预览按钮 |
|
|
||||||
|
|
||||||
### 样式特点
|
|
||||||
|
|
||||||
- 上传区域:200x200px 的虚线边框区域
|
|
||||||
- 图片预览:上传后显示图片,鼠标悬停显示操作按钮
|
|
||||||
- 删除按钮:右上角红色删除图标
|
|
||||||
- 预览按钮:眼睛图标,点击在新窗口预览
|
|
||||||
- 响应式:移动端自动调整尺寸
|
|
||||||
|
|
||||||
### 文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
src/components/Upload/
|
|
||||||
├── MainImgUpload.tsx # 主图上传组件
|
|
||||||
├── mainImgUpload.module.scss # 主图上传样式
|
|
||||||
├── VideoUpload.tsx # 视频上传组件
|
|
||||||
└── index.module.scss # 通用上传样式
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 技术实现
|
### 2. ImageUpload 多图上传组件
|
||||||
|
|
||||||
|
#### 功能特点
|
||||||
|
- 支持多张图片上传
|
||||||
|
- 可设置最大上传数量
|
||||||
|
- 支持图片预览和删除
|
||||||
|
- **支持数据回显**:编辑时自动显示已上传的图片数组
|
||||||
|
|
||||||
|
#### 使用方法
|
||||||
|
```tsx
|
||||||
|
import ImageUpload from '@/components/Upload/ImageUpload/ImageUpload';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [images, setImages] = useState<string[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageUpload
|
||||||
|
value={images}
|
||||||
|
onChange={setImages}
|
||||||
|
count={9} // 最大9张
|
||||||
|
accept="image/*"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编辑模式数据回显
|
||||||
|
```tsx
|
||||||
|
// 编辑模式下,传入已有的图片URL数组
|
||||||
|
const [images, setImages] = useState<string[]>([
|
||||||
|
'https://example.com/image1.jpg',
|
||||||
|
'https://example.com/image2.jpg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
<ImageUpload
|
||||||
|
value={images} // 会自动显示已上传的图片
|
||||||
|
onChange={setImages}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. VideoUpload 视频上传组件
|
||||||
|
|
||||||
|
#### 功能特点
|
||||||
|
- 支持视频文件上传
|
||||||
|
- 支持单个或多个视频
|
||||||
|
- 视频预览功能
|
||||||
|
- 文件大小验证
|
||||||
|
- **支持数据回显**:编辑时自动显示已上传的视频
|
||||||
|
|
||||||
|
#### 使用方法
|
||||||
|
```tsx
|
||||||
|
import VideoUpload from '@/components/Upload/VideoUpload';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [videoUrl, setVideoUrl] = useState<string>('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoUpload
|
||||||
|
value={videoUrl}
|
||||||
|
onChange={setVideoUrl}
|
||||||
|
maxSize={50} // 最大50MB
|
||||||
|
showPreview={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编辑模式数据回显
|
||||||
|
```tsx
|
||||||
|
// 编辑模式下,传入已有的视频URL
|
||||||
|
const [videoUrl, setVideoUrl] = useState<string>('https://example.com/video.mp4');
|
||||||
|
|
||||||
|
<VideoUpload
|
||||||
|
value={videoUrl} // 会自动显示已上传的视频
|
||||||
|
onChange={setVideoUrl}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. FileUpload 文件上传组件
|
||||||
|
|
||||||
|
#### 功能特点
|
||||||
|
- 支持Excel、Word、PPT等文档文件
|
||||||
|
- 可配置接受的文件类型
|
||||||
|
- 文件预览和下载
|
||||||
|
- **支持数据回显**:编辑时自动显示已上传的文件
|
||||||
|
|
||||||
|
#### 使用方法
|
||||||
|
```tsx
|
||||||
|
import FileUpload from '@/components/Upload/FileUpload';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [fileUrl, setFileUrl] = useState<string>('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FileUpload
|
||||||
|
value={fileUrl}
|
||||||
|
onChange={setFileUrl}
|
||||||
|
maxSize={10} // 最大10MB
|
||||||
|
acceptTypes={['excel', 'word', 'ppt']}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编辑模式数据回显
|
||||||
|
```tsx
|
||||||
|
// 编辑模式下,传入已有的文件URL
|
||||||
|
const [fileUrl, setFileUrl] = useState<string>('https://example.com/document.xlsx');
|
||||||
|
|
||||||
|
<FileUpload
|
||||||
|
value={fileUrl} // 会自动显示已上传的文件
|
||||||
|
onChange={setFileUrl}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. AvatarUpload 头像上传组件
|
||||||
|
|
||||||
|
#### 功能特点
|
||||||
|
- 专门的头像上传组件
|
||||||
|
- 圆形头像显示
|
||||||
|
- 支持删除和重新上传
|
||||||
|
- **支持数据回显**:编辑时自动显示已上传的头像
|
||||||
|
|
||||||
|
#### 使用方法
|
||||||
|
```tsx
|
||||||
|
import AvatarUpload from '@/components/Upload/AvatarUpload';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [avatarUrl, setAvatarUrl] = useState<string>('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AvatarUpload
|
||||||
|
value={avatarUrl}
|
||||||
|
onChange={setAvatarUrl}
|
||||||
|
size={100} // 头像尺寸
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编辑模式数据回显
|
||||||
|
```tsx
|
||||||
|
// 编辑模式下,传入已有的头像URL
|
||||||
|
const [avatarUrl, setAvatarUrl] = useState<string>('https://example.com/avatar.jpg');
|
||||||
|
|
||||||
|
<AvatarUpload
|
||||||
|
value={avatarUrl} // 会自动显示已上传的头像
|
||||||
|
onChange={setAvatarUrl}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据回显机制
|
||||||
|
|
||||||
|
### 工作原理
|
||||||
|
所有Upload组件都通过以下机制实现数据回显:
|
||||||
|
|
||||||
|
1. **useEffect监听value变化**:当传入的value发生变化时,自动更新内部状态
|
||||||
|
2. **文件列表同步**:将URL转换为文件列表格式,显示已上传的文件
|
||||||
|
3. **状态管理**:维护上传状态、文件列表等内部状态
|
||||||
|
4. **UI更新**:根据文件列表自动更新界面显示
|
||||||
|
|
||||||
|
### 使用场景
|
||||||
|
- **新增模式**:value为空或未定义,显示上传按钮
|
||||||
|
- **编辑模式**:value包含已上传文件的URL,自动显示文件
|
||||||
|
- **混合模式**:支持部分文件已上传,部分文件待上传
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
1. **URL格式**:确保传入的URL是有效的文件访问地址
|
||||||
|
2. **权限验证**:确保文件URL在编辑时仍然可访问
|
||||||
|
3. **状态同步**:value和onChange需要正确配合使用
|
||||||
|
4. **错误处理**:组件会自动处理无效URL的显示
|
||||||
|
|
||||||
|
## 技术实现
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
- 基于 antd Upload 组件
|
- 基于 antd Upload 组件
|
||||||
- 使用 antd-mobile 的 Toast 提示
|
- 使用 antd-mobile 的 Toast 提示
|
||||||
- 支持 FormData 上传
|
- 支持 FormData 上传
|
||||||
- 自动处理文件验证和错误提示
|
- 自动处理文件验证和错误提示
|
||||||
- 集成项目统一的API请求封装
|
- 集成项目统一的API请求封装
|
||||||
|
- **完整的数据回显支持**
|
||||||
|
|
||||||
|
### 文件结构
|
||||||
|
```
|
||||||
|
src/components/Upload/
|
||||||
|
├── MainImgUpload/ # 主图上传组件
|
||||||
|
├── ImageUpload/ # 多图上传组件
|
||||||
|
├── VideoUpload/ # 视频上传组件
|
||||||
|
├── FileUpload/ # 文件上传组件
|
||||||
|
├── AvatarUpload/ # 头像上传组件
|
||||||
|
└── README.md # 使用说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
### 统一的数据回显模式
|
||||||
|
所有组件都遵循相同的数据回显模式:
|
||||||
|
```tsx
|
||||||
|
// 1. 接收value属性
|
||||||
|
interface Props {
|
||||||
|
value?: string | string[];
|
||||||
|
onChange?: (url: string | string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 使用useEffect监听value变化
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
// 将URL转换为文件列表格式
|
||||||
|
const files = convertUrlToFileList(value);
|
||||||
|
setFileList(files);
|
||||||
|
} else {
|
||||||
|
setFileList([]);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
// 3. 在UI中显示文件列表
|
||||||
|
// 4. 支持编辑、删除、预览等操作
|
||||||
|
```
|
||||||
|
|||||||
@@ -108,18 +108,7 @@ const Login: React.FC = () => {
|
|||||||
|
|
||||||
// 根据设备数量判断跳转
|
// 根据设备数量判断跳转
|
||||||
if (deviceTotal > 0) {
|
if (deviceTotal > 0) {
|
||||||
// 有设备,跳转到首页或重定向URL
|
navigate("/");
|
||||||
const returnUrl = searchParams.get("returnUrl");
|
|
||||||
if (returnUrl) {
|
|
||||||
const decodedUrl = decodeURIComponent(returnUrl);
|
|
||||||
if (isLoginPage(decodedUrl)) {
|
|
||||||
navigate("/");
|
|
||||||
} else {
|
|
||||||
window.location.href = decodedUrl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
navigate("/");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 没有设备,跳转到引导页面
|
// 没有设备,跳转到引导页面
|
||||||
navigate("/guide");
|
navigate("/guide");
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState } from "react";
|
||||||
import { Input, Button, Tabs, Modal, Alert, message } from "antd";
|
import { Input, Button, Tabs, Modal, message } from "antd";
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
CloseOutlined,
|
CloseOutlined,
|
||||||
UploadOutlined,
|
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
PictureOutlined,
|
PictureOutlined,
|
||||||
@@ -14,7 +13,13 @@ import {
|
|||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import styles from "./messages.module.scss";
|
import styles from "./messages.module.scss";
|
||||||
import { uploadFile } from "@/api/common";
|
// 导入Upload组件
|
||||||
|
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
|
||||||
|
import VideoUpload from "@/components/Upload/VideoUpload";
|
||||||
|
import FileUpload from "@/components/Upload/FileUpload";
|
||||||
|
import MainImgUpload from "@/components/Upload/MainImgUpload";
|
||||||
|
// 导入GroupSelection组件
|
||||||
|
import GroupSelection from "@/components/GroupSelection";
|
||||||
|
|
||||||
interface MessageContent {
|
interface MessageContent {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -31,7 +36,7 @@ interface MessageContent {
|
|||||||
description?: string;
|
description?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
coverImage?: string;
|
coverImage?: string;
|
||||||
groupId?: string;
|
groupIds?: string[]; // 改为数组以支持GroupSelection组件
|
||||||
linkUrl?: string;
|
linkUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,13 +63,6 @@ const messageTypes = [
|
|||||||
{ id: "group", icon: TeamOutlined, label: "邀请入群" },
|
{ id: "group", icon: TeamOutlined, label: "邀请入群" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 模拟群组数据
|
|
||||||
const mockGroups = [
|
|
||||||
{ id: "1", name: "产品交流群1", memberCount: 156 },
|
|
||||||
{ id: "2", name: "产品交流群2", memberCount: 234 },
|
|
||||||
{ id: "3", name: "产品交流群3", memberCount: 89 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const MessageSettings: React.FC<MessageSettingsProps> = ({
|
const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||||||
formData,
|
formData,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -86,15 +84,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false);
|
const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false);
|
||||||
const [isGroupSelectOpen, setIsGroupSelectOpen] = useState(false);
|
|
||||||
const [selectedGroupId, setSelectedGroupId] = useState("");
|
|
||||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
||||||
const [uploadingIndex, setUploadingIndex] = useState<string | null>(null);
|
|
||||||
const [uploadingType, setUploadingType] = useState<
|
|
||||||
"miniprogram" | "link" | null
|
|
||||||
>(null);
|
|
||||||
const [uploadingDay, setUploadingDay] = useState<number | null>(null);
|
|
||||||
const [uploadingMsgIdx, setUploadingMsgIdx] = useState<number | null>(null);
|
|
||||||
|
|
||||||
// 添加新消息
|
// 添加新消息
|
||||||
const handleAddMessage = (dayIndex: number, type = "text") => {
|
const handleAddMessage = (dayIndex: number, type = "text") => {
|
||||||
@@ -176,61 +165,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
|||||||
message.success(`已添加第${newDay}天的消息计划`);
|
message.success(`已添加第${newDay}天的消息计划`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 选择群组
|
|
||||||
const handleSelectGroup = (groupId: string) => {
|
|
||||||
setSelectedGroupId(groupId);
|
|
||||||
setIsGroupSelectOpen(false);
|
|
||||||
message.success(
|
|
||||||
`已选择群组:${mockGroups.find(g => g.id === groupId)?.name}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 触发文件选择
|
|
||||||
const triggerUpload = (
|
|
||||||
dayIdx: number,
|
|
||||||
msgIdx: number,
|
|
||||||
type: "miniprogram" | "link",
|
|
||||||
) => {
|
|
||||||
setUploadingDay(dayIdx);
|
|
||||||
setUploadingMsgIdx(msgIdx);
|
|
||||||
setUploadingType(type);
|
|
||||||
setTimeout(() => {
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理文件上传
|
|
||||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (
|
|
||||||
!file ||
|
|
||||||
uploadingDay === null ||
|
|
||||||
uploadingMsgIdx === null ||
|
|
||||||
!uploadingType
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
setUploadingIndex(`${uploadingDay}-${uploadingMsgIdx}`);
|
|
||||||
try {
|
|
||||||
const url = await uploadFile(file);
|
|
||||||
// 更新对应消息的coverImage
|
|
||||||
setDayPlans(prev => {
|
|
||||||
const newPlans = [...prev];
|
|
||||||
const msg = newPlans[uploadingDay].messages[uploadingMsgIdx];
|
|
||||||
msg.coverImage = url;
|
|
||||||
return newPlans;
|
|
||||||
});
|
|
||||||
message.success("上传成功");
|
|
||||||
} catch (err) {
|
|
||||||
message.error("上传失败");
|
|
||||||
} finally {
|
|
||||||
setUploadingIndex(null);
|
|
||||||
setUploadingType(null);
|
|
||||||
setUploadingDay(null);
|
|
||||||
setUploadingMsgIdx(null);
|
|
||||||
if (fileInputRef.current) fileInputRef.current.value = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const items = dayPlans.map((plan, dayIndex) => ({
|
const items = dayPlans.map((plan, dayIndex) => ({
|
||||||
key: plan.day.toString(),
|
key: plan.day.toString(),
|
||||||
label: plan.day === 0 ? "即时消息" : `第${plan.day}天`,
|
label: plan.day === 0 ? "即时消息" : `第${plan.day}天`,
|
||||||
@@ -404,43 +338,16 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
|||||||
style={{ marginBottom: 8 }}
|
style={{ marginBottom: 8 }}
|
||||||
/>
|
/>
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
{message.coverImage ? (
|
<MainImgUpload
|
||||||
<div
|
value={message.coverImage || ""}
|
||||||
style={{
|
onChange={url =>
|
||||||
position: "relative",
|
handleUpdateMessage(dayIndex, messageIndex, {
|
||||||
display: "inline-block",
|
coverImage: url,
|
||||||
}}
|
})
|
||||||
>
|
}
|
||||||
<img
|
maxSize={5}
|
||||||
src={message.coverImage}
|
showPreview={true}
|
||||||
alt="封面"
|
/>
|
||||||
style={{ width: 120, borderRadius: 6 }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() =>
|
|
||||||
handleUpdateMessage(dayIndex, messageIndex, {
|
|
||||||
coverImage: undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
style={{ position: "absolute", top: 0, right: 0 }}
|
|
||||||
>
|
|
||||||
<CloseOutlined />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
loading={
|
|
||||||
uploadingIndex === `${dayIndex}-${messageIndex}`
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
triggerUpload(dayIndex, messageIndex, "miniprogram")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
上传封面
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -478,78 +385,81 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
|||||||
style={{ marginBottom: 8 }}
|
style={{ marginBottom: 8 }}
|
||||||
/>
|
/>
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
{message.coverImage ? (
|
<MainImgUpload
|
||||||
<div
|
value={message.coverImage || ""}
|
||||||
style={{
|
onChange={url =>
|
||||||
position: "relative",
|
handleUpdateMessage(dayIndex, messageIndex, {
|
||||||
display: "inline-block",
|
coverImage: url,
|
||||||
}}
|
})
|
||||||
>
|
}
|
||||||
<img
|
maxSize={5}
|
||||||
src={message.coverImage}
|
showPreview={true}
|
||||||
alt="封面"
|
/>
|
||||||
style={{ width: 120, borderRadius: 6 }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() =>
|
|
||||||
handleUpdateMessage(dayIndex, messageIndex, {
|
|
||||||
coverImage: undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
style={{ position: "absolute", top: 0, right: 0 }}
|
|
||||||
>
|
|
||||||
<CloseOutlined />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
loading={
|
|
||||||
uploadingIndex === `${dayIndex}-${messageIndex}`
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
triggerUpload(dayIndex, messageIndex, "link")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
上传封面
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* 群邀请消息 */}
|
{/* 群邀请消息 */}
|
||||||
{message.type === "group" && (
|
{message.type === "group" && (
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Button onClick={() => setIsGroupSelectOpen(true)}>
|
<GroupSelection
|
||||||
{selectedGroupId
|
selectedGroups={message.groupIds || []}
|
||||||
? mockGroups.find(g => g.id === selectedGroupId)?.name
|
onSelect={groupIds =>
|
||||||
: "选择邀请入的群"}
|
handleUpdateMessage(dayIndex, messageIndex, {
|
||||||
</Button>
|
groupIds: groupIds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="选择邀请入的群"
|
||||||
|
showSelectedList={true}
|
||||||
|
selectedListMaxHeight={200}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 图片/视频/文件消息 */}
|
{/* 图片消息 */}
|
||||||
{(message.type === "image" ||
|
{message.type === "image" && (
|
||||||
message.type === "video" ||
|
|
||||||
message.type === "file") && (
|
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Button
|
<ImageUpload
|
||||||
icon={<UploadOutlined />}
|
value={message.content ? [message.content] : []}
|
||||||
onClick={() =>
|
onChange={urls =>
|
||||||
handleFileUpload(
|
handleUpdateMessage(dayIndex, messageIndex, {
|
||||||
dayIndex,
|
content: urls[0] || "",
|
||||||
messageIndex,
|
})
|
||||||
message.type as any,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
count={1}
|
||||||
上传
|
accept="image/*"
|
||||||
{message.type === "image"
|
/>
|
||||||
? "图片"
|
</div>
|
||||||
: message.type === "video"
|
)}
|
||||||
? "视频"
|
{/* 视频消息 */}
|
||||||
: "文件"}
|
{message.type === "video" && (
|
||||||
</Button>
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<VideoUpload
|
||||||
|
value={message.content || ""}
|
||||||
|
onChange={url => {
|
||||||
|
const videoUrl = Array.isArray(url) ? url[0] || "" : url;
|
||||||
|
handleUpdateMessage(dayIndex, messageIndex, {
|
||||||
|
content: videoUrl,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
maxSize={50}
|
||||||
|
showPreview={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 文件消息 */}
|
||||||
|
{message.type === "file" && (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<FileUpload
|
||||||
|
value={message.content || ""}
|
||||||
|
onChange={url => {
|
||||||
|
const fileUrl = Array.isArray(url) ? url[0] || "" : url;
|
||||||
|
handleUpdateMessage(dayIndex, messageIndex, {
|
||||||
|
content: fileUrl,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
maxSize={10}
|
||||||
|
showPreview={true}
|
||||||
|
acceptTypes={["excel", "word", "ppt"]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -603,41 +513,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
|||||||
添加第 {dayPlans.length} 天计划
|
添加第 {dayPlans.length} 天计划
|
||||||
</Button>
|
</Button>
|
||||||
</Modal>
|
</Modal>
|
||||||
{/* 选择群聊弹窗 */}
|
|
||||||
<Modal
|
|
||||||
title="选择群聊"
|
|
||||||
open={isGroupSelectOpen}
|
|
||||||
onCancel={() => setIsGroupSelectOpen(false)}
|
|
||||||
onOk={() => {
|
|
||||||
handleSelectGroup(selectedGroupId);
|
|
||||||
setIsGroupSelectOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{mockGroups.map(group => (
|
|
||||||
<div
|
|
||||||
key={group.id}
|
|
||||||
className={
|
|
||||||
styles["messages-group-select-item"] +
|
|
||||||
(selectedGroupId === group.id ? " " + styles.selected : "")
|
|
||||||
}
|
|
||||||
onClick={() => handleSelectGroup(group.id)}
|
|
||||||
>
|
|
||||||
<div className="font-medium">{group.name}</div>
|
|
||||||
<div className="text-sm text-gray-500">
|
|
||||||
成员数:{group.memberCount}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user