FEAT => 本次更新项目为:完善上传组件使用说明,增加数据回显功能示例,优化代码结构,移除冗余代码
This commit is contained in:
@@ -1,22 +1,28 @@
|
||||
# Upload 组件使用说明
|
||||
|
||||
## MainImgUpload 主图封面上传组件
|
||||
## 组件概述
|
||||
|
||||
### 功能特点
|
||||
本项目提供了多个专门的上传组件,所有组件都支持编辑时的数据回显功能,确保在编辑模式下能够正确显示已上传的文件。
|
||||
|
||||
## 组件列表
|
||||
|
||||
### 1. MainImgUpload 主图封面上传组件
|
||||
|
||||
#### 功能特点
|
||||
- 只支持上传一张图片作为主图封面
|
||||
- 上传后右上角显示删除按钮
|
||||
- 支持图片预览功能
|
||||
- 响应式设计,适配移动端
|
||||
- 样式参考VideoUpload组件风格
|
||||
- 16:9宽高比,宽度高度自适应
|
||||
- **支持数据回显**:编辑时自动显示已上传的图片
|
||||
|
||||
### 使用方法
|
||||
#### 使用方法
|
||||
|
||||
```tsx
|
||||
import MainImgUpload from "@/components/Upload/MainImgUpload";
|
||||
import MainImgUpload from '@/components/Upload/MainImgUpload';
|
||||
|
||||
const MyComponent = () => {
|
||||
const [mainImage, setMainImage] = useState<string>("");
|
||||
const [mainImage, setMainImage] = useState<string>('');
|
||||
|
||||
return (
|
||||
<MainImgUpload
|
||||
@@ -30,39 +36,230 @@ const MyComponent = () => {
|
||||
};
|
||||
```
|
||||
|
||||
### Props 参数
|
||||
#### 编辑模式数据回显
|
||||
```tsx
|
||||
// 编辑模式下,传入已有的图片URL
|
||||
const [mainImage, setMainImage] = useState<string>('https://example.com/image.jpg');
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | --------------------- | ------ | ---------------- |
|
||||
| value | string | '' | 当前图片URL |
|
||||
| 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 # 通用上传样式
|
||||
<MainImgUpload
|
||||
value={mainImage} // 会自动显示已上传的图片
|
||||
onChange={setMainImage}
|
||||
/>
|
||||
```
|
||||
|
||||
### 技术实现
|
||||
### 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-mobile 的 Toast 提示
|
||||
- 支持 FormData 上传
|
||||
- 自动处理文件验证和错误提示
|
||||
- 集成项目统一的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) {
|
||||
// 有设备,跳转到首页或重定向URL
|
||||
const returnUrl = searchParams.get("returnUrl");
|
||||
if (returnUrl) {
|
||||
const decodedUrl = decodeURIComponent(returnUrl);
|
||||
if (isLoginPage(decodedUrl)) {
|
||||
navigate("/");
|
||||
} else {
|
||||
window.location.href = decodedUrl;
|
||||
}
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
navigate("/");
|
||||
} else {
|
||||
// 没有设备,跳转到引导页面
|
||||
navigate("/guide");
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useState, useRef } from "react";
|
||||
import { Input, Button, Tabs, Modal, Alert, message } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { Input, Button, Tabs, Modal, message } from "antd";
|
||||
import {
|
||||
PlusOutlined,
|
||||
CloseOutlined,
|
||||
UploadOutlined,
|
||||
ClockCircleOutlined,
|
||||
MessageOutlined,
|
||||
PictureOutlined,
|
||||
@@ -14,7 +13,13 @@ import {
|
||||
TeamOutlined,
|
||||
} from "@ant-design/icons";
|
||||
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 {
|
||||
id: string;
|
||||
@@ -31,7 +36,7 @@ interface MessageContent {
|
||||
description?: string;
|
||||
address?: string;
|
||||
coverImage?: string;
|
||||
groupId?: string;
|
||||
groupIds?: string[]; // 改为数组以支持GroupSelection组件
|
||||
linkUrl?: string;
|
||||
}
|
||||
|
||||
@@ -58,13 +63,6 @@ const messageTypes = [
|
||||
{ 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> = ({
|
||||
formData,
|
||||
onChange,
|
||||
@@ -86,15 +84,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||||
},
|
||||
]);
|
||||
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") => {
|
||||
@@ -176,61 +165,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||||
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) => ({
|
||||
key: plan.day.toString(),
|
||||
label: plan.day === 0 ? "即时消息" : `第${plan.day}天`,
|
||||
@@ -404,43 +338,16 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||||
style={{ marginBottom: 8 }}
|
||||
/>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
{message.coverImage ? (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={message.coverImage}
|
||||
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>
|
||||
)}
|
||||
<MainImgUpload
|
||||
value={message.coverImage || ""}
|
||||
onChange={url =>
|
||||
handleUpdateMessage(dayIndex, messageIndex, {
|
||||
coverImage: url,
|
||||
})
|
||||
}
|
||||
maxSize={5}
|
||||
showPreview={true}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -478,78 +385,81 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||||
style={{ marginBottom: 8 }}
|
||||
/>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
{message.coverImage ? (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={message.coverImage}
|
||||
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>
|
||||
)}
|
||||
<MainImgUpload
|
||||
value={message.coverImage || ""}
|
||||
onChange={url =>
|
||||
handleUpdateMessage(dayIndex, messageIndex, {
|
||||
coverImage: url,
|
||||
})
|
||||
}
|
||||
maxSize={5}
|
||||
showPreview={true}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* 群邀请消息 */}
|
||||
{message.type === "group" && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Button onClick={() => setIsGroupSelectOpen(true)}>
|
||||
{selectedGroupId
|
||||
? mockGroups.find(g => g.id === selectedGroupId)?.name
|
||||
: "选择邀请入的群"}
|
||||
</Button>
|
||||
<GroupSelection
|
||||
selectedGroups={message.groupIds || []}
|
||||
onSelect={groupIds =>
|
||||
handleUpdateMessage(dayIndex, messageIndex, {
|
||||
groupIds: groupIds,
|
||||
})
|
||||
}
|
||||
placeholder="选择邀请入的群"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={200}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* 图片/视频/文件消息 */}
|
||||
{(message.type === "image" ||
|
||||
message.type === "video" ||
|
||||
message.type === "file") && (
|
||||
{/* 图片消息 */}
|
||||
{message.type === "image" && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Button
|
||||
icon={<UploadOutlined />}
|
||||
onClick={() =>
|
||||
handleFileUpload(
|
||||
dayIndex,
|
||||
messageIndex,
|
||||
message.type as any,
|
||||
)
|
||||
<ImageUpload
|
||||
value={message.content ? [message.content] : []}
|
||||
onChange={urls =>
|
||||
handleUpdateMessage(dayIndex, messageIndex, {
|
||||
content: urls[0] || "",
|
||||
})
|
||||
}
|
||||
>
|
||||
上传
|
||||
{message.type === "image"
|
||||
? "图片"
|
||||
: message.type === "video"
|
||||
? "视频"
|
||||
: "文件"}
|
||||
</Button>
|
||||
count={1}
|
||||
accept="image/*"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* 视频消息 */}
|
||||
{message.type === "video" && (
|
||||
<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>
|
||||
@@ -603,41 +513,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||||
添加第 {dayPlans.length} 天计划
|
||||
</Button>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user