index.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <template>
  2. <custom-header v-if="isHeaderShow" :name="t('agriRecord.farmDetail')"></custom-header>
  3. <div class="monitor-index" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
  4. <!-- 天气遮罩 -->
  5. <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
  6. <!-- 头部 -->
  7. <div v-show="activeGardenTab === 'current'" class="agri-record-header">
  8. <weather-info ref="weatherInfoRef" :hasWeather="false" from="agri_record" class="weather-info"
  9. @weatherExpanded="weatherExpanded" @changeGarden="changeGarden" @changeGardenTab="changeGardenTab"
  10. @reportTabClick="handleReportTabClick" @farmInfoMaintain="handleFarmInfoMaintain" :isGarden="true"
  11. :gardenId="defaultGardenId" />
  12. </div>
  13. <!-- 农场列表 -->
  14. <div v-show="activeGardenTab === 'list'">
  15. <garden-list ref="gardenListRef" :garden-id="selectedGardenId" @loaded="handleGardenLoaded"
  16. @selectGarden="handleGardenSelected" />
  17. </div>
  18. <!-- 作物档案:Tab 固定顶部,仅农事列表区域滚动 -->
  19. <div class="archives-time-line" v-show="activeGardenTab === 'current'">
  20. <div class="archives-time-line-fixed">
  21. <div class="farm-work-tabs">
  22. <div
  23. v-for="tab in farmWorkTabs"
  24. :key="tab.key"
  25. class="farm-work-tabs__item"
  26. :class="{ 'farm-work-tabs__item--active': activeFarmWorkTab === tab.key }"
  27. @click="handleFarmWorkTabClick(tab.key)"
  28. >
  29. {{ t(tab.labelKey) }}
  30. </div>
  31. </div>
  32. </div>
  33. <div ref="archivesScrollAreaRef" class="archives-time-line-content">
  34. <archives-farm-time-line :farmId="gardenId" :activeFarmWorkTab="activeFarmWorkTab"></archives-farm-time-line>
  35. </div>
  36. </div>
  37. </div>
  38. </template>
  39. <script setup>
  40. import { useI18n } from "@/i18n";
  41. const { t } = useI18n();
  42. import customHeader from "@/components/customHeader.vue";
  43. import { ref, computed, onActivated, onDeactivated, watch, nextTick } from "vue";
  44. import { useStore } from "vuex";
  45. import weatherInfo from "@/components/weatherInfo.vue";
  46. import { useRoute, useRouter, onBeforeRouteLeave } from "vue-router";
  47. import ArchivesFarmTimeLine from "@/components/pageComponents/ArchivesFarmTimeLine.vue";
  48. import gardenList from "@/components/gardenList.vue";
  49. const route = useRoute();
  50. const router = useRouter();
  51. const archivesScrollAreaRef = ref(null);
  52. const getArchivesOuterScrollKey = () =>
  53. `agriRecordArchivesOuterScroll:${gardenId?.value ?? "none"}:${route.path}`;
  54. const saveArchivesOuterScrollTop = () => {
  55. if (!archivesScrollAreaRef.value || activeGardenTab.value !== "current") return;
  56. const scrollTop = archivesScrollAreaRef.value.scrollTop || 0;
  57. sessionStorage.setItem(getArchivesOuterScrollKey(), String(scrollTop));
  58. };
  59. const restoreArchivesOuterScrollTop = () => {
  60. if (!archivesScrollAreaRef.value || activeGardenTab.value !== "current") return false;
  61. const raw = sessionStorage.getItem(getArchivesOuterScrollKey());
  62. if (raw == null) return false;
  63. const scrollTop = Number(raw);
  64. if (Number.isNaN(scrollTop)) return false;
  65. const el = archivesScrollAreaRef.value;
  66. const maxScrollTop = Math.max(0, (el.scrollHeight || 0) - (el.clientHeight || 0));
  67. el.scrollTop = Math.min(scrollTop, maxScrollTop);
  68. return true;
  69. };
  70. const restoreArchivesOuterScrollTopWithRetry = (retryCount = 6) => {
  71. const ok = restoreArchivesOuterScrollTop();
  72. if (ok || retryCount <= 0) return;
  73. setTimeout(() => restoreArchivesOuterScrollTopWithRetry(retryCount - 1), 50);
  74. };
  75. const defaultGardenId = ref(null);
  76. const selectedGardenId = ref(null);
  77. const gardenListRef = ref(null);
  78. const activeGardenTab = ref('current');
  79. const activeFarmWorkTab = ref('0');
  80. const farmWorkTabs = [
  81. { key: '0', labelKey: 'agriRecord.allFarmWork' },
  82. { key: '1', labelKey: 'agriRecord.traceFarmWork' },
  83. ];
  84. const handleFarmWorkTabClick = (tab) => {
  85. activeFarmWorkTab.value = tab;
  86. };
  87. const changeGardenTab = (tab) => {
  88. activeGardenTab.value = tab;
  89. };
  90. const currentFarmName = ref("");
  91. const currentFarmVariety = ref(null);
  92. const handleReportTabClick = (item) => {
  93. if (item.key === "historyRisk") {
  94. router.push(
  95. `/history_risk_report?farmVariety=${currentFarmVariety.value ?? ""}&currentFarmName=${currentFarmName.value ?? ""}`
  96. );
  97. }
  98. };
  99. const handleFarmInfoMaintain = (farmId) => {
  100. router.push(`/create_farm?type=edit&farmId=${farmId}&from=agri_record`);
  101. };
  102. const handleGardenLoaded = ({ hasFarm }) => {
  103. weatherInfoRef.value?.setGardenLoaded?.(hasFarm);
  104. };
  105. const handleGardenSelected = (garden) => {
  106. selectedGardenId.value = garden?.id ?? null;
  107. weatherInfoRef.value?.setSelectedGarden?.(garden);
  108. };
  109. const isHeaderShow = ref(false);
  110. const weatherInfoRef = ref(null);
  111. onActivated(() => {
  112. // 用来接收我的农场跳转过来的农场详情逻辑
  113. if (route.query.isHeaderShow) {
  114. isHeaderShow.value = true;
  115. defaultGardenId.value = route.query.farmId;
  116. }
  117. const savedFarmId = localStorage.getItem("selectedFarmId");
  118. selectedGardenId.value = savedFarmId ? Number(savedFarmId) : null;
  119. gardenListRef.value?.refreshFarmList?.();
  120. nextTick(() => {
  121. requestAnimationFrame(() => {
  122. restoreArchivesOuterScrollTopWithRetry();
  123. });
  124. });
  125. });
  126. onDeactivated(() => {
  127. saveArchivesOuterScrollTop();
  128. });
  129. onBeforeRouteLeave(() => {
  130. saveArchivesOuterScrollTop();
  131. });
  132. watch(activeGardenTab, (val, oldVal) => {
  133. if (oldVal === "current" && val !== "current") {
  134. saveArchivesOuterScrollTop();
  135. }
  136. if (val === "current") {
  137. nextTick(() => {
  138. requestAnimationFrame(() => {
  139. restoreArchivesOuterScrollTopWithRetry();
  140. });
  141. });
  142. }
  143. });
  144. const store = useStore();
  145. const tabBarHeight = computed(() => store.state.home.tabBarHeight);
  146. const isExpanded = ref(false);
  147. const weatherExpanded = (isExpandedValue) => {
  148. isExpanded.value = isExpandedValue;
  149. };
  150. // 点击遮罩时收起天气
  151. const handleMaskClick = () => {
  152. if (weatherInfoRef.value && weatherInfoRef.value.toggleExpand) {
  153. weatherInfoRef.value.toggleExpand();
  154. }
  155. };
  156. const gardenId = ref(store.state.home.gardenId);
  157. const changeGarden = (data) => {
  158. if (!data?.id) return;
  159. gardenId.value = data.id;
  160. selectedGardenId.value = data.id;
  161. currentFarmName.value = data.name ?? "";
  162. currentFarmVariety.value = data.farm_variety ?? null;
  163. store.commit("home/SET_GARDEN_ID", data.id);
  164. };
  165. </script>
  166. <style scoped lang="scss">
  167. .monitor-index {
  168. width: 100%;
  169. height: 100%;
  170. box-sizing: border-box;
  171. background: linear-gradient(180deg, #2199F8 8%, #F5F5F5 19%, #F5F5F5 73%, #F5F5F5 100%);
  172. .weather-mask {
  173. position: fixed;
  174. top: 0;
  175. left: 0;
  176. width: 100%;
  177. height: 100%;
  178. background-color: rgba(0, 0, 0, 0.52);
  179. z-index: 11;
  180. }
  181. .agri-record-header {
  182. position: absolute;
  183. z-index: 12;
  184. left: 10px;
  185. top: 12px;
  186. width: calc(100% - 20px);
  187. transform: translateZ(0);
  188. .weather-info {
  189. width: 100%;
  190. position: relative;
  191. left: auto;
  192. top: auto;
  193. :deep(.garden-tabs) {
  194. .garden-item.left-item.active .current-name {
  195. color: #2199F8;
  196. font-weight: 600;
  197. }
  198. }
  199. }
  200. }
  201. .archives-time-line {
  202. position: relative;
  203. height: 100%;
  204. padding: 12px;
  205. padding-top: 100px;
  206. display: flex;
  207. flex-direction: column;
  208. min-height: 0;
  209. overflow: hidden;
  210. box-sizing: border-box;
  211. .archives-time-line-fixed {
  212. flex-shrink: 0;
  213. }
  214. .farm-work-tabs {
  215. display: flex;
  216. gap: 8px;
  217. margin-top: 10px;
  218. &__item {
  219. flex: 1;
  220. padding: 5px 0;
  221. border-radius: 2px;
  222. text-align: center;
  223. background: #EFEFEF;
  224. color: #858585;
  225. &--active {
  226. background: #2199f8;
  227. color: #ffffff;
  228. }
  229. }
  230. }
  231. .archives-time-line-content {
  232. margin-top: 10px;
  233. flex: 1;
  234. min-height: 0;
  235. overflow-y: auto;
  236. -webkit-overflow-scrolling: touch;
  237. background: #fff;
  238. border-radius: 8px;
  239. padding: 10px;
  240. box-sizing: border-box;
  241. :deep(.timeline-container) {
  242. height: auto;
  243. overflow: visible;
  244. }
  245. }
  246. }
  247. }
  248. </style>