|
@@ -19,7 +19,7 @@
|
|
|
<span class="term-name">{{ formatDate(t.startDate) }}</span>
|
|
<span class="term-name">{{ formatDate(t.startDate) }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div
|
|
<div
|
|
|
- v-for="(p, idx) in phenologyList"
|
|
|
|
|
|
|
+ v-for="(p, idx) in sortedPhenologyList"
|
|
|
:key="`phenology-${uniqueTimestamp}-${idx}`"
|
|
:key="`phenology-${uniqueTimestamp}-${idx}`"
|
|
|
class="phenology-bar"
|
|
class="phenology-bar"
|
|
|
>
|
|
>
|
|
@@ -135,7 +135,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { ref, nextTick, watch, onMounted, onUnmounted } from "vue";
|
|
|
|
|
|
|
+import { ref, computed, nextTick, watch, onMounted, onUnmounted } from "vue";
|
|
|
import { ElMessage } from "element-plus";
|
|
import { ElMessage } from "element-plus";
|
|
|
import { Empty, Popup } from "vant";
|
|
import { Empty, Popup } from "vant";
|
|
|
import { base_img_url2 } from "@/api/config";
|
|
import { base_img_url2 } from "@/api/config";
|
|
@@ -184,6 +184,14 @@ const emits = defineEmits(["row-click"]);
|
|
|
|
|
|
|
|
const solarTerms = ref([]);
|
|
const solarTerms = ref([]);
|
|
|
const phenologyList = ref([]);
|
|
const phenologyList = ref([]);
|
|
|
|
|
+// 按 progress 排序的物候期列表,用于渲染(与 calculatePhenologyPositions 中的排序保持一致)
|
|
|
|
|
+const sortedPhenologyList = computed(() => {
|
|
|
|
|
+ return [...phenologyList.value].sort((a, b) => {
|
|
|
|
|
+ const aProgress = Math.min(Number(a?.progress) || 0, Number(a?.progress2) || 0);
|
|
|
|
|
+ const bProgress = Math.min(Number(b?.progress) || 0, Number(b?.progress2) || 0);
|
|
|
|
|
+ return aProgress - bProgress;
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
// 由 phenologyList 每项的 startDate 生成,用于左侧时间轴展示(替代原 solarTerms 在此块的展示)
|
|
// 由 phenologyList 每项的 startDate 生成,用于左侧时间轴展示(替代原 solarTerms 在此块的展示)
|
|
|
const termDateList = ref([]);
|
|
const termDateList = ref([]);
|
|
|
const timelineContainerRef = ref(null);
|
|
const timelineContainerRef = ref(null);
|
|
@@ -192,6 +200,8 @@ const timelineListRef = ref(null);
|
|
|
const isInitialLoad = ref(true);
|
|
const isInitialLoad = ref(true);
|
|
|
// 存储timeline-list的实际渲染高度
|
|
// 存储timeline-list的实际渲染高度
|
|
|
const timelineListHeight = ref(0);
|
|
const timelineListHeight = ref(0);
|
|
|
|
|
+// 存储每个物候期的实际DOM位置(通过startDate作为key)
|
|
|
|
|
+const phenologyDomPositions = ref(new Map());
|
|
|
// 生成唯一的时间戳,用于确保key的唯一性
|
|
// 生成唯一的时间戳,用于确保key的唯一性
|
|
|
const uniqueTimestamp = ref(Date.now());
|
|
const uniqueTimestamp = ref(Date.now());
|
|
|
// ResizeObserver 实例,用于监听高度变化
|
|
// ResizeObserver 实例,用于监听高度变化
|
|
@@ -313,7 +323,7 @@ const handleImageClick = (fw) => {
|
|
|
|
|
|
|
|
// 获取下一个reproductive-item的phenologyName
|
|
// 获取下一个reproductive-item的phenologyName
|
|
|
const getNextPhenologyName = (currentPhenologyIdx, currentReproductiveIdx) => {
|
|
const getNextPhenologyName = (currentPhenologyIdx, currentReproductiveIdx) => {
|
|
|
- const currentPhenology = phenologyList.value[currentPhenologyIdx];
|
|
|
|
|
|
|
+ const currentPhenology = sortedPhenologyList.value[currentPhenologyIdx];
|
|
|
if (!currentPhenology || !Array.isArray(currentPhenology.reproductiveList)) {
|
|
if (!currentPhenology || !Array.isArray(currentPhenology.reproductiveList)) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
@@ -325,8 +335,8 @@ const getNextPhenologyName = (currentPhenologyIdx, currentReproductiveIdx) => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 如果当前reproductive-item是最后一个,获取下一个物候期的第一个reproductive-item
|
|
// 如果当前reproductive-item是最后一个,获取下一个物候期的第一个reproductive-item
|
|
|
- if (currentPhenologyIdx < phenologyList.value.length - 1) {
|
|
|
|
|
- const nextPhenology = phenologyList.value[currentPhenologyIdx + 1];
|
|
|
|
|
|
|
+ if (currentPhenologyIdx < sortedPhenologyList.value.length - 1) {
|
|
|
|
|
+ const nextPhenology = sortedPhenologyList.value[currentPhenologyIdx + 1];
|
|
|
if (
|
|
if (
|
|
|
nextPhenology &&
|
|
nextPhenology &&
|
|
|
Array.isArray(nextPhenology.reproductiveList) &&
|
|
Array.isArray(nextPhenology.reproductiveList) &&
|
|
@@ -421,24 +431,54 @@ const calculateTotalHeightByFarmWorks = () => {
|
|
|
return baseHeight || 100; // 至少返回100px,避免为0
|
|
return baseHeight || 100; // 至少返回100px,避免为0
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+// 更新物候期的实际DOM位置
|
|
|
|
|
+const updatePhenologyDomPositions = () => {
|
|
|
|
|
+ if (!timelineListRef.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ phenologyDomPositions.value.clear();
|
|
|
|
|
+ const phenologyBars = timelineListRef.value.querySelectorAll('.phenology-bar');
|
|
|
|
|
+
|
|
|
|
|
+ phenologyBars.forEach((bar, idx) => {
|
|
|
|
|
+ if (idx < sortedPhenologyList.value.length) {
|
|
|
|
|
+ const phenology = sortedPhenologyList.value[idx];
|
|
|
|
|
+ const startDate = phenology.startDate;
|
|
|
|
|
+ if (startDate) {
|
|
|
|
|
+ // 使用offsetTop获取相对于timeline-list的位置
|
|
|
|
|
+ const top = bar.offsetTop;
|
|
|
|
|
+ const height = bar.offsetHeight;
|
|
|
|
|
+ phenologyDomPositions.value.set(startDate, { top, height });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
const getTermStyle = (t, index) => {
|
|
const getTermStyle = (t, index) => {
|
|
|
// 优先使用实际测量的timeline-list高度,如果没有测量到则使用计算值作为后备
|
|
// 优先使用实际测量的timeline-list高度,如果没有测量到则使用计算值作为后备
|
|
|
const totalHeight = timelineListHeight.value > 0 ? timelineListHeight.value : calculateTotalHeightByFarmWorks();
|
|
const totalHeight = timelineListHeight.value > 0 ? timelineListHeight.value : calculateTotalHeightByFarmWorks();
|
|
|
// 左侧时间轴条数(来自 termDateList,即物候期 startDate 数量)
|
|
// 左侧时间轴条数(来自 termDateList,即物候期 startDate 数量)
|
|
|
const termCount = termDateList.value?.length || 1;
|
|
const termCount = termDateList.value?.length || 1;
|
|
|
|
|
|
|
|
- // 等分高度:总高度 / 节气数量
|
|
|
|
|
- const termHeight = totalHeight / termCount;
|
|
|
|
|
-
|
|
|
|
|
- // 计算top位置:索引 * 每个节气的高度
|
|
|
|
|
- const top = index * termHeight;
|
|
|
|
|
|
|
+ // 优先使用实际DOM位置
|
|
|
|
|
+ let top;
|
|
|
|
|
+ let termHeight;
|
|
|
|
|
+ const domPosition = phenologyDomPositions.value.get(t.startDate);
|
|
|
|
|
+
|
|
|
|
|
+ if (domPosition) {
|
|
|
|
|
+ // 使用物候期的实际DOM位置
|
|
|
|
|
+ top = domPosition.top;
|
|
|
|
|
+ termHeight = domPosition.height;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 后备方案:等分高度
|
|
|
|
|
+ termHeight = totalHeight / termCount;
|
|
|
|
|
+ top = index * termHeight;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
position: "absolute",
|
|
position: "absolute",
|
|
|
top: `${top}px`,
|
|
top: `${top}px`,
|
|
|
left: 0,
|
|
left: 0,
|
|
|
width: "32px",
|
|
width: "32px",
|
|
|
- height: `${termHeight}px`, // 高度等分,使用实际测量的高度
|
|
|
|
|
|
|
+ height: `${termHeight}px`,
|
|
|
display: "flex",
|
|
display: "flex",
|
|
|
alignItems: "center",
|
|
alignItems: "center",
|
|
|
};
|
|
};
|
|
@@ -622,6 +662,12 @@ const getFarmWorkPlan = () => {
|
|
|
timelineListRef.value.offsetHeight || timelineListRef.value.clientHeight;
|
|
timelineListRef.value.offsetHeight || timelineListRef.value.clientHeight;
|
|
|
if (height > 0) {
|
|
if (height > 0) {
|
|
|
timelineListHeight.value = height;
|
|
timelineListHeight.value = height;
|
|
|
|
|
+ // 更新物候期的实际DOM位置(延迟一下确保DOM完全渲染)
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ requestAnimationFrame(() => {
|
|
|
|
|
+ updatePhenologyDomPositions();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
// 如果是首次加载,滚动到当前季节对应的节气
|
|
// 如果是首次加载,滚动到当前季节对应的节气
|
|
|
if (isInitialLoad.value) {
|
|
if (isInitialLoad.value) {
|
|
@@ -641,6 +687,12 @@ const getFarmWorkPlan = () => {
|
|
|
timelineListRef.value.clientHeight;
|
|
timelineListRef.value.clientHeight;
|
|
|
if (height > 0) {
|
|
if (height > 0) {
|
|
|
timelineListHeight.value = height;
|
|
timelineListHeight.value = height;
|
|
|
|
|
+ // 更新物候期的实际DOM位置(延迟一下确保DOM完全渲染)
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ requestAnimationFrame(() => {
|
|
|
|
|
+ updatePhenologyDomPositions();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
const currentSeason = getCurrentSeason();
|
|
const currentSeason = getCurrentSeason();
|
|
@@ -892,6 +944,10 @@ const setupResizeObserver = () => {
|
|
|
const height = entry.contentRect.height;
|
|
const height = entry.contentRect.height;
|
|
|
if (height > 0 && height !== timelineListHeight.value) {
|
|
if (height > 0 && height !== timelineListHeight.value) {
|
|
|
timelineListHeight.value = height;
|
|
timelineListHeight.value = height;
|
|
|
|
|
+ // 高度变化时,更新物候期的实际DOM位置
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ updatePhenologyDomPositions();
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
@@ -924,6 +980,8 @@ watch(
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
|
setupResizeObserver();
|
|
setupResizeObserver();
|
|
|
|
|
+ // 物候期列表变化时,更新DOM位置
|
|
|
|
|
+ updatePhenologyDomPositions();
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -1198,7 +1256,7 @@ watch(
|
|
|
display: inline-block;
|
|
display: inline-block;
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
min-height: 20px;
|
|
min-height: 20px;
|
|
|
- line-height: 26px;
|
|
|
|
|
|
|
+ line-height: 20px;
|
|
|
background: #fff;
|
|
background: #fff;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
}
|
|
}
|