feat: 在播放控制器中添加用户交互锁,优化时间选择逻辑,确保用户操作优先于外部时间更新
This commit is contained in:
parent
7794c91eca
commit
c5410bb3f5
@ -22,6 +22,7 @@ const HOUR_IN_MS = 3600 * 1000;
|
||||
|
||||
// --- Internal State ---
|
||||
const selectedHour = ref(Math.floor(props.currentTime / HOUR_IN_MS));
|
||||
const isUserInteracting = ref(false); // 标志位,防止 props 更新与用户操作冲突
|
||||
|
||||
// --- Computed View Range ---
|
||||
const viewStartTime = computed(() => selectedHour.value * HOUR_IN_MS);
|
||||
@ -34,6 +35,11 @@ const { ticks } = useTimelineTicks(viewStartTime, viewEndTime);
|
||||
watch(
|
||||
() => props.currentTime,
|
||||
(newValue) => {
|
||||
// 如果用户正在主动交互(如拖动、点击),则暂时不根据外部时间更新小时选择
|
||||
if (isUserInteracting.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅在有效范围内才同步小时;避免负数/异常 currentTime 破坏选择器
|
||||
if (Number.isFinite(newValue) && newValue >= 0 && props.totalDuration > 0) {
|
||||
const maxHour = Math.max(Math.ceil(props.totalDuration / HOUR_IN_MS) - 1, 0);
|
||||
@ -56,7 +62,7 @@ const formatTime = (ms: number): string => {
|
||||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const currentTimeFormatted = computed(() => formatTime(props.currentTime));
|
||||
const currentTimeFormatted = computed(() => formatTime(pendingSeekTime.value ?? props.currentTime));
|
||||
const totalDurationFormatted = computed(() => formatTime(props.totalDuration));
|
||||
|
||||
// 本地“乐观显示”的时间:在 seek 后先行展示,待实际时间变更后清空
|
||||
@ -70,14 +76,6 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
const sliderValue = computed({
|
||||
get: () => pendingSeekTime.value ?? props.currentTime,
|
||||
set: (val: number) => {
|
||||
pendingSeekTime.value = val;
|
||||
emit('seek', val);
|
||||
},
|
||||
});
|
||||
|
||||
const playheadPosition = computed(() => {
|
||||
const viewDuration = viewEndTime.value - viewStartTime.value;
|
||||
if (viewDuration <= 0) return '0%';
|
||||
@ -101,6 +99,26 @@ const hourOptions = computed(() => {
|
||||
|
||||
// --- Event Handlers ---
|
||||
const timelineContainerRef = ref<HTMLElement | null>(null);
|
||||
let interactionTimer: number | undefined;
|
||||
|
||||
// 设置一个短暂的用户交互锁,以避免外部时间更新覆盖用户的即时选择
|
||||
const setUserInteraction = () => {
|
||||
isUserInteracting.value = true;
|
||||
clearTimeout(interactionTimer);
|
||||
interactionTimer = window.setTimeout(() => {
|
||||
isUserInteracting.value = false;
|
||||
}, 2000); // 2秒内,用户的操作优先
|
||||
};
|
||||
|
||||
const handleHourChange = (newHour: number) => {
|
||||
setUserInteraction();
|
||||
selectedHour.value = newHour;
|
||||
const newTime = newHour * HOUR_IN_MS;
|
||||
// 确保搜寻时间不超过总时长
|
||||
const clampedTime = Math.min(newTime, props.totalDuration);
|
||||
pendingSeekTime.value = clampedTime;
|
||||
emit('seek', clampedTime);
|
||||
};
|
||||
|
||||
const handlePlayPause = () => {
|
||||
if (props.isPlaying) {
|
||||
@ -112,6 +130,7 @@ const handlePlayPause = () => {
|
||||
|
||||
// 根据点击时间轴位置计算对应时间并触发 seek
|
||||
const handleTimelineClick = (event: MouseEvent) => {
|
||||
setUserInteraction();
|
||||
const container = timelineContainerRef.value;
|
||||
if (!container) return;
|
||||
|
||||
@ -155,7 +174,7 @@ const handleTimelineClick = (event: MouseEvent) => {
|
||||
|
||||
<!-- Hour Selector -->
|
||||
<a-col>
|
||||
<a-select v-model:value="selectedHour" style="width: 150px" :options="hourOptions" />
|
||||
<a-select :value="selectedHour" style="width: 150px" :options="hourOptions" @change="handleHourChange" />
|
||||
</a-col>
|
||||
|
||||
<!-- Timeline Slider -->
|
||||
@ -173,13 +192,6 @@ const handleTimelineClick = (event: MouseEvent) => {
|
||||
<span v-if="tick.label" class="tick-label">{{ tick.label }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<a-slider
|
||||
v-model:value="sliderValue"
|
||||
:min="viewStartTime"
|
||||
:max="viewEndTime"
|
||||
:tip-formatter="formatTime as any"
|
||||
:tooltip-open="false"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@ -215,6 +227,20 @@ const handleTimelineClick = (event: MouseEvent) => {
|
||||
position: relative;
|
||||
padding-top: 20px; // Space for tick labels
|
||||
margin-bottom: 5px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
height: 2px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 1px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.playhead-marker {
|
||||
@ -270,5 +296,8 @@ body[data-theme='dark'] {
|
||||
.tick-label {
|
||||
color: #aaa;
|
||||
}
|
||||
.timeline-container::after {
|
||||
background-color: #434343;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -42,8 +42,8 @@ export function useTimelineTicks(viewStartTime: Ref<number>, viewEndTime: Ref<nu
|
||||
|
||||
result.push({
|
||||
position: `${(relativeTime / viewDuration) * 100}%`,
|
||||
// 使用相对视窗的时间展示,避免绝对时间导致的大数/负数
|
||||
label: isMajor ? formatLabelTime(relativeTime) : null,
|
||||
// 使用绝对时间来展示刻度标签,确保其在切换小时后能正确反映当前时间
|
||||
label: isMajor ? formatLabelTime(time) : null,
|
||||
isMajor,
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user