diff --git a/src/components/ImageModal.css b/src/components/ImageModal.css new file mode 100644 index 0000000..127c712 --- /dev/null +++ b/src/components/ImageModal.css @@ -0,0 +1,65 @@ +/* PC端图片弹窗样式 */ +.desktop-image-modal .ant-modal-wrap { + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.desktop-image-modal .ant-modal { + top: auto !important; + left: auto !important; + transform: none !important; + margin: 0 !important; + padding-bottom: 0 !important; + position: relative !important; +} + +.desktop-image-modal .ant-modal-content { + border-radius: 8px; + overflow: hidden; +} + +/* 移动端图片弹窗样式 */ +.mobile-image-modal .ant-modal-wrap { + display: flex !important; + align-items: center !important; + justify-content: center !important; + padding: 64px 0 0 0 !important; /* 顶部留出标题栏空间 */ +} + +.mobile-image-modal .ant-modal { + top: auto !important; + left: auto !important; + transform: none !important; + margin: 0 !important; + padding-bottom: 0 !important; + position: relative !important; + width: 100vw !important; +} + +.mobile-image-modal .ant-modal-content { + border-radius: 0; + width: 100% !important; + max-height: calc(100vh - 64px); +} + +.mobile-image-modal .ant-modal-body { + padding: 0 !important; +} + +/* 确保图片容器不会溢出 */ +.image-modal-container { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.image-modal-container img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + display: block; +} \ No newline at end of file diff --git a/src/components/ImageModal.tsx b/src/components/ImageModal.tsx new file mode 100644 index 0000000..45a2f74 --- /dev/null +++ b/src/components/ImageModal.tsx @@ -0,0 +1,188 @@ +import React, { useState, useEffect } from 'react'; +import { Modal, Spin } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import './ImageModal.css'; + +interface ImageModalProps { + visible: boolean; + imageUrl: string; + onClose: () => void; +} + +// 图片缓存 +const imageCache = new Set(); + +const ImageModal: React.FC = ({ visible, imageUrl, onClose }) => { + const [loading, setLoading] = useState(false); + const [imageLoaded, setImageLoaded] = useState(false); + const [imageError, setImageError] = useState(false); + const [isMobile, setIsMobile] = useState(false); + const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null); + + // 检测是否为移动端 + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + + return () => window.removeEventListener('resize', checkMobile); + }, []); + + // 预加载图片 + useEffect(() => { + if (visible && imageUrl) { + // 如果图片已缓存,直接显示 + if (imageCache.has(imageUrl)) { + setImageLoaded(true); + setLoading(false); + return; + } + + setLoading(true); + setImageLoaded(false); + setImageError(false); + + const img = new Image(); + img.onload = () => { + imageCache.add(imageUrl); + setImageDimensions({ width: img.naturalWidth, height: img.naturalHeight }); + setImageLoaded(true); + setLoading(false); + }; + img.onerror = () => { + setImageError(true); + setLoading(false); + }; + img.src = imageUrl; + } + }, [visible, imageUrl]); + + // 重置状态当弹窗关闭时 + useEffect(() => { + if (!visible) { + setLoading(false); + setImageLoaded(false); + setImageError(false); + setImageDimensions(null); + } + }, [visible]); + + // 计算移动端弹窗高度 + const getMobileModalHeight = () => { + if (imageLoaded && imageDimensions) { + // 如果图片已加载,根据图片比例自适应高度 + const availableHeight = window.innerHeight - 64; // 减去标题栏高度 + const availableWidth = window.innerWidth; + + // 计算图片按宽度100%显示时的高度 + const aspectRatio = imageDimensions.height / imageDimensions.width; + const calculatedHeight = availableWidth * aspectRatio; + + // 确保高度不超过可用空间的90% + const maxHeight = availableHeight * 0.9; + const finalHeight = Math.min(calculatedHeight, maxHeight); + + return `${finalHeight}px`; + } + // 图片未加载时,使用默认高度(除标题栏外的33%) + return 'calc((100vh - 64px) * 0.33)'; + }; + + const modalStyle = isMobile ? { + // 移动端居中显示,不设置top + paddingBottom: 0, + margin: 0, + } : { + // PC端不设置top,让centered属性处理居中 + }; + + const modalBodyStyle = isMobile ? { + padding: 0, + height: getMobileModalHeight(), + minHeight: 'calc((100vh - 64px) * 0.33)', // 最小高度为33% + maxHeight: 'calc(100vh - 64px)', // 最大高度不超过可视区域 + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#000', + } : { + padding: 0, + height: '66vh', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#000', + }; + + return ( + + {/* 自定义关闭按钮 */} +
{ + e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + }} + > + +
+ + {/* 图片内容 */} +
+ {loading && ( + + )} + + {imageError && ( +
+
📷
+
图片加载失败
+
+ )} + + {imageLoaded && !loading && !imageError && ( + 预览图片 + )} +
+
+ ); +}; + +export default ImageModal; \ No newline at end of file diff --git a/src/components/ResourceList.tsx b/src/components/ResourceList.tsx index 6dd3fe9..54bfa32 100644 --- a/src/components/ResourceList.tsx +++ b/src/components/ResourceList.tsx @@ -3,9 +3,10 @@ import { Layout, Typography, Table, Grid, InputNumber, Button, Space, Tag, messa import type { ColumnsType, ColumnType } from 'antd/es/table'; import type { FilterDropdownProps } from 'antd/es/table/interface'; import type { TableProps } from 'antd'; -import { SearchOutlined, EllipsisOutlined, DeleteOutlined, ManOutlined, WomanOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; +import { SearchOutlined, EllipsisOutlined, DeleteOutlined, ManOutlined, WomanOutlined, ExclamationCircleOutlined, PictureOutlined } from '@ant-design/icons'; import './MainContent.css'; import InputDrawer from './InputDrawer.tsx'; +import ImageModal from './ImageModal.tsx'; import { getPeoples } from '../apis'; import type { People } from '../apis'; import { deletePeople } from '../apis/people'; @@ -28,6 +29,7 @@ function transformPeoples(list: People[] = []): Resource[] { marital_status: person.marital_status, introduction: person.introduction || {}, contact: person.contact || '', + cover: person.cover || '', })); } @@ -516,6 +518,10 @@ const ResourceList: React.FC = ({ inputOpen = false, onCloseInput, contai // const [inputResult, setInputResult] = React.useState(null); const [swipedRowId, setSwipedRowId] = React.useState(null); const touchStartRef = React.useRef<{ x: number; y: number } | null>(null); + + // 图片弹窗状态 + const [imageModalVisible, setImageModalVisible] = React.useState(false); + const [currentImageUrl, setCurrentImageUrl] = React.useState(''); const handleTableChange: TableProps['onChange'] = (pg) => { setPagination({ current: pg?.current ?? 1, pageSize: pg?.pageSize ?? 10 }); @@ -589,17 +595,44 @@ const ResourceList: React.FC = ({ inputOpen = false, onCloseInput, contai }, onFilter: (filterValue: React.Key | boolean, record: Resource) => String(record.name).includes(String(filterValue)), render: (text: string, record: Resource) => { - if (!isMobile) return {text}; + // 图片图标逻辑 + const hasCover = record.cover && record.cover.trim() !== ''; + const pictureIcon = ( + { + setCurrentImageUrl(record.cover); + setImageModalVisible(true); + } : undefined} + /> + ); + + if (!isMobile) { + // 桌面端:姓名后面跟图片图标 + return ( + + {text} + {pictureIcon} + + ); + } + + // 移动端:姓名 + 性别图标 + 图片图标 const g = record.gender; - const icon = g === '男' + const genderIcon = g === '男' ? : g === '女' ? : ; + return ( {text} - {icon} + {genderIcon} + {pictureIcon} ); }, @@ -820,6 +853,16 @@ const ResourceList: React.FC = ({ inputOpen = false, onCloseInput, contai showUpload={false} mode={'search'} /> + + {/* 图片预览弹窗 */} + { + setImageModalVisible(false); + setCurrentImageUrl(''); + }} + /> ); };