React - 封装礼物PK条组件
(一)版本1
import React from 'react';
import './index.less';interface PKBarProps {matchTitle: string;leftLabel: string;rightLabel: string;leftImageUrl: string;rightImageUrl: string;leftValue: number;rightValue: number;remainingTime: string;skin: number;minPercent?: number; // 新增最小占比
}const PKBar: React.FC<PKBarProps> = ({matchTitle,leftLabel,rightLabel,leftImageUrl,rightImageUrl,leftValue,rightValue,remainingTime,skin,minPercent = 8,
}) => {const totalValue = leftValue + rightValue;const leftPercent = totalValue > 0? Math.max((leftValue / totalValue) * 100, minPercent): 50;const rightPercent = totalValue > 0? Math.max((rightValue / totalValue) * 100, minPercent): 50;const adjustedLeftPercent = leftPercent > 100 - minPercent ? 100 - minPercent : leftPercent;const adjustedRightPercent = rightPercent > 100 - minPercent ? 100 - minPercent : rightPercent;const markers = skin === 0? ['+8流量', '+4流量', '+2流量', '+2流量', '+4流量', '+8流量']: ['5张', '4张', '2张', '4张', '5张'];const intervalWidth = 100 / (markers.length + 1); // 每个区间的宽度// 计算每个区间的中点位置const markerPositions = markers.map((_, index) => `${(index + 1) * intervalWidth}%`);return (<div className={`pk-container skin-${skin}`}><img className="pk-rectangle" src={require('@/component/PKBar/images/Rectangle.png')} alt="Rectangle"/><span className="pk-time">{matchTitle} {remainingTime}</span><div className="pk-header"><div className="pk-left-image"><img src={leftImageUrl} alt="Left"/><span className="pk-label">{leftLabel}</span></div><div className="pk-right-image"><img src={rightImageUrl} alt="Right"/><span className="pk-label">{rightLabel}</span></div></div><div className="pk-bar"><divclassName="pk-left"style={{width: `${adjustedLeftPercent}%`,background: 'linear-gradient(to right, #FF6B6B, #FF8E53, #FFAAA5 98%, #FFFFFF)',}}></div><span className="pk-value pk-left-value">{leftValue}</span><divclassName="pk-right"style={{width: `${adjustedRightPercent}%`,background: 'linear-gradient(to left, #1E66FF, #4568DC, #A770EF 98%, #FFFFFF)',}}></div><span className="pk-value pk-right-value">{rightValue}</span>{/* 刻度线容器 */}<div className="pk-ticks">{markers.map((_, index) => (<divkey={index}className="pk-tick"style={{left: `${(index + 1) * intervalWidth}%`,}}></div>))}</div></div><div className="pk-markers">{markerPositions.map((pos, index) => (<spankey={index}className="pk-marker"style={{left: pos,transform: 'translateX(-50%)',}}>{markers[index]}</span>))}</div></div>);
};
export default PKBar;
.pk-container {width: 707px;height: 183px;background-color: rgba(0, 0, 0, 0.3); /* 设置透明黑色背景 */border-radius: 10px;padding: 10px; /* 确保边距一致 */box-sizing: border-box; /* 确保 padding 不影响宽度 */position: relative; // 确保容器是定位元素的参考
}.pk-rectangle {position: absolute;top: 0;left: 50%;transform: translateX(-50%);width: 280px;height: 39px;
}.pk-header {display: flex;justify-content: space-between;align-items: center;width: 100%;margin: 10px 0 0 0;
}.pk-left-image, .pk-right-image {display: flex;align-items: center;gap: 12px;
}.pk-left-image img, .pk-right-image img {width: 67px;height: 67px;
}.pk-label {font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;font-weight: 600;font-size: 31px;text-align: center;line-height: 51px;color: #fff;
}.pk-time {font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;font-weight: 400; // 字重400font-size: 28px; // 字体大小28pxline-height: 34px; // 行高34pxcolor: #fff; // 白色字体position: absolute; // 绝对定位top: 2px; // 垂直居中相对于 pk-rectangleleft: 50%; // 水平居中transform: translateX(-50%); // 修正水平居中偏移text-align: center;
}.pk-left, .pk-right {//min-width: 8%; // 设置最小宽度,确保颜色可见height: 100%;transition: width 0.3s ease; // 添加平滑过渡效果
}.pk-left-value, .pk-right-value {font-weight: 700;font-size: 28px;font-family: "PingFang SC", "Microsoft YaHei",sans-serif;line-height: 45.05px;color: white;position: absolute;top: 50%;transform: translateY(-50%);
}.pk-left-value {left: 10px;
}.pk-right-value {right: 10px;
}.pk-bar {width: calc(100% - 20px);/* 减去 10px 左右的 padding */height: 40px;border-radius: 10px;position: absolute;display: flex;overflow: hidden;left: 10px;/* 确保位置从左边有 10px 距离 */
}.pk-ticks {position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: 1; // 确保刻度线位于条的上层
}.pk-tick {position: absolute;top: 4px; // 距离 PK 条顶部 4pxheight: calc(100% - 8px); // 高度减少 8px,以留下上下 4px 的间距width: 2px; // 刻度线宽度background-color: white; // 刻度线颜色opacity: 0.5; // 半透明效果
}
.pk-markers {position: absolute;top: 125px; /* 根据展示图调整位置 */width: 100%; /* 与 PKBar 的宽度一致 */left: 0; /* 与 PKBar 的左边距一致 */font-size: 20px; /* 确保刻度字体大小合适 */font-weight: 400;line-height: 32px;color: white; /* 刻度文字颜色 */z-index: 2; /* 确定顺序不被覆盖 */
}.pk-marker {position: absolute;text-align: center;transform: translateX(-50%); /* 确保文字居中 */font-size: 24px;font-weight: 400;line-height: 32px;
}/* 针对皮肤的不同布局,可以通过条件类名调整 */
.pk-container.skin-0 .pk-markers {font-size: 12px; /* 按需求调整字体大小 */
}.pk-container.skin-1 .pk-markers {font-size: 14px; /* 道具刻度文字稍大 */
}
(二)版本2
优化后的代码示例:
1. 修正百分比计算逻辑:
const calculatePercents = (leftValue: number, rightValue: number, minPercent: number) => {const total = leftValue + rightValue;if (total === 0) return { left: 50, right: 50 };let leftPercent = (leftValue / total) * 100;let rightPercent = (rightValue / total) * 100;if (leftPercent < minPercent) {leftPercent = minPercent;rightPercent = 100 - minPercent;} else if (rightPercent < minPercent) {rightPercent = minPercent;leftPercent = 100 - minPercent;}return { left: leftPercent, right: rightPercent };
};const { left: adjustedLeftPercent, right: adjustedRightPercent } = calculatePercents(leftValue, rightValue, minPercent);
2.布局优化:
发现版本一即使算出了定位,但是由于是字符串,很难居中定位,为此需要将span的外围包裹一层div,通过div容器去精准定位
<div className="pk-markers">{markerPositions.map((pos, index) => (<divkey={index}className="pk-marker-container"style={{position: 'absolute',left: pos,transform: 'translateX(-50%)', // 使用动态计算的 transformheight: '100%',display: 'flex',alignItems: 'center',pointerEvents: 'none',textAlign: 'center',}}><span className="pk-marker-text" style={{width: '100%'}}>{markers[index]}</span></div>))}</div>
.pk-markers {position: absolute;top: 125px; /* 根据实际情况调整 */width: calc(100% - 20px); /* 与 pk-bar 一致 */left: 10px; /* 与 pk-bar 一致 */height: 32px;font-size: 20px;font-weight: 400;line-height: 32px;color: white;z-index: 2;pointer-events: none;
}.pk-marker-container {position: absolute;top: 0;height: 100%;display: flex;justify-content: center; /* 水平居中 */align-items: center; /* 垂直居中 */
}.pk-marker-text {white-space: nowrap; /* 防止换行 */text-align: center;font-weight: 400;font-size: 24px;line-height: 32px;
}
import React from 'react';
import './index.less';interface PKBarProps {matchTitle: string;leftLabel: string;rightLabel: string;leftImageUrl: string;rightImageUrl: string;leftValue: number;rightValue: number;remainingTime: string;skin: number;minPercent?: number; // 新增最小占比
}const PKBar: React.FC<PKBarProps> = ({matchTitle,leftLabel,rightLabel,leftImageUrl,rightImageUrl,leftValue,rightValue,remainingTime,skin,minPercent = 8,
}) => {const calculatePercents = (leftValue: number, rightValue: number, minPercent: number) => {const total = leftValue + rightValue;if (total === 0) return { left: 50, right: 50 };let leftPercent = (leftValue / total) * 100;let rightPercent = (rightValue / total) * 100;if (leftPercent < minPercent) {leftPercent = minPercent;rightPercent = 100 - minPercent;} else if (rightPercent < minPercent) {rightPercent = minPercent;leftPercent = 100 - minPercent;}return { left: leftPercent, right: rightPercent };};const { left: adjustedLeftPercent, right: adjustedRightPercent } = calculatePercents(leftValue, rightValue, minPercent);const markers = skin === 0? ['+8流量', '+4流量', '+2流量', '+2流量', '+4流量', '+8流量']: ['5张', '4张', '2张', '4张'];const intervalWidth = 100 / (markers.length + 1); // 每个区间的宽度// 计算每个区间的中点位置const markerPositions = markers.map((_, index) => `${(index + 1) * intervalWidth}%`);console.log('markerPositions:', markerPositions)return (<div className={`pk-container skin-${skin}`}><img className="pk-rectangle" src={require('@/component/PKBar/images/Rectangle.png')} alt="Rectangle"/><span className="pk-time">{matchTitle} {remainingTime}</span><div className="pk-header"><div className="pk-left-image"><img src={leftImageUrl} alt="Left"/><span className="pk-label">{leftLabel}</span></div><div className="pk-right-image"><img src={rightImageUrl} alt="Right"/><span className="pk-label">{rightLabel}</span></div></div><div className="pk-bar"><divclassName="pk-left"style={{width: `${adjustedLeftPercent}%`,background: 'linear-gradient(to right, #FF6B6B, #FF8E53, #FFAAA5 98%, #FFFFFF)',}}></div><span className="pk-value pk-left-value">{leftValue}</span><divclassName="pk-right"style={{width: `${adjustedRightPercent}%`,background: 'linear-gradient(to left, #1E66FF, #4568DC, #A770EF 98%, #FFFFFF)',}}></div><span className="pk-value pk-right-value">{rightValue}</span>{/* 刻度线容器 */}<div className="pk-ticks">{markers.map((_, index) => (<divkey={index}className="pk-tick"style={{left: `${(index + 1) * intervalWidth}%`,}}></div>))}</div></div><div className="pk-markers">{markerPositions.map((pos, index) => (<divkey={index}className="pk-marker-container"style={{position: 'absolute',left: pos,transform: 'translateX(-50%)', height: '100%',display: 'flex',alignItems: 'center',pointerEvents: 'none',textAlign: 'center',}}><span className="pk-marker-text" style={{width: '100%'}}>{markers[index]}</span></div>))}</div></div>);
};
export default PKBar;
.pk-container {width: 707px;height: 183px;background-color: rgba(0, 0, 0, 0.3); /* 设置透明黑色背景 */border-radius: 10px;padding: 10px; /* 确保边距一致 */box-sizing: border-box; /* 确保 padding 不影响宽度 */position: relative; // 确保容器是定位元素的参考
}.pk-rectangle {position: absolute;top: 0;left: 50%;transform: translateX(-50%);width: 280px;height: 39px;
}.pk-header {display: flex;justify-content: space-between;align-items: center;width: 100%;margin: 10px 0 0 0;
}.pk-left-image, .pk-right-image {display: flex;align-items: center;gap: 12px;
}.pk-left-image img, .pk-right-image img {width: 67px;height: 67px;
}.pk-label {font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;font-weight: 600;font-size: 31px;text-align: center;line-height: 51px;color: #fff;
}.pk-time {font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;font-weight: 400; // 字重400font-size: 28px; // 字体大小28pxline-height: 34px; // 行高34pxcolor: #fff; // 白色字体position: absolute; // 绝对定位top: 2px; // 垂直居中相对于 pk-rectangleleft: 50%; // 水平居中transform: translateX(-50%); // 修正水平居中偏移text-align: center;
}.pk-left, .pk-right {//min-width: 8%; // 设置最小宽度,确保颜色可见height: 100%;transition: width 0.3s ease; // 添加平滑过渡效果
}.pk-left-value, .pk-right-value {font-weight: 700;font-size: 28px;font-family: "PingFang SC", "Microsoft YaHei",sans-serif;line-height: 45.05px;color: white;position: absolute;top: 50%;transform: translateY(-50%);
}.pk-left-value {left: 10px;
}.pk-right-value {right: 10px;
}.pk-bar {width: calc(100% - 20px);/* 减去 10px 左右的 padding */height: 40px;border-radius: 10px;position: absolute;display: flex;overflow: hidden;left: 10px;/* 确保位置从左边有 10px 距离 */
}.pk-ticks {position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: 1; // 确保刻度线位于条的上层
}.pk-tick {position: absolute;top: 4px; // 距离 PK 条顶部 4pxheight: calc(100% - 8px); // 高度减少 8px,以留下上下 4px 的间距width: 2px; // 刻度线宽度background-color: white; // 刻度线颜色opacity: 0.5; // 半透明效果
}
.pk-markers {position: absolute;top: 125px; /* 根据实际情况调整 */width: calc(100% - 20px); /* 与 pk-bar 一致 */left: 10px; /* 与 pk-bar 一致 */height: 32px;font-size: 20px;font-weight: 400;line-height: 32px;color: white;z-index: 2;pointer-events: none;
}.pk-marker-container {position: absolute;top: 0;height: 100%;display: flex;justify-content: center; /* 水平居中 */align-items: center; /* 垂直居中 */
}.pk-marker-text {white-space: nowrap; /* 防止换行 */text-align: center;font-weight: 400;font-size: 24px;line-height: 32px;
}/* 针对皮肤的不同布局,可以通过条件类名调整 */
.pk-container.skin-0 .pk-markers {font-size: 12px; /* 按需求调整字体大小 */
}.pk-container.skin-1 .pk-markers {font-size: 14px; /* 道具刻度文字稍大 */
}