隐患详情重新设计,检查计划重新设计成做题排查式
This commit is contained in:
164
components/hazardDetail/useHazardDetailScroll.js
Normal file
164
components/hazardDetail/useHazardDetailScroll.js
Normal file
@@ -0,0 +1,164 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user