darwArea.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <template>
  2. <div class="edit-map">
  3. <custom-header :name="viewOnly ? '查看区域' : '勾选地块'"></custom-header>
  4. <div class="variety-tabs" v-if="varietyTabs.length > 0">
  5. <div v-for="(v, index) in varietyTabs" :key="index" class="variety-tab"
  6. :class="{ 'variety-tab--active': activeVariety === index }" @click="handleVarietyClick(v, index)">
  7. {{ v.regionName }}
  8. </div>
  9. </div>
  10. <div class="edit-map-content">
  11. <div class="edit-map-tip" v-if="!viewOnly">操作提示:拖动圆点,即可调整地块边界</div>
  12. <div class="map-container" ref="mapContainer"></div>
  13. <div class="edit-map-footer">
  14. <div class="footer-back" @click="goBack">
  15. <img class="back-icon" src="@/assets/img/home/go-back.png" alt="" />
  16. </div>
  17. <div class="edit-map-footer-btn" :class="{ 'confirm-btn-box': viewOnly }">
  18. <template v-if="!viewOnly">
  19. <div class="btn-reset" @click="resetPolygon">重置区域</div>
  20. <div class="btn-confirm" @click="confirmArea">确认</div>
  21. </template>
  22. <div v-else class="btn-confirm" @click="handleEditRegion">编辑区域</div>
  23. </div>
  24. </div>
  25. </div>
  26. <save-region-success-popup v-model:show="showSuccessPopup"
  27. :title="`${activeVarietyName} 区域已保存成功`"
  28. :has-next="varietyTabs.length > 0 && activeVariety < varietyTabs.length - 1" @know="handleKnow"
  29. @next="handleSelectNextVariety" />
  30. <tip-popup v-model:show="showTipPopup" type="success" text="您的农情报告已生成" text2="请查看" buttonText="点击查看"
  31. @confirm="handleTipConfirm" />
  32. </div>
  33. </template>
  34. <script setup>
  35. import customHeader from "@/components/customHeader.vue";
  36. import SaveRegionSuccessPopup from "@/components/popup/saveRegionSuccessPopup.vue";
  37. import { ref, computed, onActivated, onDeactivated, nextTick } from "vue";
  38. import { useStore } from "vuex";
  39. import DrawRegionMap from "../../interactionList/map/drawRegionMap.js";
  40. import { useRouter, useRoute } from "vue-router";
  41. import { convertPointToArray } from "@/utils/index";
  42. import { ElMessage, ElMessageBox } from "element-plus";
  43. import TipPopup from "@/components/popup/tipPopup.vue";
  44. const store = useStore();
  45. const router = useRouter();
  46. const route = useRoute();
  47. const mapContainer = ref(null);
  48. const drawRegionMap = new DrawRegionMap();
  49. const showSuccessPopup = ref(false);
  50. // 是否从第一个品种开始的引导流程(需要全部品种都确认后再退出)
  51. const isGuidedFlow = ref(false);
  52. // 每个品种(tab)对应的地块数据草稿:key 为 tab index,value 为 { geomArr, geom }
  53. const regionsDraftByIndex = ref({});
  54. const submitting = ref(false);
  55. const type = ref(null);
  56. const varietyTabs = ref([]);
  57. const activeVariety = ref(0);
  58. const regionGeom = ref(null);
  59. const handleVarietyClick = (tab, index) => {
  60. activeVariety.value = index;
  61. regionGeom.value = tab.geom;
  62. // 每次切换/重绘地块前先清空,避免多边形叠加残留
  63. if (drawRegionMap.kmap?.polygonLayer?.source) {
  64. drawRegionMap.kmap.polygonLayer.source.clear();
  65. }
  66. if (tab.geom?.length) {
  67. setTimeout(() => {
  68. drawRegionMap.setAreaGeometry([tab.geom], true);
  69. }, 50);
  70. }
  71. };
  72. const activeVarietyName = computed(() => {
  73. const current = varietyTabs.value[activeVariety.value];
  74. return current?.regionName || "";
  75. });
  76. const viewOnly = computed(() => route.query.type === "viewOnly");
  77. const point = ref(null);
  78. async function fetchFarmSubjectDetail() {
  79. const subjectId = route.query?.subjectId;
  80. if (!subjectId) return;
  81. const { data } = await VE_API.basic_farm.fetchFarmSubjectDetail({ subjectId });
  82. if (data?.regionList?.length) {
  83. if(route.query?.varietyId) {
  84. activeVariety.value = data?.regionList.findIndex(item => item.typeId == route.query?.varietyId);
  85. }
  86. point.value = data.farmLocation;
  87. varietyTabs.value = data.regionList;
  88. handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
  89. }
  90. }
  91. onActivated(async () => {
  92. activeVariety.value = 0;
  93. // keep-alive 场景下再次进入前先销毁旧地图实例,避免重复 init 导致图层状态错位
  94. if (drawRegionMap.kmap) {
  95. drawRegionMap.abortOngoingDrawSketch?.();
  96. drawRegionMap.destroyMap?.();
  97. }
  98. type.value = route.query.type;
  99. const editable = !viewOnly.value;
  100. await fetchFarmSubjectDetail();
  101. drawRegionMap.initMap(point.value, mapContainer.value, editable, true, true);
  102. // 从编辑态进入仅查看时,需重新初始化为不可编辑
  103. if (viewOnly.value && drawRegionMap.kmap && drawRegionMap.editable) {
  104. drawRegionMap.destroyMap();
  105. drawRegionMap.initMap(point.value, mapContainer.value, false, true, false);
  106. }
  107. // 从仅查看进入勾画(编辑)时,需重新初始化为可编辑
  108. if (!viewOnly.value && drawRegionMap.kmap && !drawRegionMap.editable) {
  109. drawRegionMap.destroyMap();
  110. drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
  111. }
  112. // 查看模式下已通过 fitAllRegions 适配;编辑模式再设置地图中心
  113. if (!viewOnly.value) {
  114. drawRegionMap.setMapPosition(convertPointToArray(point.value));
  115. }
  116. });
  117. onDeactivated(() => {
  118. activeVariety.value = 0;
  119. // 离开页面时中止未完成绘制并销毁地图,确保下次进入是干净实例
  120. drawRegionMap.abortOngoingDrawSketch?.();
  121. drawRegionMap.destroyMap?.();
  122. })
  123. const goBack = () => {
  124. // drawRegionMap.clearLayer()
  125. router.back()
  126. };
  127. const resetPolygon = () => {
  128. ElMessageBox.confirm(
  129. "确认要重置当前地块吗?重置后将恢复到进入页面时的区域形状。",
  130. "重置确认",
  131. {
  132. confirmButtonText: "确认重置",
  133. cancelButtonText: "取消",
  134. type: "warning",
  135. }
  136. )
  137. .then(() => {
  138. // 先取消进行中的勾画草图(未完成时线条在 Draw 的 overlay 上,清 source 去不掉)
  139. drawRegionMap.abortOngoingDrawSketch();
  140. // 只清空当前可编辑多边形,不影响只读状态图层
  141. if (drawRegionMap.kmap && drawRegionMap.kmap.polygonLayer && drawRegionMap.kmap.polygonLayer.source) {
  142. drawRegionMap.kmap.polygonLayer.source.clear();
  143. }
  144. // 使用进入页面时路由上带的 polygonData 作为“初始形状”进行回显
  145. const polygonDataStr = route.query.polygonData;
  146. if (polygonDataStr) {
  147. try {
  148. const parsed = JSON.parse(polygonDataStr);
  149. if (parsed && Array.isArray(parsed.geometryArr) && parsed.geometryArr.length) {
  150. drawRegionMap.setAreaGeometry(parsed.geometryArr);
  151. }
  152. } catch (_) {
  153. // 解析失败则不做处理,仅相当于清空重画
  154. }
  155. }
  156. ElMessage.success("地块已重置");
  157. })
  158. .catch(() => {
  159. // 用户取消重置,不做任何操作
  160. });
  161. };
  162. const buildRegionsPayload = () => {
  163. const regions = [];
  164. varietyTabs.value.forEach((tab, index) => {
  165. const draft = regionsDraftByIndex.value[index];
  166. if (!draft?.geom) return;
  167. regions.push({
  168. regionId: tab.regionId,
  169. typeId: tab.typeId,
  170. regionName: tab.regionName,
  171. geom: draft.geom,
  172. });
  173. });
  174. return regions;
  175. };
  176. const submitRegions = async () => {
  177. if (submitting.value) return;
  178. const regions = buildRegionsPayload();
  179. if (regions.length === 0) {
  180. ElMessage.warning("请先勾画地块后再确认");
  181. return;
  182. }
  183. submitting.value = true;
  184. try {
  185. const res = await VE_API.basic_farm.saveBasicFarmInfoByExpertV3({
  186. id: route.query.subjectId,
  187. expertMiniUserId: '81881',
  188. regions,
  189. });
  190. if (res?.code === 0) {
  191. return true;
  192. }
  193. ElMessage.error(res?.msg || "保存失败");
  194. return false;
  195. } catch (e) {
  196. ElMessage.error("保存失败");
  197. return false;
  198. } finally {
  199. submitting.value = false;
  200. }
  201. };
  202. const confirmArea = async () => {
  203. // 先把当前品种的地块保存到草稿
  204. const polygonData = drawRegionMap.getAreaGeometry?.();
  205. const geometryArr = polygonData?.geometryArr;
  206. if (!Array.isArray(geometryArr) || geometryArr.length === 0) {
  207. ElMessage.warning("请先勾画地块后再确认");
  208. return;
  209. }
  210. // geom 后端一般期望 string;若勾画多个,则序列化为 JSON 数组
  211. const geom = geometryArr.length === 1 ? geometryArr[0] : JSON.stringify(geometryArr);
  212. regionsDraftByIndex.value[activeVariety.value] = { geomArr: geometryArr, geom };
  213. // 任意品种确认后都进入引导流程(中间品种也可继续“下一步”)
  214. isGuidedFlow.value = true;
  215. // 每次确认都提交接口(携带目前已确认过的所有品种 regions)
  216. const ok = await submitRegions();
  217. await VE_API.basic_farm.updateLastViewTime({
  218. regionId: varietyTabs.value[activeVariety.value].regionId,
  219. });
  220. fetchFarmSubjectDetail()
  221. if (!ok) return;
  222. // 引导流程:弹成功提示,由弹窗控制“下一步/完成”
  223. if (isGuidedFlow.value) {
  224. showSuccessPopup.value = true;
  225. return;
  226. }
  227. // 非引导流程:确认后返回上一页
  228. router.back();
  229. };
  230. const handleKnow = () => {
  231. showSuccessPopup.value = false;
  232. if (route.query.showTipPopup) {
  233. showTipPopup.value = true;
  234. return;
  235. }
  236. if(route.query.targetUrl) {
  237. router.replace(route.query.targetUrl);
  238. return;
  239. }
  240. router.back();
  241. };
  242. const handleSelectNextVariety = () => {
  243. if (varietyTabs.value.length === 0) {
  244. handleKnow();
  245. return;
  246. }
  247. const nextIndex = activeVariety.value + 1;
  248. if (nextIndex < varietyTabs.value.length) {
  249. const nextTab = varietyTabs.value[nextIndex];
  250. // 勾画当前品种完成后,先用统一的 clearLayer 方法重置图层,再切换到下一个品种
  251. if (drawRegionMap && typeof drawRegionMap.clearLayer === "function") {
  252. drawRegionMap.clearLayer();
  253. }
  254. handleVarietyClick(nextTab, nextIndex);
  255. showSuccessPopup.value = false;
  256. } else {
  257. // 如果已经是最后一个品种,则直接返回上一页
  258. handleKnow();
  259. }
  260. };
  261. const handleEditRegion = () => {
  262. // 从查看态切换到可勾画编辑态:移除查看标记并重建地图(editable=true)
  263. const nextQuery = { ...route.query };
  264. delete nextQuery.type;
  265. delete nextQuery.viewOnly;
  266. router.replace({ query: nextQuery });
  267. nextTick(() => {
  268. if (drawRegionMap.kmap) {
  269. drawRegionMap.destroyMap();
  270. }
  271. drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
  272. if (varietyTabs.value.length && regionGeom.value.length) {
  273. drawRegionMap.setAreaGeometry([regionGeom.value],true);
  274. }
  275. // 切到编辑态后,统一自适应到所有区域,避免画面偏移
  276. if (drawRegionMap.fitAllRegions) {
  277. drawRegionMap.fitAllRegions();
  278. }
  279. });
  280. };
  281. const showTipPopup = ref(false);
  282. const handleTipConfirm = () => {
  283. router.push("/growth_report?hideInteraction=true");
  284. }
  285. </script>
  286. <style lang="scss" scoped>
  287. .edit-map {
  288. width: 100%;
  289. height: 100vh;
  290. overflow: hidden;
  291. .variety-tabs {
  292. display: flex;
  293. align-items: center;
  294. gap: 8px;
  295. padding: 10px 12px 0;
  296. overflow-x: auto;
  297. overflow-y: hidden;
  298. flex-wrap: nowrap;
  299. -webkit-overflow-scrolling: touch;
  300. .variety-tab {
  301. padding: 4px 12px;
  302. border-radius: 2px;
  303. color: #767676;
  304. background: #fff;
  305. border: 0.5px solid rgba(174, 174, 174, 0.8);
  306. white-space: nowrap;
  307. }
  308. .variety-tab--active {
  309. background: #2199F8;
  310. color: #ffffff;
  311. }
  312. }
  313. .edit-map-content {
  314. width: 100%;
  315. margin-top: 10px;
  316. height: calc(100% - 58px);
  317. position: relative;
  318. .edit-map-tip {
  319. position: absolute;
  320. top: 23px;
  321. left: calc(50% - 256px / 2);
  322. z-index: 1;
  323. font-size: 12px;
  324. color: #fff;
  325. padding: 9px 20px;
  326. background: rgba(0, 0, 0, 0.5);
  327. border-radius: 20px;
  328. }
  329. .map-container {
  330. width: 100%;
  331. height: 100%;
  332. }
  333. .edit-map-footer {
  334. position: absolute;
  335. bottom: 110px;
  336. left: 12px;
  337. width: calc(100% - 24px);
  338. display: flex;
  339. flex-direction: column;
  340. align-items: flex-end;
  341. .footer-back {
  342. padding: 6px 7px 9px;
  343. background: #fff;
  344. border-radius: 8px;
  345. margin-bottom: 12px;
  346. .back-icon {
  347. width: 20px;
  348. height: 18px;
  349. }
  350. }
  351. .edit-map-footer-btn {
  352. display: flex;
  353. justify-content: space-between;
  354. align-items: center;
  355. width: 100%;
  356. position: fixed;
  357. bottom: 0;
  358. left: 0;
  359. background: #fff;
  360. padding: 10px 12px 20px 12px;
  361. box-sizing: border-box;
  362. &.confirm-btn-box {
  363. justify-content: center;
  364. }
  365. div {
  366. width: 100px;
  367. text-align: center;
  368. color: #666666;
  369. font-size: 14px;
  370. padding: 8px 0;
  371. border-radius: 25px;
  372. border: 0.5px solid rgba(153, 153, 153, 0.5);
  373. }
  374. .btn-confirm {
  375. background: #2199F8;
  376. color: #fff;
  377. border: 1px solid #2199F8;
  378. }
  379. }
  380. }
  381. }
  382. }
  383. </style>