refactor: 移除测试相关代码及组件
fix(websocket): 禁用所有Toast提示以优化用户体验 删除开发环境下的测试路由、页面及组件,清理不再使用的代码。同时修改websocket模块,移除所有Toast提示以避免干扰用户操作。
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
# 上传组件测试页面
|
||||
|
||||
## 功能说明
|
||||
|
||||
这是一个完整的上传组件测试页面,用于演示和测试各种上传组件的功能。
|
||||
|
||||
## 包含的组件
|
||||
|
||||
### 1. 图片上传组件 (UploadComponent)
|
||||
|
||||
- 支持多图片上传
|
||||
- 可设置最大上传数量
|
||||
- 文件类型验证
|
||||
- 文件大小限制 (5MB)
|
||||
- 删除确认功能
|
||||
|
||||
### 2. 头像上传组件 (AvatarUpload)
|
||||
|
||||
- 圆形头像显示
|
||||
- 支持尺寸调节
|
||||
- 单张图片上传
|
||||
- 悬停效果
|
||||
|
||||
### 3. 视频上传组件 (VideoUpload)
|
||||
|
||||
- 支持视频文件上传
|
||||
- 文件大小限制 (50MB)
|
||||
- 上传进度显示
|
||||
|
||||
## 测试功能
|
||||
|
||||
### 控制面板
|
||||
|
||||
- 图片上传数量限制调节 (1-20张)
|
||||
- 头像尺寸调节 (60-200px)
|
||||
- 各组件禁用状态切换
|
||||
|
||||
### 表单集成测试
|
||||
|
||||
- 模拟真实表单场景
|
||||
- 包含文本输入、文本域、图片、头像、视频上传
|
||||
- 展示组件在表单中的使用方式
|
||||
|
||||
### 操作功能
|
||||
|
||||
- 提交表单数据 (输出到控制台)
|
||||
- 加载模拟数据
|
||||
- 重置所有数据
|
||||
- 页面刷新功能
|
||||
|
||||
### 数据展示
|
||||
|
||||
- 实时显示所有组件的当前状态
|
||||
- JSON格式展示,便于调试
|
||||
|
||||
## 使用Layout组件
|
||||
|
||||
页面使用了Layout组件进行布局,包含:
|
||||
|
||||
1. **自定义头部**
|
||||
- 返回按钮
|
||||
- 页面标题和图标
|
||||
- 搜索功能
|
||||
- 刷新功能
|
||||
|
||||
2. **主要内容区域**
|
||||
- 响应式设计
|
||||
- 滚动支持
|
||||
- 加载状态显示
|
||||
|
||||
3. **样式特点**
|
||||
- 响应式设计
|
||||
- 暗色主题支持
|
||||
- 现代化UI设计
|
||||
|
||||
## 访问方式
|
||||
|
||||
访问路径:`/mobile/test/upload`
|
||||
|
||||
## 开发说明
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
src/pages/mobile/test/
|
||||
├── upload.tsx # 主测试页面
|
||||
├── upload.module.scss # 样式文件
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
### 依赖组件
|
||||
|
||||
- Layout: 页面布局组件
|
||||
- PopupHeader: 头部搜索组件
|
||||
- UploadComponent: 图片上传组件
|
||||
- AvatarUpload: 头像上传组件
|
||||
- VideoUpload: 视频上传组件
|
||||
|
||||
### 技术栈
|
||||
|
||||
- React 18
|
||||
- TypeScript
|
||||
- Antd Mobile
|
||||
- SCSS Modules
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 上传功能需要配置正确的API地址
|
||||
2. 文件上传需要有效的认证token
|
||||
3. 测试数据使用外部图片服务,需要网络连接
|
||||
4. 建议在移动端环境下测试以获得最佳体验
|
||||
@@ -1,72 +0,0 @@
|
||||
import React from "react";
|
||||
import { Card, Button, Space, Typography, Tag } from "antd";
|
||||
import {
|
||||
MessageOutlined,
|
||||
SelectOutlined,
|
||||
UploadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isDevelopment } from "@/utils/env";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
const TestIndex: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Layout header={<NavCommon title="测试页面" />}>
|
||||
<div style={{ padding: "20px", maxWidth: "800px", margin: "0 auto" }}>
|
||||
<Title level={2}>
|
||||
测试页面
|
||||
{isDevelopment && (
|
||||
<Tag color="orange" style={{ marginLeft: 8, fontSize: "12px" }}>
|
||||
开发环境
|
||||
</Tag>
|
||||
)}
|
||||
</Title>
|
||||
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<Card title="组件测试" size="small">
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<MessageOutlined />}
|
||||
size="large"
|
||||
block
|
||||
onClick={() => navigate("/test/iframe")}
|
||||
>
|
||||
UniApp桥接测试
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
icon={<SelectOutlined />}
|
||||
size="large"
|
||||
block
|
||||
onClick={() => navigate("/test/select")}
|
||||
>
|
||||
选择组件测试
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
icon={<UploadOutlined />}
|
||||
size="large"
|
||||
block
|
||||
onClick={() => navigate("/test/upload")}
|
||||
>
|
||||
上传组件测试
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card title="说明" size="small">
|
||||
<Text>这里提供各种功能的测试页面,方便开发和调试。</Text>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestIndex;
|
||||
@@ -1,221 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Tabs, Tag } from "antd-mobile";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import FriendSelection from "@/components/FriendSelection";
|
||||
import GroupSelection from "@/components/GroupSelection";
|
||||
import ContentSelection from "@/components/ContentSelection";
|
||||
import AccountSelection from "@/components/AccountSelection";
|
||||
import PoolSelection from "@/components/PoolSelection";
|
||||
import { isDevelopment } from "@/utils/env";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { AccountItem } from "@/components/AccountSelection/data";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
const ComponentTest: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState("pools");
|
||||
|
||||
// 设备选择状态
|
||||
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
// 群组选择状态
|
||||
const [selectedGroups, setSelectedGroups] = useState<GroupSelectionItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
// 内容库选择状态
|
||||
const [selectedContent, setSelectedContent] = useState<ContentItem[]>([]);
|
||||
|
||||
const [selectedAccounts, setSelectedAccounts] = useState<AccountItem[]>([]);
|
||||
// 好友选择状态
|
||||
const [selectedFriendsOptions, setSelectedFriendsOptions] = useState<
|
||||
FriendSelectionItem[]
|
||||
>([]);
|
||||
|
||||
// 流量池选择状态
|
||||
const [selectedPools, setSelectedPools] = useState<PoolSelectionItem[]>([]);
|
||||
return (
|
||||
<Layout header={<NavCommon title="组件调试" />}>
|
||||
<div style={{ padding: 16 }}>
|
||||
{isDevelopment && (
|
||||
<div style={{ marginBottom: 16, textAlign: "center" }}>
|
||||
<Tag color="orange" style={{ fontSize: "12px" }}>
|
||||
开发环境 - 组件测试
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
||||
<Tabs.Tab title="好友选择" key="friends">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>FriendSelection 组件测试</h3>
|
||||
<FriendSelection
|
||||
selectedOptions={selectedFriendsOptions}
|
||||
onSelect={setSelectedFriendsOptions}
|
||||
placeholder="请选择微信好友"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab title="内容库选择" key="libraries">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>ContentSelection 组件测试</h3>
|
||||
<ContentSelection
|
||||
selectedOptions={selectedContent}
|
||||
onSelect={setSelectedContent}
|
||||
placeholder="请选择内容库"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选内容库:</strong> {selectedContent.length} 个
|
||||
<br />
|
||||
<strong>内容库ID:</strong>{" "}
|
||||
{selectedContent.map(c => c.id).join(", ") || "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="群组选择" key="groups2">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>GroupSelection 组件测试</h3>
|
||||
<GroupSelection
|
||||
selectedOptions={selectedGroups}
|
||||
onSelect={setSelectedGroups}
|
||||
placeholder="请选择微信群组"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选群组:</strong> {selectedGroups.length} 个
|
||||
<br />
|
||||
<strong>群组ID:</strong>{" "}
|
||||
{selectedGroups.map(g => g.id).join(", ") || "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="设备选择" key="devices">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>DeviceSelection 组件测试</h3>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={setSelectedDevices}
|
||||
placeholder="请选择设备"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选设备:</strong> {selectedDevices.length} 个
|
||||
<br />
|
||||
<strong>设备ID:</strong>
|
||||
{selectedDevices.map(d => d.id).join(", ") || "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="账号选择" key="accounts">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>AccountSelection 组件测试</h3>
|
||||
<AccountSelection
|
||||
selectedOptions={selectedAccounts}
|
||||
onSelect={setSelectedAccounts}
|
||||
placeholder="请选择账号"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
// 可根据实际API和props补充其它参数
|
||||
/>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<strong>已选账号:</strong>
|
||||
{selectedAccounts.length > 0
|
||||
? selectedAccounts.join(", ")
|
||||
: "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="群组选择" key="groups">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>GroupSelection 组件测试</h3>
|
||||
<GroupSelection
|
||||
selectedOptions={selectedGroups}
|
||||
onSelect={setSelectedGroups}
|
||||
placeholder="请选择微信群组"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选群组:</strong> {selectedGroups.length} 个
|
||||
<br />
|
||||
<strong>群组ID:</strong>{" "}
|
||||
{selectedGroups.map(g => g.id).join(", ") || "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab title="流量池选择" key="pools">
|
||||
<div style={{ padding: "16px 0" }}>
|
||||
<h3 style={{ marginBottom: 16 }}>PoolSelection 组件测试</h3>
|
||||
<PoolSelection
|
||||
selectedOptions={selectedPools}
|
||||
onSelect={setSelectedPools}
|
||||
placeholder="请选择流量池"
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: 16,
|
||||
padding: 12,
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
<strong>已选流量池:</strong> {selectedPools.length} 个
|
||||
<br />
|
||||
<strong>流量池ID:</strong>{" "}
|
||||
{selectedPools.map(p => p.id).join(", ") || "无"}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentTest;
|
||||
@@ -1,179 +0,0 @@
|
||||
import React from "react";
|
||||
import UpdateNotification from "@/components/UpdateNotification";
|
||||
|
||||
const UpdateNotificationTest: React.FC = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
minHeight: "100vh",
|
||||
backgroundColor: "#f5f5f5",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{/* 更新通知组件 */}
|
||||
<UpdateNotification forceShow={true} />
|
||||
|
||||
{/* 页面内容 */}
|
||||
<div
|
||||
style={{
|
||||
paddingTop: "calc(80px + env(safe-area-inset-top))", // 为通知栏留出空间
|
||||
padding: "20px",
|
||||
maxWidth: "800px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
borderRadius: "12px",
|
||||
padding: "24px",
|
||||
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
style={{
|
||||
fontSize: "20px",
|
||||
fontWeight: "600",
|
||||
marginBottom: "16px",
|
||||
color: "#333",
|
||||
}}
|
||||
>
|
||||
UpdateNotification 组件预览
|
||||
</h2>
|
||||
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "8px",
|
||||
color: "#666",
|
||||
}}
|
||||
>
|
||||
设计特点:
|
||||
</h3>
|
||||
<ul
|
||||
style={{
|
||||
paddingLeft: "20px",
|
||||
lineHeight: "1.6",
|
||||
color: "#666",
|
||||
}}
|
||||
>
|
||||
<li>酷黑风格横向条设计</li>
|
||||
<li>顶部固定定位,支持安全区域</li>
|
||||
<li>渐变背景和半透明边框</li>
|
||||
<li>蓝色主题按钮</li>
|
||||
<li>从上方滑入动画效果</li>
|
||||
<li>红色更新图标脉冲动画</li>
|
||||
<li>移动端优化的字体和按钮尺寸</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "8px",
|
||||
color: "#666",
|
||||
}}
|
||||
>
|
||||
功能说明:
|
||||
</h3>
|
||||
<ul
|
||||
style={{
|
||||
paddingLeft: "20px",
|
||||
lineHeight: "1.6",
|
||||
color: "#666",
|
||||
}}
|
||||
>
|
||||
<li>点击“立即更新”会刷新页面</li>
|
||||
<li>点击“稍后”会隐藏通知,10分钟后重新检查</li>
|
||||
<li>通知固定在顶部,不会影响页面布局</li>
|
||||
<li>支持安全区域适配,确保在刘海屏设备上正常显示</li>
|
||||
<li>响应式设计,适配不同屏幕尺寸</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: "16px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #e9ecef",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: "14px",
|
||||
color: "#666",
|
||||
lineHeight: "1.5",
|
||||
}}
|
||||
>
|
||||
<strong>注意:</strong>
|
||||
此页面强制显示更新通知组件用于预览效果。在实际使用中,组件会根据更新检测结果自动显示或隐藏。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 模拟页面内容 */}
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
borderRadius: "12px",
|
||||
padding: "24px",
|
||||
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)",
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "16px",
|
||||
color: "#333",
|
||||
}}
|
||||
>
|
||||
页面内容区域
|
||||
</h3>
|
||||
<p
|
||||
style={{
|
||||
lineHeight: "1.6",
|
||||
color: "#666",
|
||||
marginBottom: "16px",
|
||||
}}
|
||||
>
|
||||
这里是页面的主要内容区域。更新通知栏固定在顶部,不会影响页面内容的正常显示和交互。
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
lineHeight: "1.6",
|
||||
color: "#666",
|
||||
marginBottom: "16px",
|
||||
}}
|
||||
>
|
||||
页面内容会自动为顶部通知栏预留空间,确保内容不被遮挡。在有安全区域的设备上,
|
||||
通知栏会自动适配安全区域高度。
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
height: "200px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderRadius: "8px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "#999",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
模拟内容区域
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateNotificationTest;
|
||||
@@ -1,354 +0,0 @@
|
||||
.container {
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
|
||||
.customHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.headerLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.backButton {
|
||||
padding: 4px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
font-size: 18px;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
|
||||
.headerIcon {
|
||||
font-size: 20px;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controlPanel {
|
||||
margin-bottom: 16px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.controlItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.testSection {
|
||||
margin-bottom: 16px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.urlList {
|
||||
.urlItem {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
span:first-child {
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
min-width: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.url {
|
||||
color: #1890ff;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
flex: 1;
|
||||
min-width: 0; // 确保flex项目可以收缩
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emptyText {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
.formItem {
|
||||
margin-bottom: 16px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionPanel {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dataPanel {
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dataContent {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 12px;
|
||||
|
||||
.customHeader {
|
||||
padding: 8px 12px;
|
||||
|
||||
.headerLeft {
|
||||
.headerTitle {
|
||||
font-size: 14px;
|
||||
|
||||
.headerIcon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.testSection {
|
||||
.result {
|
||||
.urlList {
|
||||
.urlItem {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dataPanel {
|
||||
.dataContent {
|
||||
pre {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色主题支持
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.container {
|
||||
background: #1f1f1f;
|
||||
|
||||
.customHeader {
|
||||
background: #2a2a2a;
|
||||
border-bottom-color: #434343;
|
||||
|
||||
.headerLeft {
|
||||
.backButton {
|
||||
color: #ccc;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
color: #fff;
|
||||
|
||||
.headerIcon {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.testSection {
|
||||
h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.result {
|
||||
background: #2a2a2a;
|
||||
|
||||
h4 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.urlList {
|
||||
.urlItem {
|
||||
span:first-child {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.url {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 单个URL项(头像、视频)的样式
|
||||
.urlItem {
|
||||
.url {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyText {
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
.formItem {
|
||||
label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
background: #2a2a2a;
|
||||
border-color: #434343;
|
||||
color: #fff;
|
||||
|
||||
&:focus {
|
||||
border-color: #40a9ff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 169, 255, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dataPanel {
|
||||
h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dataContent {
|
||||
background: #2a2a2a;
|
||||
|
||||
pre {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Card, Space, Divider, Toast, Switch } from "antd-mobile";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
|
||||
import AvatarUpload from "@/components/Upload/AvatarUpload";
|
||||
import VideoUpload from "@/components/Upload/VideoUpload";
|
||||
import FileUpload from "@/components/Upload/FileUpload";
|
||||
import MainImgUpload from "@/components/Upload/MainImgUpload";
|
||||
import styles from "./upload.module.scss";
|
||||
|
||||
// 错误边界组件
|
||||
class ErrorBoundary extends React.Component<
|
||||
{ children: React.ReactNode },
|
||||
{ hasError: boolean; error: Error | null }
|
||||
> {
|
||||
constructor(props: { children: React.ReactNode }) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error("Error caught by boundary:", error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div style={{ padding: 20, textAlign: "center" }}>
|
||||
<h3>组件出现错误</h3>
|
||||
<p>错误信息: {this.state.error?.message}</p>
|
||||
<Button
|
||||
onClick={() => this.setState({ hasError: false, error: null })}
|
||||
>
|
||||
重试
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const UploadTestPage: React.FC = () => {
|
||||
// 搜索状态
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 图片上传状态
|
||||
const [imageUrls, setImageUrls] = useState<string[]>([]);
|
||||
const [imageCount, setImageCount] = useState(9);
|
||||
const [imageDisabled, setImageDisabled] = useState(false);
|
||||
|
||||
// 头像上传状态
|
||||
const [avatarUrl, setAvatarUrl] = useState<string>("");
|
||||
const [avatarSize, setAvatarSize] = useState(100);
|
||||
const [avatarDisabled, setAvatarDisabled] = useState(false);
|
||||
|
||||
// 视频上传状态
|
||||
const [videoUrl, setVideoUrl] = useState<string>("");
|
||||
const [videoUrls, setVideoUrls] = useState<string[]>([]);
|
||||
const [videoDisabled, setVideoDisabled] = useState(false);
|
||||
const [videoCount, setVideoCount] = useState(1);
|
||||
|
||||
// 文件上传状态
|
||||
const [fileUrl, setFileUrl] = useState<string>("");
|
||||
const [fileUrls, setFileUrls] = useState<string[]>([]);
|
||||
const [fileDisabled, setFileDisabled] = useState(false);
|
||||
const [fileCount, setFileCount] = useState(1);
|
||||
const [fileTypes, setFileTypes] = useState<string[]>([
|
||||
"excel",
|
||||
"word",
|
||||
"ppt",
|
||||
]);
|
||||
|
||||
// 主图上传状态
|
||||
const [mainImgUrl, setMainImgUrl] = useState<string>("");
|
||||
const [mainImgDisabled, setMainImgDisabled] = useState(false);
|
||||
const [mainImgMaxSize, setMainImgMaxSize] = useState(5);
|
||||
const [mainImgShowPreview, setMainImgShowPreview] = useState(true);
|
||||
|
||||
return (
|
||||
<Layout header={<NavCommon title="上传组件功能测试" />} loading={loading}>
|
||||
<div className={styles.container}>
|
||||
{/* 图片上传测试 */}
|
||||
<ErrorBoundary>
|
||||
<Card className={styles.testSection}>
|
||||
<h3>图片上传组件测试</h3>
|
||||
<p>支持多图片上传,可设置数量限制</p>
|
||||
|
||||
<ImageUpload
|
||||
value={imageUrls}
|
||||
onChange={setImageUrls}
|
||||
count={imageCount}
|
||||
accept="image/*"
|
||||
disabled={imageDisabled}
|
||||
/>
|
||||
|
||||
<div className={styles.result}>
|
||||
<h4>当前图片URLs:</h4>
|
||||
<div className={styles.urlList}>
|
||||
{imageUrls.length > 0 ? (
|
||||
imageUrls.map((url, index) => (
|
||||
<div key={index} className={styles.urlItem}>
|
||||
<span>{index + 1}.</span>
|
||||
<span className={styles.url}>
|
||||
{typeof url === "string" ? url : "无效URL"}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无图片</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* 头像上传测试 */}
|
||||
<ErrorBoundary>
|
||||
<Card className={styles.testSection}>
|
||||
<h3>头像上传组件测试</h3>
|
||||
<p>支持单张图片上传,圆形显示</p>
|
||||
|
||||
<AvatarUpload
|
||||
value={avatarUrl}
|
||||
onChange={setAvatarUrl}
|
||||
disabled={avatarDisabled}
|
||||
size={avatarSize}
|
||||
/>
|
||||
|
||||
<div className={styles.result}>
|
||||
<h4>当前头像URL:</h4>
|
||||
<div className={styles.urlList}>
|
||||
<div className={styles.urlItem}>
|
||||
{avatarUrl ? (
|
||||
<div className={styles.url}>
|
||||
{typeof avatarUrl === "string" ? avatarUrl : "无效URL"}
|
||||
</div>
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无头像</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* 视频上传测试 */}
|
||||
<ErrorBoundary>
|
||||
<Card className={styles.testSection}>
|
||||
<h3>视频上传组件测试</h3>
|
||||
<p>支持视频文件上传,最大50MB,支持预览功能,可设置上传数量</p>
|
||||
|
||||
{/* 视频上传控制面板 */}
|
||||
<div className={styles.controlPanel}>
|
||||
<div className={styles.controlItem}>
|
||||
<span>视频上传数量:</span>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setVideoCount(Math.max(1, videoCount - 1))}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<span>{videoCount}</span>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setVideoCount(Math.min(10, videoCount + 1))}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VideoUpload
|
||||
value={videoCount === 1 ? videoUrl : videoUrls}
|
||||
onChange={url => {
|
||||
if (videoCount === 1) {
|
||||
setVideoUrl(url as string);
|
||||
} else {
|
||||
setVideoUrls(url as string[]);
|
||||
}
|
||||
}}
|
||||
disabled={videoDisabled}
|
||||
maxSize={50}
|
||||
showPreview={true}
|
||||
maxCount={videoCount}
|
||||
/>
|
||||
|
||||
<div className={styles.result}>
|
||||
<h4>当前视频URL:</h4>
|
||||
<div className={styles.urlList}>
|
||||
{videoCount === 1 ? (
|
||||
<div className={styles.urlItem}>
|
||||
{videoUrl ? (
|
||||
<div className={styles.url}>
|
||||
{typeof videoUrl === "string" ? videoUrl : "无效URL"}
|
||||
</div>
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无视频</span>
|
||||
)}
|
||||
</div>
|
||||
) : videoUrls.length > 0 ? (
|
||||
videoUrls.map((url, index) => (
|
||||
<div key={index} className={styles.urlItem}>
|
||||
<span>{index + 1}.</span>
|
||||
<div className={styles.url}>
|
||||
{typeof url === "string" ? url : "无效URL"}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无视频</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* 文件上传测试 */}
|
||||
<ErrorBoundary>
|
||||
<Card className={styles.testSection}>
|
||||
<h3>文件上传组件测试</h3>
|
||||
<p>支持Excel、Word、PPT格式文件上传,可设置上传数量和文件类型</p>
|
||||
|
||||
{/* 文件上传控制面板 */}
|
||||
<div className={styles.controlPanel}>
|
||||
<div className={styles.controlItem}>
|
||||
<span>文件上传数量:</span>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setFileCount(Math.max(1, fileCount - 1))}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<span>{fileCount}</span>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => setFileCount(Math.min(10, fileCount + 1))}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div className={styles.controlItem}>
|
||||
<span>文件类型:</span>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
color={fileTypes.includes("excel") ? "primary" : "default"}
|
||||
onClick={() => {
|
||||
if (fileTypes.includes("excel")) {
|
||||
setFileTypes(fileTypes.filter(t => t !== "excel"));
|
||||
} else {
|
||||
setFileTypes([...fileTypes, "excel"]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Excel
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
color={fileTypes.includes("word") ? "primary" : "default"}
|
||||
onClick={() => {
|
||||
if (fileTypes.includes("word")) {
|
||||
setFileTypes(fileTypes.filter(t => t !== "word"));
|
||||
} else {
|
||||
setFileTypes([...fileTypes, "word"]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Word
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
color={fileTypes.includes("ppt") ? "primary" : "default"}
|
||||
onClick={() => {
|
||||
if (fileTypes.includes("ppt")) {
|
||||
setFileTypes(fileTypes.filter(t => t !== "ppt"));
|
||||
} else {
|
||||
setFileTypes([...fileTypes, "ppt"]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
PPT
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FileUpload
|
||||
value={fileCount === 1 ? fileUrl : fileUrls}
|
||||
onChange={url => {
|
||||
if (fileCount === 1) {
|
||||
setFileUrl(url as string);
|
||||
} else {
|
||||
setFileUrls(url as string[]);
|
||||
}
|
||||
}}
|
||||
disabled={fileDisabled}
|
||||
maxSize={10}
|
||||
showPreview={true}
|
||||
maxCount={fileCount}
|
||||
acceptTypes={fileTypes}
|
||||
/>
|
||||
|
||||
<div className={styles.result}>
|
||||
<h4>当前文件URL:</h4>
|
||||
<div className={styles.urlList}>
|
||||
{fileCount === 1 ? (
|
||||
<div className={styles.urlItem}>
|
||||
{fileUrl ? (
|
||||
<div className={styles.url}>
|
||||
{typeof fileUrl === "string" ? fileUrl : "无效URL"}
|
||||
</div>
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无文件</span>
|
||||
)}
|
||||
</div>
|
||||
) : fileUrls.length > 0 ? (
|
||||
fileUrls.map((url, index) => (
|
||||
<div key={index} className={styles.urlItem}>
|
||||
<span>{index + 1}.</span>
|
||||
<div className={styles.url}>
|
||||
{typeof url === "string" ? url : "无效URL"}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无文件</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* 主图上传测试 */}
|
||||
<ErrorBoundary>
|
||||
<Card className={styles.testSection}>
|
||||
<h3>主图封面上传组件测试</h3>
|
||||
<p>支持单张图片上传作为主图封面,上传后右上角显示删除按钮</p>
|
||||
|
||||
{/* 主图上传控制面板 */}
|
||||
<div className={styles.controlPanel}>
|
||||
<div className={styles.controlItem}>
|
||||
<span>最大文件大小:</span>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() =>
|
||||
setMainImgMaxSize(Math.max(1, mainImgMaxSize - 1))
|
||||
}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<span>{mainImgMaxSize}MB</span>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() =>
|
||||
setMainImgMaxSize(Math.min(20, mainImgMaxSize + 1))
|
||||
}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div className={styles.controlItem}>
|
||||
<span>显示预览按钮:</span>
|
||||
<Switch
|
||||
checked={mainImgShowPreview}
|
||||
onChange={setMainImgShowPreview}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.controlItem}>
|
||||
<span>禁用状态:</span>
|
||||
<Switch
|
||||
checked={mainImgDisabled}
|
||||
onChange={setMainImgDisabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MainImgUpload
|
||||
value={mainImgUrl}
|
||||
onChange={setMainImgUrl}
|
||||
disabled={mainImgDisabled}
|
||||
maxSize={mainImgMaxSize}
|
||||
showPreview={mainImgShowPreview}
|
||||
/>
|
||||
|
||||
<div className={styles.result}>
|
||||
<h4>当前主图URL:</h4>
|
||||
<div className={styles.urlList}>
|
||||
<div className={styles.urlItem}>
|
||||
{mainImgUrl ? (
|
||||
<div className={styles.url}>
|
||||
{typeof mainImgUrl === "string" ? mainImgUrl : "无效URL"}
|
||||
</div>
|
||||
) : (
|
||||
<span className={styles.emptyText}>暂无主图</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadTestPage;
|
||||
Reference in New Issue
Block a user