darwArea.vue 13 KB

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