FEAT => 本次更新项目为:移除上传组件相关文件,整合上传功能至主图上传组件,优化代码结构及样式
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
|||||||
FilePptOutlined,
|
FilePptOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import type { UploadProps, UploadFile } from "antd/es/upload/interface";
|
import type { UploadProps, UploadFile } from "antd/es/upload/interface";
|
||||||
import style from "./FileUpload.module.scss";
|
import style from "./index.module.scss";
|
||||||
|
|
||||||
interface FileUploadProps {
|
interface FileUploadProps {
|
||||||
value?: string | string[]; // 支持单个字符串或字符串数组
|
value?: string | string[]; // 支持单个字符串或字符串数组
|
||||||
484
nkebao/src/components/Upload/ImageUpload/index.module.scss
Normal file
484
nkebao/src/components/Upload/ImageUpload/index.module.scss
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
.uploadContainer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// 自定义上传组件样式
|
||||||
|
:global {
|
||||||
|
.adm-image-uploader {
|
||||||
|
.adm-image-uploader-upload-button {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1677ff;
|
||||||
|
background: #f0f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-upload-button-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-item {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.adm-image-uploader-item-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-item-delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-item-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用状态
|
||||||
|
.uploadContainer.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
.uploadContainer.error {
|
||||||
|
:global {
|
||||||
|
.adm-image-uploader-upload-button {
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
background: #fff2f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.uploadContainer {
|
||||||
|
:global {
|
||||||
|
.adm-image-uploader {
|
||||||
|
.adm-image-uploader-upload-button,
|
||||||
|
.adm-image-uploader-item {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-upload-button-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头像上传组件样式
|
||||||
|
.avatarUploadContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.avatarWrapper {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 142, 238, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarImage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarPlaceholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarUploadOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadLoading {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarDeleteBtn {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -8px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ff7875;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .avatarUploadOverlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarTip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频上传组件样式
|
||||||
|
.videoUploadContainer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.videoUploadButton {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
border: 2px dashed #d9d9d9;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.uploadingIcon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingText {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadProgress {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.uploadIcon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #1890ff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .uploadIcon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItem {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItemContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.videoIcon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoInfo {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.videoName {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSize {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoActions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewBtn {
|
||||||
|
color: #1890ff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteBtn {
|
||||||
|
color: #ff4d4f;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
background: #fff2f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemProgress {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoPreview {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
video {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暗色主题支持
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.videoUploadContainer {
|
||||||
|
.videoUploadButton {
|
||||||
|
background: linear-gradient(135deg, #2a2a2a 0%, #1f1f1f 100%);
|
||||||
|
border-color: #434343;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, #1a365d 0%, #2d3748 100%);
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
.uploadingText {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItem {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-color: #434343;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItemContent {
|
||||||
|
.videoInfo {
|
||||||
|
.videoName {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSize {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
&:hover {
|
||||||
|
background: #434343;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
558
nkebao/src/components/Upload/MainImgUpload/index.module.scss
Normal file
558
nkebao/src/components/Upload/MainImgUpload/index.module.scss
Normal file
@@ -0,0 +1,558 @@
|
|||||||
|
.mainImgUploadContainer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
// 覆盖 antd Upload 组件的默认样式
|
||||||
|
:global {
|
||||||
|
.ant-upload {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-list-text {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-list-text .ant-upload-list-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgUploadButton {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
min-height: 180px;
|
||||||
|
min-width: 320px;
|
||||||
|
border: 2px dashed #d9d9d9;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.uploadingIcon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingText {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.uploadIcon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #1890ff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .uploadIcon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgItem {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgItemContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.mainImgIcon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgInfo {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.mainImgName {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgSize {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgActions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewBtn {
|
||||||
|
color: #1890ff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteBtn {
|
||||||
|
color: #ff4d4f;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
background: #fff2f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgPreview {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
min-height: 180px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
|
||||||
|
.mainImgImage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.mainImgActions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: white;
|
||||||
|
color: #1890ff;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteBtn:hover {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .mainImgOverlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用状态
|
||||||
|
.mainImgUploadContainer.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
.mainImgUploadContainer.error {
|
||||||
|
.mainImgUploadButton {
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
background: #fff2f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.mainImgUploadContainer {
|
||||||
|
.mainImgUploadButton {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
min-height: 135px;
|
||||||
|
min-width: 240px;
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.uploadIcon {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.uploadingIcon {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingText {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgItem {
|
||||||
|
.mainImgItemContent {
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
.mainImgIcon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgInfo {
|
||||||
|
.mainImgName {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgSize {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
padding: 3px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgPreview {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
min-height: 135px;
|
||||||
|
|
||||||
|
.mainImgOverlay {
|
||||||
|
.mainImgActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.mainImgUploadContainer {
|
||||||
|
.mainImgUploadButton {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
min-height: 90px;
|
||||||
|
min-width: 160px;
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
padding: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.uploadIcon {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
padding: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.uploadingIcon {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingText {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgItem {
|
||||||
|
.mainImgItemContent {
|
||||||
|
padding: 6px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.mainImgIcon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgInfo {
|
||||||
|
.mainImgName {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgSize {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgPreview {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
min-height: 90px;
|
||||||
|
|
||||||
|
.mainImgOverlay {
|
||||||
|
.mainImgActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暗色主题支持
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.mainImgUploadContainer {
|
||||||
|
.mainImgUploadButton {
|
||||||
|
background: linear-gradient(135deg, #2a2a2a 0%, #1f1f1f 100%);
|
||||||
|
border-color: #434343;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, #1a365d 0%, #2d3748 100%);
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
.uploadingText {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgItem {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-color: #434343;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgItemContent {
|
||||||
|
.mainImgInfo {
|
||||||
|
.mainImgName {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgSize {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
&:hover {
|
||||||
|
background: #434343;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainImgPreview {
|
||||||
|
background: #1f1f1f;
|
||||||
|
|
||||||
|
.mainImgOverlay {
|
||||||
|
.mainImgActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #ccc;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
313
nkebao/src/components/Upload/MainImgUpload/index.tsx
Normal file
313
nkebao/src/components/Upload/MainImgUpload/index.tsx
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Upload, message, Button } from "antd";
|
||||||
|
import {
|
||||||
|
LoadingOutlined,
|
||||||
|
PictureOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
CloudUploadOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import type { UploadProps, UploadFile } from "antd/es/upload/interface";
|
||||||
|
import style from "./index.module.scss";
|
||||||
|
|
||||||
|
interface MainImgUploadProps {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (url: string) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
maxSize?: number; // 最大文件大小(MB)
|
||||||
|
showPreview?: boolean; // 是否显示预览
|
||||||
|
}
|
||||||
|
|
||||||
|
const MainImgUpload: React.FC<MainImgUploadProps> = ({
|
||||||
|
value = "",
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
className,
|
||||||
|
maxSize = 5,
|
||||||
|
showPreview = true,
|
||||||
|
}) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value) {
|
||||||
|
const files: UploadFile[] = [
|
||||||
|
{
|
||||||
|
uid: "main-img",
|
||||||
|
name: "main-image",
|
||||||
|
status: "done",
|
||||||
|
url: value,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
setFileList(files);
|
||||||
|
} else {
|
||||||
|
setFileList([]);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
// 文件验证
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
const isImage = file.type.startsWith("image/");
|
||||||
|
if (!isImage) {
|
||||||
|
message.error("只能上传图片文件!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLtMaxSize = file.size / 1024 / 1024 < maxSize;
|
||||||
|
if (!isLtMaxSize) {
|
||||||
|
message.error(`图片大小不能超过${maxSize}MB!`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理文件变化
|
||||||
|
const handleChange: UploadProps["onChange"] = info => {
|
||||||
|
// 更新 fileList,确保所有 URL 都是字符串
|
||||||
|
const updatedFileList = info.fileList.map(file => {
|
||||||
|
let url = "";
|
||||||
|
|
||||||
|
if (file.url) {
|
||||||
|
url = file.url;
|
||||||
|
} else if (file.response) {
|
||||||
|
// 处理响应对象
|
||||||
|
if (typeof file.response === "string") {
|
||||||
|
url = file.response;
|
||||||
|
} else if (file.response.data) {
|
||||||
|
url =
|
||||||
|
typeof file.response.data === "string"
|
||||||
|
? file.response.data
|
||||||
|
: file.response.data.url || "";
|
||||||
|
} else if (file.response.url) {
|
||||||
|
url = file.response.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
url: url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setFileList(updatedFileList);
|
||||||
|
|
||||||
|
// 处理上传状态
|
||||||
|
if (info.file.status === "uploading") {
|
||||||
|
setLoading(true);
|
||||||
|
} else if (info.file.status === "done") {
|
||||||
|
setLoading(false);
|
||||||
|
message.success("图片上传成功!");
|
||||||
|
|
||||||
|
// 从响应中获取上传后的URL
|
||||||
|
let uploadedUrl = "";
|
||||||
|
|
||||||
|
if (info.file.response) {
|
||||||
|
if (typeof info.file.response === "string") {
|
||||||
|
uploadedUrl = info.file.response;
|
||||||
|
} else if (info.file.response.data) {
|
||||||
|
uploadedUrl =
|
||||||
|
typeof info.file.response.data === "string"
|
||||||
|
? info.file.response.data
|
||||||
|
: info.file.response.data.url || "";
|
||||||
|
} else if (info.file.response.url) {
|
||||||
|
uploadedUrl = info.file.response.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadedUrl) {
|
||||||
|
onChange?.(uploadedUrl);
|
||||||
|
}
|
||||||
|
} else if (info.file.status === "error") {
|
||||||
|
setLoading(false);
|
||||||
|
message.error("上传失败,请重试");
|
||||||
|
} else if (info.file.status === "removed") {
|
||||||
|
onChange?.("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
|
const handleRemove = () => {
|
||||||
|
setFileList([]);
|
||||||
|
onChange?.("");
|
||||||
|
message.success("图片已删除");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 预览图片
|
||||||
|
const handlePreview = (url: string) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = url;
|
||||||
|
const newWindow = window.open();
|
||||||
|
if (newWindow) {
|
||||||
|
newWindow.document.write(img.outerHTML);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
const formatFileSize = (bytes: number) => {
|
||||||
|
if (bytes === 0) return "0 B";
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ["B", "KB", "MB", "GB"];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义上传按钮
|
||||||
|
const uploadButton = (
|
||||||
|
<div className={style.mainImgUploadButton}>
|
||||||
|
{loading ? (
|
||||||
|
<div className={style.uploadingContainer}>
|
||||||
|
<div className={style.uploadingIcon}>
|
||||||
|
<LoadingOutlined spin />
|
||||||
|
</div>
|
||||||
|
<div className={style.uploadingText}>上传中...</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={style.uploadContent}>
|
||||||
|
<div className={style.uploadIcon}>
|
||||||
|
<CloudUploadOutlined />
|
||||||
|
</div>
|
||||||
|
<div className={style.uploadText}>
|
||||||
|
<div className={style.uploadTitle}>上传主图封面</div>
|
||||||
|
<div className={style.uploadSubtitle}>
|
||||||
|
支持 JPG、PNG、GIF 等格式,最大 {maxSize}MB
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 自定义文件列表项
|
||||||
|
const customItemRender = (
|
||||||
|
originNode: React.ReactElement,
|
||||||
|
file: UploadFile,
|
||||||
|
) => {
|
||||||
|
if (file.status === "uploading") {
|
||||||
|
return (
|
||||||
|
<div className={style.mainImgItem}>
|
||||||
|
<div className={style.mainImgItemContent}>
|
||||||
|
<div className={style.mainImgIcon}>
|
||||||
|
<PictureOutlined />
|
||||||
|
</div>
|
||||||
|
<div className={style.mainImgInfo}>
|
||||||
|
<div className={style.mainImgName}>{file.name}</div>
|
||||||
|
<div className={style.mainImgSize}>
|
||||||
|
{file.size ? formatFileSize(file.size) : "计算中..."}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style.mainImgActions}>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleRemove()}
|
||||||
|
className={style.deleteBtn}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.status === "done") {
|
||||||
|
return (
|
||||||
|
<div className={style.mainImgItem}>
|
||||||
|
<div className={style.mainImgItemContent}>
|
||||||
|
<div className={style.mainImgIcon}>
|
||||||
|
<PictureOutlined />
|
||||||
|
</div>
|
||||||
|
<div className={style.mainImgInfo}>
|
||||||
|
<div className={style.mainImgName}>{file.name}</div>
|
||||||
|
<div className={style.mainImgSize}>
|
||||||
|
{file.size ? formatFileSize(file.size) : "未知大小"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style.mainImgActions}>
|
||||||
|
{showPreview && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => handlePreview(file.url || "")}
|
||||||
|
className={style.previewBtn}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleRemove()}
|
||||||
|
className={style.deleteBtn}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style.mainImgPreview}>
|
||||||
|
<img
|
||||||
|
src={file.url}
|
||||||
|
alt={file.name}
|
||||||
|
className={style.mainImgImage}
|
||||||
|
/>
|
||||||
|
<div className={style.mainImgOverlay}>
|
||||||
|
<div className={style.mainImgActions}>
|
||||||
|
{showPreview && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => handlePreview(file.url || "")}
|
||||||
|
className={style.previewBtn}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleRemove()}
|
||||||
|
className={style.deleteBtn}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return originNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = import.meta.env.VITE_API_BASE_URL + "/v1/attachment/upload";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${style.mainImgUploadContainer} ${className || ""}`}>
|
||||||
|
<Upload
|
||||||
|
name="file"
|
||||||
|
headers={{
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
}}
|
||||||
|
action={action}
|
||||||
|
multiple={false}
|
||||||
|
fileList={fileList}
|
||||||
|
accept="image/*"
|
||||||
|
listType="text"
|
||||||
|
showUploadList={{
|
||||||
|
showPreviewIcon: false,
|
||||||
|
showRemoveIcon: false,
|
||||||
|
showDownloadIcon: false,
|
||||||
|
}}
|
||||||
|
disabled={disabled || loading}
|
||||||
|
beforeUpload={beforeUpload}
|
||||||
|
onChange={handleChange}
|
||||||
|
onRemove={handleRemove}
|
||||||
|
maxCount={1}
|
||||||
|
itemRender={customItemRender}
|
||||||
|
>
|
||||||
|
{fileList.length >= 1 ? null : uploadButton}
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainImgUpload;
|
||||||
@@ -1,183 +1,68 @@
|
|||||||
# Upload 上传组件
|
# Upload 组件使用说明
|
||||||
|
|
||||||
基于 antd-mobile 的 ImageUploader 组件封装的上传组件,支持图片上传、预览、删除等功能。
|
## MainImgUpload 主图封面上传组件
|
||||||
|
|
||||||
## 功能特性
|
### 功能特点
|
||||||
|
|
||||||
- ✅ 支持单张/多张图片上传
|
- 只支持上传一张图片作为主图封面
|
||||||
- ✅ 文件类型和大小验证
|
- 上传后右上角显示删除按钮
|
||||||
- ✅ 上传进度显示
|
- 支持图片预览功能
|
||||||
- ✅ 图片预览功能
|
- 响应式设计,适配移动端
|
||||||
- ✅ 删除确认
|
- 样式参考VideoUpload组件风格
|
||||||
- ✅ 数量限制
|
|
||||||
- ✅ 编辑和新增状态支持
|
|
||||||
- ✅ 响应式设计
|
|
||||||
- ✅ 头像上传专用组件
|
|
||||||
|
|
||||||
## 组件列表
|
### 使用方法
|
||||||
|
|
||||||
### 1. UploadComponent (图片上传)
|
|
||||||
|
|
||||||
通用的图片上传组件,支持多张图片上传。
|
|
||||||
|
|
||||||
### 2. AvatarUpload (头像上传)
|
|
||||||
|
|
||||||
专门的头像上传组件,支持圆形头像显示、删除功能。
|
|
||||||
|
|
||||||
### 3. VideoUpload (视频上传)
|
|
||||||
|
|
||||||
视频上传组件,支持视频文件上传和预览。
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 基础用法
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import React, { useState } from "react";
|
import MainImgUpload from "@/components/Upload/MainImgUpload";
|
||||||
import UploadComponent from "@/components/Upload";
|
|
||||||
|
|
||||||
const MyComponent = () => {
|
const MyComponent = () => {
|
||||||
const [images, setImages] = useState<string[]>([]);
|
const [mainImage, setMainImage] = useState<string>("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UploadComponent
|
<MainImgUpload
|
||||||
value={images}
|
value={mainImage}
|
||||||
onChange={setImages}
|
onChange={setMainImage}
|
||||||
count={5}
|
maxSize={5} // 最大5MB
|
||||||
accept="image/*"
|
showPreview={true} // 显示预览按钮
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 头像上传
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import AvatarUpload from "@/components/Upload/AvatarUpload";
|
|
||||||
|
|
||||||
const AvatarComponent = () => {
|
|
||||||
const [avatar, setAvatar] = useState<string>("");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AvatarUpload
|
|
||||||
value={avatar}
|
|
||||||
onChange={setAvatar}
|
|
||||||
size={100}
|
|
||||||
disabled={false}
|
disabled={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 编辑模式
|
### Props 参数
|
||||||
|
|
||||||
```tsx
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
const EditComponent = () => {
|
| ----------- | --------------------- | ------ | ---------------- |
|
||||||
const [images, setImages] = useState<string[]>([
|
| value | string | '' | 当前图片URL |
|
||||||
"https://example.com/image1.jpg",
|
| onChange | (url: string) => void | - | 图片URL变化回调 |
|
||||||
"https://example.com/image2.jpg",
|
| disabled | boolean | false | 是否禁用 |
|
||||||
]);
|
| className | string | - | 自定义样式类名 |
|
||||||
|
| maxSize | number | 5 | 最大文件大小(MB) |
|
||||||
|
| showPreview | boolean | true | 是否显示预览按钮 |
|
||||||
|
|
||||||
return (
|
### 样式特点
|
||||||
<UploadComponent
|
|
||||||
value={images}
|
- 上传区域:200x200px 的虚线边框区域
|
||||||
onChange={setImages}
|
- 图片预览:上传后显示图片,鼠标悬停显示操作按钮
|
||||||
count={9}
|
- 删除按钮:右上角红色删除图标
|
||||||
disabled={false}
|
- 预览按钮:眼睛图标,点击在新窗口预览
|
||||||
/>
|
- 响应式:移动端自动调整尺寸
|
||||||
);
|
|
||||||
};
|
### 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/components/Upload/
|
||||||
|
├── MainImgUpload.tsx # 主图上传组件
|
||||||
|
├── mainImgUpload.module.scss # 主图上传样式
|
||||||
|
├── VideoUpload.tsx # 视频上传组件
|
||||||
|
└── index.module.scss # 通用上传样式
|
||||||
```
|
```
|
||||||
|
|
||||||
### 禁用状态
|
### 技术实现
|
||||||
|
|
||||||
```tsx
|
- 基于 antd Upload 组件
|
||||||
<UploadComponent value={images} onChange={setImages} disabled={true} />
|
- 使用 antd-mobile 的 Toast 提示
|
||||||
```
|
- 支持 FormData 上传
|
||||||
|
- 自动处理文件验证和错误提示
|
||||||
## API
|
- 集成项目统一的API请求封装
|
||||||
|
|
||||||
### UploadComponent Props
|
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 |
|
|
||||||
| --------- | -------------- | -------------------------- | ----------- |
|
|
||||||
| value | 图片URL数组 | `string[]` | `[]` |
|
|
||||||
| onChange | 图片变化回调 | `(urls: string[]) => void` | - |
|
|
||||||
| count | 最大上传数量 | `number` | `9` |
|
|
||||||
| accept | 接受的文件类型 | `string` | `"image/*"` |
|
|
||||||
| disabled | 是否禁用 | `boolean` | `false` |
|
|
||||||
| className | 自定义类名 | `string` | - |
|
|
||||||
|
|
||||||
### AvatarUpload Props
|
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 |
|
|
||||||
| --------- | ------------ | ----------------------- | ------- |
|
|
||||||
| value | 头像URL | `string` | `""` |
|
|
||||||
| onChange | 头像变化回调 | `(url: string) => void` | - |
|
|
||||||
| disabled | 是否禁用 | `boolean` | `false` |
|
|
||||||
| className | 自定义类名 | `string` | - |
|
|
||||||
| size | 头像尺寸 | `number` | `100` |
|
|
||||||
|
|
||||||
### VideoUpload Props
|
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 |
|
|
||||||
| --------- | ------------ | ----------------------- | ------- |
|
|
||||||
| value | 视频URL | `string` | `""` |
|
|
||||||
| onChange | 视频变化回调 | `(url: string) => void` | - |
|
|
||||||
| disabled | 是否禁用 | `boolean` | `false` |
|
|
||||||
| className | 自定义类名 | `string` | - |
|
|
||||||
|
|
||||||
### 事件
|
|
||||||
|
|
||||||
| 事件名 | 说明 | 回调参数 |
|
|
||||||
| -------- | ------------------ | -------------------------- |
|
|
||||||
| onChange | 文件列表变化时触发 | `(urls: string[]) => void` |
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **文件大小限制**: 默认限制为 5MB
|
|
||||||
2. **文件类型**: 默认只接受图片文件
|
|
||||||
3. **上传接口**: 使用 `/v1/attachment/upload` 接口
|
|
||||||
4. **认证**: 自动携带 token 进行认证
|
|
||||||
5. **预览**: 点击图片可预览
|
|
||||||
6. **删除**: 删除图片会有确认提示
|
|
||||||
7. **头像组件**: 支持圆形显示、删除按钮、上传覆盖层
|
|
||||||
|
|
||||||
## 样式定制
|
|
||||||
|
|
||||||
组件支持通过 CSS 模块进行样式定制:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
.uploadContainer {
|
|
||||||
// 自定义样式
|
|
||||||
:global {
|
|
||||||
.adm-image-uploader {
|
|
||||||
// 覆盖 antd-mobile 默认样式
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatarUploadContainer {
|
|
||||||
// 头像上传组件样式
|
|
||||||
.avatarWrapper {
|
|
||||||
// 头像容器样式
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
- 文件类型不匹配时会显示错误提示
|
|
||||||
- 文件大小超限时会显示错误提示
|
|
||||||
- 上传失败时会显示错误提示
|
|
||||||
- 网络错误时会显示错误提示
|
|
||||||
|
|
||||||
## 头像上传特性
|
|
||||||
|
|
||||||
- **圆形显示**: 头像以圆形方式显示
|
|
||||||
- **占位符**: 无头像时显示用户图标
|
|
||||||
- **上传覆盖**: 鼠标悬停显示上传图标
|
|
||||||
- **删除功能**: 右上角删除按钮
|
|
||||||
- **加载状态**: 上传时显示加载提示
|
|
||||||
- **尺寸可调**: 支持自定义头像尺寸
|
|
||||||
|
|||||||
484
nkebao/src/components/Upload/VideoUpload/index.module.scss
Normal file
484
nkebao/src/components/Upload/VideoUpload/index.module.scss
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
.uploadContainer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// 自定义上传组件样式
|
||||||
|
:global {
|
||||||
|
.adm-image-uploader {
|
||||||
|
.adm-image-uploader-upload-button {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1677ff;
|
||||||
|
background: #f0f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-upload-button-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-item {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.adm-image-uploader-item-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-item-delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-item-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用状态
|
||||||
|
.uploadContainer.disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误状态
|
||||||
|
.uploadContainer.error {
|
||||||
|
:global {
|
||||||
|
.adm-image-uploader-upload-button {
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
background: #fff2f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.uploadContainer {
|
||||||
|
:global {
|
||||||
|
.adm-image-uploader {
|
||||||
|
.adm-image-uploader-upload-button,
|
||||||
|
.adm-image-uploader-item {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-image-uploader-upload-button-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头像上传组件样式
|
||||||
|
.avatarUploadContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.avatarWrapper {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 142, 238, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarImage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarPlaceholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarUploadOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadLoading {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarDeleteBtn {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -8px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ff7875;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .avatarUploadOverlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarTip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频上传组件样式
|
||||||
|
.videoUploadContainer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.videoUploadButton {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
border: 2px dashed #d9d9d9;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(24, 144, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.uploadingIcon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #1890ff;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingText {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadProgress {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.uploadIcon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #1890ff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .uploadIcon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItem {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItemContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.videoIcon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoInfo {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.videoName {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSize {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoActions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewBtn {
|
||||||
|
color: #1890ff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
background: #e6f7ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteBtn {
|
||||||
|
color: #ff4d4f;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ff7875;
|
||||||
|
background: #fff2f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemProgress {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoPreview {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
video {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动画效果
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暗色主题支持
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.videoUploadContainer {
|
||||||
|
.videoUploadButton {
|
||||||
|
background: linear-gradient(135deg, #2a2a2a 0%, #1f1f1f 100%);
|
||||||
|
border-color: #434343;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, #1a365d 0%, #2d3748 100%);
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadingContainer {
|
||||||
|
.uploadingText {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadContent {
|
||||||
|
.uploadText {
|
||||||
|
.uploadTitle {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadSubtitle {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItem {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-color: #434343;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoItemContent {
|
||||||
|
.videoInfo {
|
||||||
|
.videoName {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoSize {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoActions {
|
||||||
|
.previewBtn,
|
||||||
|
.deleteBtn {
|
||||||
|
&:hover {
|
||||||
|
background: #434343;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import NavCommon from "@/components/NavCommon";
|
import NavCommon from "@/components/NavCommon";
|
||||||
import UploadComponent from "@/components/Upload/ImageUpload";
|
import UploadComponent from "@/components/Upload/ImageUpload/ImageUpload";
|
||||||
import VideoUpload from "@/components/Upload/VideoUpload";
|
import VideoUpload from "@/components/Upload/VideoUpload";
|
||||||
import {
|
import {
|
||||||
getContentItemDetail,
|
getContentItemDetail,
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import React, { useState } from "react";
|
|||||||
import { Button, Card, Space, Divider, Toast, Switch } from "antd-mobile";
|
import { Button, Card, Space, Divider, Toast, Switch } from "antd-mobile";
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import NavCommon from "@/components/NavCommon";
|
import NavCommon from "@/components/NavCommon";
|
||||||
import ImageUpload from "@/components/Upload/ImageUpload";
|
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
|
||||||
import AvatarUpload from "@/components/Upload/AvatarUpload";
|
import AvatarUpload from "@/components/Upload/AvatarUpload";
|
||||||
import VideoUpload from "@/components/Upload/VideoUpload";
|
import VideoUpload from "@/components/Upload/VideoUpload";
|
||||||
import FileUpload from "@/components/Upload/FileUpload";
|
import FileUpload from "@/components/Upload/FileUpload";
|
||||||
|
import MainImgUpload from "@/components/Upload/MainImgUpload";
|
||||||
import styles from "./upload.module.scss";
|
import styles from "./upload.module.scss";
|
||||||
|
|
||||||
// 错误边界组件
|
// 错误边界组件
|
||||||
@@ -77,6 +78,12 @@ const UploadTestPage: React.FC = () => {
|
|||||||
"ppt",
|
"ppt",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 主图上传状态
|
||||||
|
const [mainImgUrl, setMainImgUrl] = useState<string>("");
|
||||||
|
const [mainImgDisabled, setMainImgDisabled] = useState(false);
|
||||||
|
const [mainImgMaxSize, setMainImgMaxSize] = useState(5);
|
||||||
|
const [mainImgShowPreview, setMainImgShowPreview] = useState(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout header={<NavCommon title="上传组件功能测试" />} loading={loading}>
|
<Layout header={<NavCommon title="上传组件功能测试" />} loading={loading}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@@ -335,6 +342,79 @@ const UploadTestPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</ErrorBoundary>
|
</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>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user