180 lines
5.3 KiB
JavaScript
180 lines
5.3 KiB
JavaScript
import { useState, useRef } from 'react'
|
|
import { createPortal } from 'react-dom'
|
|
import { Button, Card, Tag } from 'antd'
|
|
import {
|
|
BulbOutlined,
|
|
WarningOutlined,
|
|
ThunderboltOutlined,
|
|
} from '@ant-design/icons'
|
|
import './ButtonWithHoverCard.css'
|
|
|
|
/**
|
|
* 悬浮展开卡片按钮组件
|
|
* 鼠标悬停时,在按钮旁边展开一个精美的信息卡片
|
|
* @param {Object} props
|
|
* @param {string} props.label - 按钮文本
|
|
* @param {ReactNode} props.icon - 按钮图标
|
|
* @param {string} props.type - 按钮类型
|
|
* @param {boolean} props.danger - 危险按钮
|
|
* @param {boolean} props.disabled - 禁用状态
|
|
* @param {Function} props.onClick - 点击回调
|
|
* @param {Object} props.cardInfo - 卡片信息配置
|
|
* @param {string} props.size - 按钮大小
|
|
*/
|
|
function ButtonWithHoverCard({
|
|
label,
|
|
icon,
|
|
type = 'default',
|
|
danger = false,
|
|
disabled = false,
|
|
onClick,
|
|
cardInfo,
|
|
size = 'middle',
|
|
...restProps
|
|
}) {
|
|
const [showCard, setShowCard] = useState(false)
|
|
const [cardPosition, setCardPosition] = useState({ top: 0, left: 0 })
|
|
const wrapperRef = useRef(null)
|
|
|
|
const handleMouseEnter = () => {
|
|
if (!cardInfo || disabled) return
|
|
|
|
if (wrapperRef.current) {
|
|
const rect = wrapperRef.current.getBoundingClientRect()
|
|
setCardPosition({
|
|
top: rect.top + rect.height / 2,
|
|
left: rect.right + 12,
|
|
})
|
|
}
|
|
setShowCard(true)
|
|
}
|
|
|
|
const handleMouseLeave = () => {
|
|
setShowCard(false)
|
|
}
|
|
|
|
// 渲染悬浮卡片
|
|
const renderCard = () => {
|
|
if (!showCard || !cardInfo) return null
|
|
|
|
return (
|
|
<div
|
|
className={`hover-info-card ${showCard ? 'hover-info-card-visible' : ''}`}
|
|
style={{
|
|
top: cardPosition.top,
|
|
left: cardPosition.left,
|
|
}}
|
|
>
|
|
<Card
|
|
size="small"
|
|
bordered={false}
|
|
className="hover-info-card-content"
|
|
>
|
|
{/* 标题区 */}
|
|
<div className="hover-card-header">
|
|
<div className="hover-card-title-wrapper">
|
|
{cardInfo.icon && (
|
|
<span className="hover-card-icon">{cardInfo.icon}</span>
|
|
)}
|
|
<h4 className="hover-card-title">{cardInfo.title}</h4>
|
|
</div>
|
|
{cardInfo.badge && (
|
|
<Tag color={cardInfo.badge.color} className="hover-card-badge">
|
|
{cardInfo.badge.text}
|
|
</Tag>
|
|
)}
|
|
</div>
|
|
|
|
{/* 描述 */}
|
|
{cardInfo.description && (
|
|
<div className="hover-card-section">
|
|
<p className="hover-card-description">{cardInfo.description}</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* 使用场景 */}
|
|
{cardInfo.scenarios && cardInfo.scenarios.length > 0 && (
|
|
<div className="hover-card-section">
|
|
<div className="hover-card-section-title">
|
|
<BulbOutlined className="section-icon" />
|
|
使用场景
|
|
</div>
|
|
<ul className="hover-card-list">
|
|
{cardInfo.scenarios.slice(0, 2).map((scenario, index) => (
|
|
<li key={index}>{scenario}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{/* 快速提示 */}
|
|
{cardInfo.quickTips && cardInfo.quickTips.length > 0 && (
|
|
<div className="hover-card-section">
|
|
<div className="hover-card-section-title">
|
|
<ThunderboltOutlined className="section-icon" />
|
|
快速提示
|
|
</div>
|
|
<ul className="hover-card-list">
|
|
{cardInfo.quickTips.map((tip, index) => (
|
|
<li key={index}>{tip}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{/* 注意事项 */}
|
|
{cardInfo.warnings && cardInfo.warnings.length > 0 && (
|
|
<div className="hover-card-section hover-card-warning">
|
|
<div className="hover-card-section-title">
|
|
<WarningOutlined className="section-icon" />
|
|
注意
|
|
</div>
|
|
<ul className="hover-card-list">
|
|
{cardInfo.warnings.slice(0, 2).map((warning, index) => (
|
|
<li key={index}>{warning}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{/* 快捷键 */}
|
|
{cardInfo.shortcut && (
|
|
<div className="hover-card-footer">
|
|
<span className="footer-label">快捷键</span>
|
|
<kbd className="footer-kbd">{cardInfo.shortcut}</kbd>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
ref={wrapperRef}
|
|
className="button-hover-card-wrapper"
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
<Button
|
|
type={type}
|
|
icon={icon}
|
|
danger={danger}
|
|
disabled={disabled}
|
|
onClick={onClick}
|
|
size={size}
|
|
{...restProps}
|
|
>
|
|
{label}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 使用 Portal 渲染悬浮卡片到 body */}
|
|
{typeof document !== 'undefined' && createPortal(renderCard(), document.body)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default ButtonWithHoverCard
|