fix: 修复选集点击偏移问题,优化事件处理和布局
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# 选集点击偏移问题的深度修复
|
||||
|
||||
## 问题复现
|
||||
|
||||
用户点击第 6 集(紫色框框),但系统选择了第 7 集(绿色填充),存在点击偏移问题。
|
||||
|
||||
## 深度分析与修复
|
||||
|
||||
### 🔍 问题根因分析
|
||||
|
||||
经过多轮调试,发现问题有**多个层面**:
|
||||
|
||||
#### 1. CSS Grid 布局问题
|
||||
|
||||
```css
|
||||
/* 问题代码 */
|
||||
grid-cols-[repeat(auto-fill,minmax(40px,1fr))]
|
||||
|
||||
/* 问题分析 */
|
||||
- minmax(40px,1fr) 中的 1fr 导致按钮宽度不固定
|
||||
- 在不同屏幕宽度下,按钮实际宽度变化很大
|
||||
- 视觉按钮位置与点击区域不匹配
|
||||
```
|
||||
|
||||
#### 2. 事件处理逻辑问题
|
||||
|
||||
```javascript
|
||||
// 问题代码
|
||||
const handleEpisodeChange = (episodeNumber: number) => {
|
||||
if (episodeNumber >= 0 && episodeNumber < totalEpisodes) {
|
||||
setCurrentEpisodeIndex(episodeNumber); // 错误!
|
||||
}
|
||||
};
|
||||
|
||||
// 问题分析
|
||||
- EpisodeSelector传递的是显示集数(1-based): 1, 2, 3, 4, 5, 6, 7...
|
||||
- 但直接设置为currentEpisodeIndex(0-based): 0, 1, 2, 3, 4, 5, 6...
|
||||
- 导致点击第6集实际设置为index=6,对应第7集
|
||||
```
|
||||
|
||||
#### 3. 可能的覆盖层问题
|
||||
|
||||
- SkipController 的固定定位面板可能覆盖选集区域
|
||||
- 已移动到左下角,但可能不彻底
|
||||
|
||||
### 🔧 完整修复方案
|
||||
|
||||
#### 修复 1: CSS 布局优化
|
||||
|
||||
```tsx
|
||||
// 修复前
|
||||
<div className='grid grid-cols-[repeat(auto-fill,minmax(40px,1fr))] auto-rows-[40px] gap-x-3 gap-y-3'>
|
||||
|
||||
// 修复后
|
||||
<div className='grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-3'>
|
||||
```
|
||||
|
||||
**效果**:
|
||||
|
||||
- 每个按钮固定 48px 宽度
|
||||
- 使用 justify-center 居中对齐
|
||||
- 消除弹性宽度导致的位置偏移
|
||||
|
||||
#### 修复 2: 事件处理修正
|
||||
|
||||
```tsx
|
||||
// 修复前
|
||||
const handleEpisodeChange = (episodeNumber: number) => {
|
||||
if (episodeNumber >= 0 && episodeNumber < totalEpisodes) {
|
||||
setCurrentEpisodeIndex(episodeNumber); // 直接使用显示集数作为索引
|
||||
}
|
||||
};
|
||||
|
||||
// 修复后
|
||||
const handleEpisodeChange = (episodeNumber: number) => {
|
||||
const episodeIndex = episodeNumber - 1; // 转换:显示集数 -> 数组索引
|
||||
if (episodeIndex >= 0 && episodeIndex < totalEpisodes) {
|
||||
setCurrentEpisodeIndex(episodeIndex);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**效果**:
|
||||
|
||||
- 正确处理集数显示值到数组索引的转换
|
||||
- 点击第 6 集 → episodeNumber=6 → episodeIndex=5 → 正确选择第 6 集
|
||||
|
||||
#### 修复 3: 增强事件控制
|
||||
|
||||
```tsx
|
||||
// 添加更强的事件控制
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleEpisodeClick(episodeNumber);
|
||||
}}
|
||||
```
|
||||
|
||||
### 🎯 修复验证
|
||||
|
||||
#### 数据流验证
|
||||
|
||||
1. **用户点击第 6 集按钮**
|
||||
2. **EpisodeSelector.tsx**: `handleEpisodeClick(6)` → `onChange(6)`
|
||||
3. **play/page.tsx**: `handleEpisodeChange(6)` → `episodeIndex = 6-1 = 5`
|
||||
4. **状态更新**: `setCurrentEpisodeIndex(5)` → 选中第 6 集(index=5)
|
||||
5. **UI 更新**: `value={currentEpisodeIndex + 1} = 5+1 = 6` → 第 6 集高亮
|
||||
|
||||
#### 布局验证
|
||||
|
||||
- 固定 48px 宽度确保按钮大小一致
|
||||
- justify-center 确保居中对齐
|
||||
- gap-3 提供合适的间距
|
||||
|
||||
### 📁 修改文件
|
||||
|
||||
1. **src/components/EpisodeSelector.tsx**
|
||||
|
||||
- 修复 CSS Grid 布局:固定按钮宽度
|
||||
- 增强点击事件处理
|
||||
|
||||
2. **src/app/play/page.tsx**
|
||||
|
||||
- 修复 handleEpisodeChange 逻辑错误
|
||||
- 正确处理显示集数到索引的转换
|
||||
|
||||
3. **src/components/SkipController.tsx**
|
||||
- 移动固定面板位置(之前已修复)
|
||||
|
||||
### 🚀 预期效果
|
||||
|
||||
修复后的行为:
|
||||
|
||||
- ✅ 点击第 1 集 → 选择第 1 集
|
||||
- ✅ 点击第 6 集 → 选择第 6 集
|
||||
- ✅ 点击第 7 集 → 选择第 7 集
|
||||
- ✅ 按钮布局美观,宽度一致
|
||||
- ✅ 不受屏幕宽度影响
|
||||
|
||||
### 💡 经验总结
|
||||
|
||||
1. **UI 显示值 vs 数据索引**:要区分用户看到的值(1-based)和内部索引(0-based)
|
||||
2. **CSS 弹性布局**:在需要精确点击的场景下,避免使用 1fr 等弹性单位
|
||||
3. **多层问题**:复杂 bug 可能有多个层面的原因,需要逐层排查
|
||||
4. **事件处理**:添加 preventDefault 和 stopPropagation 增强控制
|
||||
|
||||
这次修复解决了根本问题,确保点击精确性和用户体验。
|
||||
@@ -737,12 +737,14 @@ function PlayPageClient() {
|
||||
// ---------------------------------------------------------------------------
|
||||
// 处理集数切换
|
||||
const handleEpisodeChange = (episodeNumber: number) => {
|
||||
if (episodeNumber >= 0 && episodeNumber < totalEpisodes) {
|
||||
// episodeNumber是显示的集数(从1开始),需要转换为索引(从0开始)
|
||||
const episodeIndex = episodeNumber - 1;
|
||||
if (episodeIndex >= 0 && episodeIndex < totalEpisodes) {
|
||||
// 在更换集数前保存当前播放进度
|
||||
if (artPlayerRef.current && artPlayerRef.current.paused) {
|
||||
saveCurrentPlayProgress();
|
||||
}
|
||||
setCurrentEpisodeIndex(episodeNumber);
|
||||
setCurrentEpisodeIndex(episodeIndex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
||||
</div>
|
||||
|
||||
{/* 集数网格 */}
|
||||
<div className='grid grid-cols-[repeat(auto-fill,minmax(40px,1fr))] auto-rows-[40px] gap-x-3 gap-y-3 overflow-y-auto h-full pb-4'>
|
||||
<div className='grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-3 overflow-y-auto h-full pb-4'>
|
||||
{(() => {
|
||||
const len = currentEnd - currentStart + 1;
|
||||
const episodes = Array.from({ length: len }, (_, i) =>
|
||||
@@ -333,13 +333,18 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
||||
return (
|
||||
<button
|
||||
key={episodeNumber}
|
||||
onClick={() => handleEpisodeClick(episodeNumber)}
|
||||
className={`h-10 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleEpisodeClick(episodeNumber);
|
||||
}}
|
||||
className={`w-12 h-10 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200 cursor-pointer
|
||||
${
|
||||
isActive
|
||||
? 'bg-green-500 text-white shadow-lg shadow-green-500/25 dark:bg-green-600'
|
||||
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 hover:scale-105 dark:bg-white/10 dark:text-gray-300 dark:hover:bg-white/20'
|
||||
}`.trim()}
|
||||
type="button"
|
||||
>
|
||||
{episodeNumber}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user