165 lines
4.2 KiB
JavaScript
165 lines
4.2 KiB
JavaScript
import { ref, watch, nextTick, getCurrentInstance } from 'vue';
|
|
import { onReady } from '@dcloudio/uni-app';
|
|
import { generateHazardHistory } from './hazardDetail.js';
|
|
|
|
/**
|
|
* 隐患详情左右联动滚动(与 HazardDetailPanel 保持一致)
|
|
* - 右侧滚动时,根据 node-section 位置同步左侧 activeIndex
|
|
* - 点击左侧步骤时,右侧 scroll-into-view 定位到对应卡片
|
|
*/
|
|
export function useHazardDetailScroll(detailSource, loadingSource) {
|
|
const instance = getCurrentInstance();
|
|
const queryScope = instance?.proxy || instance;
|
|
|
|
const historyList = ref([]);
|
|
const activeIndex = ref(0);
|
|
const sectionOffsets = ref([0]);
|
|
const contentViewHeight = ref(0);
|
|
const contentScrollIntoView = ref('');
|
|
const stepScrollIntoView = ref('');
|
|
const scrollWithAnimation = ref(true);
|
|
const isProgrammaticScroll = ref(false);
|
|
let measureLayoutTimer = null;
|
|
|
|
const measureLayout = () => {
|
|
if (!historyList.value.length) return;
|
|
|
|
nextTick(() => {
|
|
const query = uni.createSelectorQuery().in(queryScope);
|
|
query.select('.content-scroll').boundingClientRect();
|
|
query.select('.content-scroll').scrollOffset();
|
|
query.selectAll('.node-section').boundingClientRect();
|
|
query.exec((res) => {
|
|
const containerRect = res?.[0];
|
|
const scrollOffset = res?.[1];
|
|
const sections = res?.[2] || [];
|
|
if (!containerRect || !sections.length) return;
|
|
|
|
contentViewHeight.value = containerRect.height || 0;
|
|
const baseScrollTop = scrollOffset?.scrollTop || 0;
|
|
sectionOffsets.value = sections.map(
|
|
(section) => section.top - containerRect.top + baseScrollTop
|
|
);
|
|
});
|
|
});
|
|
};
|
|
|
|
const scheduleMeasureLayout = () => {
|
|
clearTimeout(measureLayoutTimer);
|
|
measureLayoutTimer = setTimeout(() => {
|
|
measureLayout();
|
|
}, 80);
|
|
};
|
|
|
|
const rebuildHistory = (data) => {
|
|
historyList.value = generateHazardHistory(data);
|
|
activeIndex.value = 0;
|
|
contentScrollIntoView.value = '';
|
|
scheduleMeasureLayout();
|
|
setTimeout(scheduleMeasureLayout, 300);
|
|
};
|
|
|
|
const syncActiveIndex = (scrollTop, scrollHeight = 0) => {
|
|
const count = historyList.value.length;
|
|
if (!count) return;
|
|
|
|
if (scrollHeight > 0 && contentViewHeight.value > 0) {
|
|
if (scrollTop + contentViewHeight.value >= scrollHeight - 60) {
|
|
if (activeIndex.value !== count - 1) {
|
|
activeIndex.value = count - 1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
const offsets = sectionOffsets.value;
|
|
if (!offsets.length) return;
|
|
|
|
let idx = 0;
|
|
for (let i = offsets.length - 1; i >= 0; i--) {
|
|
if (scrollTop >= offsets[i] - 80) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (idx !== activeIndex.value) {
|
|
activeIndex.value = idx;
|
|
}
|
|
};
|
|
|
|
const onContentScroll = (e) => {
|
|
if (isProgrammaticScroll.value) return;
|
|
const { scrollTop = 0, scrollHeight = 0 } = e.detail || {};
|
|
syncActiveIndex(scrollTop, scrollHeight);
|
|
scheduleMeasureLayout();
|
|
};
|
|
|
|
const onScrollToLower = () => {
|
|
const count = historyList.value.length;
|
|
if (count > 0 && activeIndex.value !== count - 1) {
|
|
activeIndex.value = count - 1;
|
|
}
|
|
};
|
|
|
|
const scrollToNode = (index) => {
|
|
if (index < 0 || index >= historyList.value.length) return;
|
|
|
|
isProgrammaticScroll.value = true;
|
|
scrollWithAnimation.value = true;
|
|
activeIndex.value = index;
|
|
contentScrollIntoView.value = 'hazard-node-' + index;
|
|
|
|
setTimeout(() => {
|
|
contentScrollIntoView.value = '';
|
|
isProgrammaticScroll.value = false;
|
|
scheduleMeasureLayout();
|
|
}, 350);
|
|
};
|
|
|
|
watch(
|
|
() => (typeof loadingSource === 'function' ? loadingSource() : loadingSource?.value),
|
|
(loading) => {
|
|
if (!loading) {
|
|
scheduleMeasureLayout();
|
|
setTimeout(scheduleMeasureLayout, 300);
|
|
}
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => (typeof detailSource === 'function' ? detailSource() : detailSource?.value),
|
|
(val) => rebuildHistory(val),
|
|
{ immediate: true, deep: true }
|
|
);
|
|
|
|
watch(activeIndex, (index) => {
|
|
stepScrollIntoView.value = 'hazard-step-' + index;
|
|
setTimeout(() => {
|
|
stepScrollIntoView.value = '';
|
|
}, 300);
|
|
});
|
|
|
|
watch(
|
|
() => historyList.value.length,
|
|
() => scheduleMeasureLayout()
|
|
);
|
|
|
|
onReady(() => {
|
|
scheduleMeasureLayout();
|
|
setTimeout(scheduleMeasureLayout, 300);
|
|
});
|
|
|
|
return {
|
|
historyList,
|
|
activeIndex,
|
|
contentScrollIntoView,
|
|
stepScrollIntoView,
|
|
scrollWithAnimation,
|
|
onContentScroll,
|
|
onScrollToLower,
|
|
scrollToNode,
|
|
scheduleMeasureLayout
|
|
};
|
|
}
|