index.vue 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296
  1. <template>
  2. <div class="create-farm">
  3. <custom-header :name="paramsType === 'client' ? '新增用户' : paramsType === 'edit' ? '编辑农场' : '创建农场'"
  4. :isGoBack="true" @goback="backgToHome"></custom-header>
  5. <!-- 地图 -->
  6. <div class="map-container" ref="mapContainer"></div>
  7. <div class="farm-content">
  8. <div class="top-mask"></div>
  9. <div class="farm-filter">
  10. <el-select v-model="locationVal" filterable remote reserve-keyword placeholder="搜索位置"
  11. :remote-method="remoteMethod" :loading="loading" @change="handleSearchRes"
  12. popper-class="location-search-popper">
  13. <el-option v-for="(item, index) in locationOptions.list" :key="index" :label="item.title"
  14. :value="{ value: item.point, item }">
  15. <span>{{ item.title }}</span>
  16. <span class="sub-title">{{ item.province }}{{ item.city }}{{ item.district || '' }}</span>
  17. </el-option>
  18. </el-select>
  19. </div>
  20. <!-- 创建 -->
  21. <div class="create-wrap">
  22. <div class="create-box">
  23. <div class="box-content">
  24. <div class="create-title">
  25. <img class="title-icon" src="@/assets/img/home/create-icon.png" alt="" />
  26. {{ paramsType === "client" ? "新增用户" : paramsType === "edit" ? "编辑农场" : "创建农场" }}
  27. </div>
  28. <div class="create-content">
  29. <div class="create-from">
  30. <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="demo-ruleForm">
  31. <el-form-item label="农场位置" prop="address">
  32. <div class="position-wrap">
  33. <el-input placeholder="农场位置" readonly v-model="ruleForm.address"
  34. autocomplete="off" />
  35. <!-- <div class="draw-btn" @click="toSubPage">
  36. {{ hasDefaultPolygon ? "点击勾选地块" : "新增地块" }}
  37. </div> -->
  38. </div>
  39. </el-form-item>
  40. <el-form-item label="种植作物" prop="speciesItem">
  41. <div class="select-wrap specie-wrap">
  42. <el-select v-model="ruleForm.speciesItem" class="select-item specie-select"
  43. multiple collapse-tags placeholder="品类(可多选)" @change="changeSpecie">
  44. <el-option v-for="(item, index) in specieList" :key="index"
  45. :label="item.name" :value="{ value: item.id, ...item }" />
  46. </el-select>
  47. </div>
  48. </el-form-item>
  49. <el-form-item label="联系人" prop="fzr">
  50. <div class="area-box">
  51. <el-input placeholder="请输入联系人姓名" v-model="ruleForm.fzr" autocomplete="off"
  52. style="width: fit-content" />
  53. </div>
  54. </el-form-item>
  55. <el-form-item label="联系电话" prop="tel">
  56. <div class="area-box">
  57. <el-input placeholder="请输入联系人电话" type="number" v-model="ruleForm.tel" autocomplete="off"
  58. style="width: fit-content" />
  59. </div>
  60. </el-form-item>
  61. <el-form-item label="农场面积" prop="mu">
  62. <div class="area-box">
  63. <el-input :placeholder="isFromEditMap ? '勾选地块获得农场面积' : '请输入农场的真实面积'
  64. " v-model="ruleForm.mu" :readonly="isFromEditMap" type="text"
  65. autocomplete="off" style="width: fit-content" @input="handleMianjiInput"
  66. @keypress="handleMianjiKeypress" />
  67. <div class="unit">亩</div>
  68. </div>
  69. </el-form-item>
  70. <el-form-item label="农场名称" prop="name">
  71. <el-input placeholder="请输入您的农场名称" v-model="ruleForm.name" autocomplete="off"
  72. @input="handleFarmNameInput" />
  73. </el-form-item>
  74. <el-form-item label="基地类别" prop="baseType" class="select-wrap client-wrap">
  75. <el-select placeholder="请选择" v-model="ruleForm.baseType" autocomplete="off">
  76. <el-option v-for="(item, index) in baseTypeList" :key="index"
  77. :label="item.name" :value="item.id" />
  78. </el-select>
  79. </el-form-item>
  80. </el-form>
  81. </div>
  82. <div class="create-btn">
  83. <div class="btn-item sencond-btn" @click="resetForm(ruleFormRef)">取消</div>
  84. <div class="btn-item primary-btn" @click="submitForm(ruleFormRef)">
  85. {{
  86. paramsType === "client"
  87. ? "添加"
  88. : paramsType === "edit"
  89. ? "确认修改"
  90. : "立即创建"
  91. }}
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. </div>
  99. </div>
  100. <!-- 农情采集成功弹窗 -->
  101. <tip-popup v-model:show="showSuccessPopup" type="success" text="农情采集成功" @confirm="handlePopupConfirm"
  102. @handleClickOverlay="handlePopupConfirm" />
  103. </template>
  104. <script setup>
  105. import customHeader from "@/components/customHeader.vue";
  106. import IndexMap from "./map/index.js";
  107. import { useRoute, useRouter } from "vue-router";
  108. import { mapLocation } from "./map/index.js";
  109. import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated } from "vue";
  110. import { useStore } from "vuex";
  111. import { convertPointToArray } from "@/utils/index";
  112. import { ElMessage } from "element-plus";
  113. import { Checkbox } from "vant";
  114. import tipPopup from "@/components/popup/tipPopup.vue";
  115. import wx from "weixin-js-sdk";
  116. import { transformFromGCJToWGS, transformFromWGSToGCJ } from "@/utils/WSCoordinate.js";
  117. const route = useRoute();
  118. const router = useRouter();
  119. const store = useStore();
  120. const indexMap = new IndexMap();
  121. const mapContainer = ref(null);
  122. // 农情采集成功弹窗
  123. const showSuccessPopup = ref(false);
  124. const handlePopupConfirm = () => {
  125. showSuccessPopup.value = false;
  126. router.replace(`/home`);
  127. };
  128. // 标记是否从编辑地图页面确认返回
  129. const isFromEditMap = ref(false);
  130. // 标记是否已创建默认地块
  131. const hasDefaultPolygon = ref(false);
  132. // 标记用户是否手动修改过农场名称
  133. const isFarmNameManuallyModified = ref(false);
  134. /**
  135. * 根据中心点生成指定边长的正方形地块WKT
  136. * @param {Array} center - 中心点坐标 [lng, lat]
  137. * @param {Number} sideLength - 正方形边长(米)
  138. * @returns {Object} 包含WKT字符串和面积(亩)的对象
  139. */
  140. function generateSquarePolygonBySideLength(center, sideLength) {
  141. // 确保输入是数字类型
  142. const lng = parseFloat(center[0]);
  143. const lat = parseFloat(center[1]);
  144. // 半边长(米)
  145. const halfSide = sideLength / 2;
  146. // 纬度方向:1度约等于111000米
  147. const latDelta = halfSide / 111000;
  148. // 经度方向:需要根据纬度调整,1度约等于111000 * cos(lat)米
  149. const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
  150. // 计算四个顶点(逆时针顺序)
  151. const topLeft = [lng - lngDelta, lat + latDelta];
  152. const topRight = [lng + lngDelta, lat + latDelta];
  153. const bottomRight = [lng + lngDelta, lat - latDelta];
  154. const bottomLeft = [lng - lngDelta, lat - latDelta];
  155. // 生成MULTIPOLYGON格式的WKT(需要闭合,所以第一个点要重复)
  156. // 明确使用join来格式化坐标点
  157. const formatPoint = (point) => `${point[0]} ${point[1]}`;
  158. const coordinates = [topLeft, topRight, bottomRight, bottomLeft, topLeft].map(formatPoint).join(", ");
  159. // 注意:MULTIPOLYGON 和括号之间需要有一个空格
  160. const wkt = `MULTIPOLYGON (((${coordinates})))`;
  161. // 计算面积(亩): 边长200米 = 40,000平方米 ≈ 60亩
  162. const areaInSquareMeters = sideLength * sideLength;
  163. const areaInMu = (areaInSquareMeters / 666.67).toFixed(2);
  164. return { wkt, area: areaInMu };
  165. }
  166. /**
  167. * 根据中心点和亩数生成正方形地块WKT
  168. * @param {Array} center - 中心点坐标 [lng, lat]
  169. * @param {Number} mu - 面积(亩)
  170. * @returns {Object} 包含WKT字符串和面积(亩)的对象
  171. */
  172. function generateSquarePolygonByMu(center, mu) {
  173. // 确保输入是数字类型
  174. const lng = parseFloat(center[0]);
  175. const lat = parseFloat(center[1]);
  176. // 1亩 ≈ 666.67平方米
  177. const areaInSquareMeters = mu * 666.67;
  178. // 正方形边长(米)
  179. const sideLength = Math.sqrt(areaInSquareMeters);
  180. // 半边长(米)
  181. const halfSide = sideLength / 2;
  182. // 纬度方向:1度约等于111000米
  183. const latDelta = halfSide / 111000;
  184. // 经度方向:需要根据纬度调整,1度约等于111000 * cos(lat)米
  185. const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
  186. // 计算四个顶点(逆时针顺序)
  187. const topLeft = [lng - lngDelta, lat + latDelta];
  188. const topRight = [lng + lngDelta, lat + latDelta];
  189. const bottomRight = [lng + lngDelta, lat - latDelta];
  190. const bottomLeft = [lng - lngDelta, lat - latDelta];
  191. // 生成MULTIPOLYGON格式的WKT(需要闭合,所以第一个点要重复)
  192. const formatPoint = (point) => `${point[0]} ${point[1]}`;
  193. const coordinates = [topLeft, topRight, bottomRight, bottomLeft, topLeft].map(formatPoint).join(", ");
  194. // 注意:MULTIPOLYGON 和括号之间需要有一个空格
  195. const wkt = `MULTIPOLYGON (((${coordinates})))`;
  196. return { wkt, area: parseFloat(mu).toFixed(2) };
  197. }
  198. onMounted(() => {
  199. // 清除上一次的地块数据,确保每次进入都是全新状态
  200. store.commit("home/SET_FARM_POLYGON", null);
  201. isFromEditMap.value = false; // 初始化时可以手动输入
  202. hasDefaultPolygon.value = false; // 初始化时没有默认地块
  203. centerPoint.value = store.state.home.miniUserLocationPoint;
  204. // const arr = convertPointToArray(centerPoint.value);
  205. // getLocationName(`${arr[1]},${arr[0]}`);
  206. indexMap.initMap(centerPoint.value, mapContainer.value);
  207. // 不再初始化时绘制默认地块,等待用户点击"新增地块"按钮
  208. // 清空地块和面积数据
  209. polygonArr.value = null;
  210. ruleForm.mu = "";
  211. // 如果是编辑模式,等待品类列表加载完成后再回填数据
  212. if (route.query.type === "edit" && store.state.home.editFarmData) {
  213. getSpecieList().then(() => {
  214. populateEditData();
  215. });
  216. } else {
  217. getSpecieList();
  218. }
  219. getBaseTypeList();
  220. });
  221. const polygonArr = ref(null);
  222. const paramsType = ref(null);
  223. onActivated(() => {
  224. const centerPointVal = sessionStorage.getItem('centerPoint') ? JSON.parse(sessionStorage.getItem('centerPoint')) : null;
  225. paramsType.value = route.query.type;
  226. centerPoint.value = centerPointVal || store.state.home.miniUserLocationPoint;
  227. const arr = convertPointToArray(centerPoint.value);
  228. getLocationName(`${arr[1]},${arr[0]}`);
  229. // 仅在携带 isReload 标记、且不是编辑/小程序回流场景时,认为是一次全新创建,重置表单和地块,
  230. // 避免破坏原有自动生成农场名称等逻辑
  231. if (route.query.isReload && paramsType.value !== "edit" && !route.query.miniJson) {
  232. // 重置表单字段到初始值
  233. ruleFormRef.value && ruleFormRef.value.resetFields();
  234. // 重置与地块绘制相关的内部状态
  235. polygonArr.value = null;
  236. isFromEditMap.value = false;
  237. hasDefaultPolygon.value = false;
  238. // 重置农场名称手动修改标记,允许自动生成农场名称
  239. isFarmNameManuallyModified.value = false;
  240. // 清空上一次地块缓存
  241. store.commit("home/SET_FARM_POLYGON", null);
  242. getSpecieList();
  243. }
  244. // 确保地图已初始化,使用 nextTick 等待 DOM 更新
  245. nextTick(() => {
  246. // 检查地图实例是否已初始化
  247. if (!indexMap.kmap) {
  248. // 如果地图未初始化,重新初始化
  249. if (mapContainer.value) {
  250. centerPoint.value = store.state.home.miniUserLocationPoint;
  251. const arr = convertPointToArray(centerPoint.value);
  252. indexMap.initMap(centerPoint.value, mapContainer.value);
  253. }
  254. // 等待地图初始化完成后再继续
  255. setTimeout(() => {
  256. handleMapUpdate();
  257. }, 100);
  258. return;
  259. }
  260. handleMapUpdate();
  261. });
  262. });
  263. onDeactivated(() => {
  264. sessionStorage.setItem('centerPoint', JSON.stringify(centerPoint.value));
  265. });
  266. // 处理地图更新的逻辑
  267. function handleMapUpdate() {
  268. if (route.query.isReload) {
  269. // 清除旧的地块数据
  270. store.commit("home/SET_FARM_POLYGON", null);
  271. isFromEditMap.value = false; // 从home进入,可以手动输入
  272. hasDefaultPolygon.value = false; // 重置默认地块状态
  273. centerPoint.value = store.state.home.miniUserLocationPoint;
  274. const arr = convertPointToArray(centerPoint.value);
  275. getLocationName(`${arr[1]},${arr[0]}`);
  276. indexMap.setMapPosition(arr);
  277. indexMap.clearLayer();
  278. // 不再自动生成默认地块,等待用户点击"新增地块"
  279. polygonArr.value = null;
  280. ruleForm.mu = "";
  281. ruleForm.defaultFarm = 0;
  282. return; // 直接返回,不执行下面的逻辑
  283. }
  284. indexMap.clearLayer();
  285. // 绘制勾画范围(从edit_map返回的情况)
  286. const polygonData = store.state.home.polygonData;
  287. // 优先处理从编辑地图页面返回的数据
  288. if (polygonData) {
  289. // 检查地块数据是否有效(数组存在且不为空)
  290. const hasValidGeometry =
  291. polygonData.geometryArr && Array.isArray(polygonData.geometryArr) && polygonData.geometryArr.length > 0;
  292. if (hasValidGeometry) {
  293. // 用户从edit_map返回,且有有效的地块数据
  294. // 根据 isConfirmed 判断是否锁定输入框
  295. if (polygonData.isConfirmed) {
  296. // 用户点击了"确认"按钮,锁定输入框并显示精确面积
  297. isFromEditMap.value = true;
  298. ruleForm.mu = polygonData.mianji;
  299. } else {
  300. // 用户点击了"取消",不锁定输入框,面积保持原样
  301. isFromEditMap.value = false;
  302. // 面积输入框保持之前的值,不更新
  303. }
  304. // 使用 nextTick 确保地图渲染完成后再设置地块
  305. nextTick(() => {
  306. indexMap.setAreaGeometry(polygonData.geometryArr);
  307. });
  308. polygonArr.value = polygonData.geometryArr;
  309. // 有地块数据时,标记已创建默认地块
  310. hasDefaultPolygon.value = true;
  311. } else {
  312. // 用户在编辑页面删除了所有地块
  313. // 重置所有状态
  314. polygonArr.value = null;
  315. ruleForm.mu = "";
  316. isFromEditMap.value = false;
  317. hasDefaultPolygon.value = false;
  318. ruleForm.defaultFarm = 0;
  319. }
  320. } else if (route.query.type === "edit" && store.state.home.editFarmData) {
  321. // 如果是编辑模式且没有从编辑地图返回的数据,回填原始编辑数据
  322. populateEditData();
  323. } else if (centerPoint.value && polygonArr.value) {
  324. // 没有新的编辑数据,保持当前地块
  325. // 同样需要检查数据有效性
  326. if (Array.isArray(polygonArr.value) && polygonArr.value.length > 0) {
  327. nextTick(() => {
  328. indexMap.setAreaGeometry(polygonArr.value);
  329. });
  330. // 保持 hasDefaultPolygon 状态
  331. }
  332. }
  333. }
  334. // 搜索
  335. const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
  336. const locationVal = ref(null);
  337. const locationOptions = reactive({
  338. list: [],
  339. });
  340. const loading = ref(false);
  341. const remoteMethod = async (keyword) => {
  342. if (keyword) {
  343. locationOptions.list = [];
  344. loading.value = true;
  345. const params = {
  346. key: MAP_KEY,
  347. keyword,
  348. location: route.query.userLocation || "113.61702297075017,23.584863449735067",
  349. };
  350. await VE_API.old_mini_map.getCtiyList({ word: keyword }).then(({ data }) => {
  351. if (data && data.length) {
  352. data.forEach((item) => {
  353. item.point = item.location.lat + "," + item.location.lng;
  354. locationOptions.list.push(item);
  355. });
  356. }
  357. });
  358. VE_API.old_mini_map.search(params).then(({ data }) => {
  359. loading.value = false;
  360. data.forEach((item) => {
  361. item.point = item.location.lat + "," + item.location.lng;
  362. locationOptions.list.push(item);
  363. });
  364. });
  365. } else {
  366. locationOptions.list = [];
  367. }
  368. };
  369. const handleSearchRes = (v) => {
  370. const parts = v.value.split(",");
  371. let { latitude, longitude } = transformFromGCJToWGS(parseFloat(parts[0]), parseFloat(parts[1]));
  372. const coordinateArray = [longitude, latitude];
  373. indexMap.setMapPosition(coordinateArray);
  374. centerPoint.value = `POINT (${coordinateArray[0]} ${coordinateArray[1]})`;
  375. ruleForm.address = v.item?.title || v.item?.address;
  376. pointAddress.value = v.item?.province + v.item?.city + (v.item?.district || '');
  377. // 更新farmCity以便后续更新农场名称
  378. farmCity.value = v.item?.city + (v.item?.district || "");
  379. // 地址修改后,如果满足条件则自动更新农场名称
  380. updateFarmNameIfNeeded();
  381. getSpecieList();
  382. };
  383. // 表单
  384. const ruleFormRef = ref(null);
  385. const ruleForm = reactive({
  386. address: "",
  387. mu: "",
  388. // 多选品类:数组
  389. speciesItem: [],
  390. name: "",
  391. fzr: "",
  392. tel: "",
  393. baseType: null,
  394. defaultFarm: 0, // 0:否 1:是
  395. });
  396. // 自定义验证规则:验证面积必须是大于0的数字
  397. const validateMianji = (rule, value, callback) => {
  398. if (!value) {
  399. callback(new Error("请输入农场面积"));
  400. } else {
  401. const num = parseFloat(value);
  402. if (isNaN(num)) {
  403. callback(new Error("面积必须是数字"));
  404. } else if (num <= 0) {
  405. callback(new Error("面积必须大于0"));
  406. } else {
  407. callback();
  408. }
  409. }
  410. };
  411. const rules = reactive({
  412. address: [{ required: true, message: "请选择农场位置", trigger: "blur" }],
  413. mu: [
  414. { required: true, message: "请输入农场面积", trigger: "blur" },
  415. { validator: validateMianji, trigger: ["blur", "change"] },
  416. ],
  417. speciesItem: [{ required: true, message: "请选择品类", trigger: "blur" }],
  418. name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
  419. fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
  420. tel: [
  421. { required: true, message: "请输入联系人电话", trigger: ["blur"] },
  422. {
  423. pattern: /^1[3-9]\d{9}$/,
  424. message: "请输入正确的手机号码",
  425. trigger: ["blur"],
  426. },
  427. ],
  428. defaultFarm: [{ required: true, message: "请选择是否为默认农场", trigger: "blur" }],
  429. baseType: [{ required: true, message: "请选择基地类别", trigger: "blur" }],
  430. });
  431. const curRole = localStorage.getItem("SET_USER_CUR_ROLE");
  432. const submitForm = (formEl) => {
  433. if (!formEl) return;
  434. formEl.validate((valid) => {
  435. if (valid) {
  436. const speciesList = Array.isArray(ruleForm.speciesItem) ? ruleForm.speciesItem : (ruleForm.speciesItem ? [ruleForm.speciesItem] : []);
  437. const mainSpecies = speciesList[0];
  438. const speciesContainer = speciesList.map((item) => ({
  439. speciesId: item?.id ?? null,
  440. containerId: item?.defaultContainerId ?? null,
  441. }));
  442. const params = {
  443. ...ruleForm,
  444. wkt: centerPoint.value,
  445. speciesContainer,
  446. agriculturalCreate: route.query.type === "client" ? 1 : 0,
  447. // 编辑时geom不是数组,新增时是数组
  448. geom:
  449. route.query.type === "edit"
  450. ? polygonArr.value && polygonArr.value.length > 0
  451. ? polygonArr.value[0]
  452. : null
  453. : polygonArr.value,
  454. };
  455. // 如果是编辑模式,添加农场ID
  456. if (route.query.type === "edit" && route.query.farmId) {
  457. params.id = route.query.farmId;
  458. }
  459. // let pageData = null;
  460. // if (route.query.miniJson) {
  461. // const json = JSON.parse(route.query.miniJson);
  462. // console.log('json', json);
  463. // // pageData = JSON.parse(json.paramsPage);
  464. // }
  465. // if (route.query.type === "add" || pageData?.type === 'add' || route.query.type === "farmer") {
  466. // 处理 geom 参数,如果是数组需要序列化
  467. const queryParams = {
  468. ...params,
  469. ...route.query,
  470. };
  471. // 如果 geom 是数组,需要序列化为 JSON 字符串
  472. if (Array.isArray(queryParams.geom)) {
  473. queryParams.geom = JSON.stringify(queryParams.geom);
  474. }
  475. // speciesContainer 为对象时需序列化,否则 query 传递后无法正确解析
  476. if (queryParams.speciesContainer && typeof queryParams.speciesContainer === 'object') {
  477. queryParams.speciesContainer = JSON.stringify(queryParams.speciesContainer);
  478. }
  479. delete queryParams.speciesItem;
  480. queryParams.speciesName = mainSpecies?.name;
  481. // if (pageData?.type === 'add') {
  482. // queryParams.invite = true;
  483. // }
  484. router.push({
  485. path: "/prescription",
  486. query: queryParams,
  487. });
  488. return;
  489. // }
  490. // const apiCall = route.query.type === "edit" ? VE_API.basic_farm.saveBasicFarmInfoByExpertV3({...params, expertMiniUserId: '81881'}) : VE_API.farm.saveFarm(params);
  491. // apiCall.then((res) => {
  492. // if (res.code === 0) {
  493. // ElMessage.success(route.query.type === "edit" ? "修改成功" : "创建成功");
  494. // // 重置表单和地块数据
  495. // ruleFormRef.value.resetFields();
  496. // store.commit("home/SET_FARM_POLYGON", null);
  497. // store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
  498. // polygonArr.value = null;
  499. // isFromEditMap.value = false;
  500. // if (route.query.type !== "edit" && curRole == 0) {
  501. // localStorage.setItem("selectedFarmId", res.data.id);
  502. // localStorage.setItem("selectedFarmName", res.data.name);
  503. // }
  504. // // 根据来源页面决定跳转目标
  505. // const fromPage = route.query.from;
  506. // if (fromPage && fromPage !== "details") {
  507. // // 如果是从monitor页面来的
  508. // router.replace(`/${fromPage}`);
  509. // } else if (fromPage === "details") {
  510. // router.go(-1);
  511. // } else {
  512. // if (route.query.miniJson) {
  513. // const json = JSON.parse(route.query.miniJson);
  514. // //上传图片
  515. // VE_API.ali
  516. // .uploadImg({
  517. // farmId: res.data.id,
  518. // images: json.images,
  519. // uploadDate: formatDate(new Date()),
  520. // })
  521. // .then(({ code, msg }) => {
  522. // if (code === 0) {
  523. // showSuccessPopup.value = true;
  524. // } else {
  525. // ElMessage.error(msg);
  526. // }
  527. // });
  528. // } else {
  529. // router.replace(`/growth_report`);
  530. // }
  531. // }
  532. // } else {
  533. // ElMessage.error(res.msg);
  534. // }
  535. // });
  536. }
  537. });
  538. };
  539. const resetForm = (formEl) => {
  540. if (!formEl) return;
  541. formEl.resetFields();
  542. // 清除地块数据
  543. store.commit("home/SET_FARM_POLYGON", null);
  544. polygonArr.value = null;
  545. isFromEditMap.value = false;
  546. hasDefaultPolygon.value = false; // 重置默认地块状态
  547. // 根据来源页面决定返回目标
  548. const fromPage = route.query.from;
  549. if (fromPage && fromPage !== "details") {
  550. router.replace(`/${fromPage}`);
  551. return;
  552. }
  553. router.go(-1);
  554. };
  555. const centerPoint = ref(null);
  556. function toSubPage() {
  557. // 如果还没有默认地块,先创建默认地块
  558. if (!hasDefaultPolygon.value) {
  559. if (centerPoint.value) {
  560. const arr = convertPointToArray(centerPoint.value);
  561. const squareData = generateSquarePolygonBySideLength(arr, 200); // 200米边长
  562. const geometryData = {
  563. geometryArr: [squareData.wkt],
  564. mianji: squareData.area,
  565. };
  566. // 绘制默认地块
  567. indexMap.setAreaGeometry(geometryData.geometryArr);
  568. // 保存地块数据
  569. polygonArr.value = geometryData.geometryArr;
  570. // 标记已创建默认地块
  571. hasDefaultPolygon.value = true;
  572. // 不跳转,停留在当前页面
  573. return;
  574. }
  575. }
  576. // 如果已有默认地块,则跳转到编辑页面
  577. // 保存到store中以便在编辑页面回显
  578. if (polygonArr.value) {
  579. const polygonData = {
  580. geometryArr: polygonArr.value,
  581. mianji: ruleForm.mu || "", // 保存用户输入的面积,如果没有输入则为空
  582. isConfirmed: false, // 标记:还未从编辑页面确认返回
  583. };
  584. store.commit("home/SET_FARM_POLYGON", polygonData);
  585. }
  586. router.push(
  587. `/edit_map?mapCenter=${centerPoint.value}&pointName=${ruleForm.address}&pointAddress=${pointAddress.value}&from=${route.query.from}&type=${route.query.type}`
  588. );
  589. }
  590. const pointAddress = ref(null);
  591. const farmCity = ref(null);
  592. function getLocationName(location) {
  593. const params = {
  594. key: MAP_KEY,
  595. location,
  596. };
  597. VE_API.old_mini_map.location(params).then(({ result }) => {
  598. // locationVal.value = result.formatted_addresses.recommend;
  599. const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
  600. ruleForm.address = add;
  601. pointAddress.value = result.address;
  602. farmCity.value = result.address_component?.city + (result.address_component?.district || "");
  603. // 地址修改后,如果满足条件则自动更新农场名称
  604. updateFarmNameIfNeeded();
  605. });
  606. }
  607. watch(
  608. () => mapLocation.data,
  609. (newValue, oldValue) => {
  610. if (newValue) {
  611. let { latitude, longitude } = transformFromWGSToGCJ(newValue[1], newValue[0]);
  612. centerPoint.value = `POINT (${newValue[0]} ${newValue[1]})`;
  613. getLocationName(`${latitude},${longitude}`);
  614. }
  615. }
  616. );
  617. const specieList = ref([]);
  618. function getSpecieList() {
  619. return VE_API.farm.fetchSpecieList({ point: centerPoint.value }).then(({ data }) => {
  620. const list = Array.isArray(data) ? data : [];
  621. // 只保留名称包含“荔枝”的品类
  622. const litchiList = list.filter((item) => item?.name && item.name.includes("荔枝"));
  623. specieList.value = litchiList;
  624. // 非编辑模式且当前未选择品类时,默认选中第一项
  625. const noSpeciesSelected =
  626. !Array.isArray(ruleForm.speciesItem) || ruleForm.speciesItem.length === 0;
  627. if (route.query.type !== "edit" && noSpeciesSelected && litchiList.length > 0) {
  628. const first = { value: litchiList[0].id, ...litchiList[0] };
  629. ruleForm.speciesItem = [first];
  630. // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
  631. changeSpecie(first);
  632. }
  633. return litchiList;
  634. });
  635. }
  636. const baseTypeList = ref([]);
  637. function getBaseTypeList() {
  638. return VE_API.farm.fetchBaseTypeList().then(({ data }) => {
  639. baseTypeList.value = data || [];
  640. return data;
  641. });
  642. }
  643. async function changeSpecie(v) {
  644. const current = Array.isArray(v) ? v[v.length - 1] : v;
  645. // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
  646. if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
  647. ruleForm.name = farmCity.value + current.name + "农场";
  648. }
  649. }
  650. /**
  651. * 格式化日期为 YYYY-MM-DD 格式
  652. * @param {Date} date - 日期对象
  653. * @returns {string} 格式化后的日期字符串
  654. */
  655. function formatDate(date) {
  656. const year = date.getFullYear();
  657. const month = String(date.getMonth() + 1).padStart(2, "0");
  658. const day = String(date.getDate()).padStart(2, "0");
  659. return `${year}-${month}-${day}`;
  660. }
  661. function backgToHome() {
  662. ruleFormRef.value?.resetFields();
  663. // 根据来源页面决定返回目标
  664. const fromPage = route.query?.from;
  665. if (route.query.miniJson) {
  666. const json = JSON.parse(route.query.miniJson);
  667. if (json.isMini) {
  668. const dropdownGardenItem = ref({
  669. organId: json.farmId,
  670. periodId: json.periodId,
  671. name: json.name,
  672. page: "create_farm",
  673. showFarmSelect: true,
  674. images: json.images,
  675. });
  676. wx.miniProgram.reLaunch({
  677. url: `/pages/subPages/new_recognize/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
  678. });
  679. }
  680. json?.from && router.replace(`/${json.from}`);
  681. // const paramsPage = JSON.parse(json.paramsPage);
  682. // if (paramsPage.isFarmer) {
  683. // router.replace('/home');
  684. // return;
  685. // }
  686. } else {
  687. if (fromPage && fromPage !== "details") {
  688. if (route.query?.type === "farmer") {
  689. router.go(-1);
  690. } else {
  691. router.replace(`/${fromPage}`);
  692. }
  693. return;
  694. } else {
  695. router.go(-1);
  696. }
  697. }
  698. }
  699. // 处理面积按键输入 - 只允许数字和小数点
  700. function handleMianjiKeypress(event) {
  701. // 如果是从编辑地图返回的,不允许手动修改
  702. if (isFromEditMap.value) {
  703. event.preventDefault();
  704. return;
  705. }
  706. const charCode = event.which ? event.which : event.keyCode;
  707. const char = String.fromCharCode(charCode);
  708. // 允许数字 (0-9) 和小数点 (.)
  709. if (!/^[0-9.]$/.test(char)) {
  710. event.preventDefault();
  711. return;
  712. }
  713. // 如果已经有小数点,不允许再输入小数点
  714. const currentValue = ruleForm.mu || "";
  715. if (char === "." && currentValue.includes(".")) {
  716. event.preventDefault();
  717. return;
  718. }
  719. // 不允许以小数点开头
  720. if (char === "." && !currentValue) {
  721. event.preventDefault();
  722. return;
  723. }
  724. }
  725. // 处理农场名称输入
  726. function handleFarmNameInput() {
  727. // 标记用户已手动修改过农场名称
  728. isFarmNameManuallyModified.value = true;
  729. }
  730. // 根据地址更新农场名称(如果满足条件)
  731. function updateFarmNameIfNeeded() {
  732. // 只有在创建模式下且用户没有手动修改过农场名称且已选择品类时,才自动更新农场名称
  733. const mainSpecies = Array.isArray(ruleForm.speciesItem)
  734. ? ruleForm.speciesItem[0]
  735. : ruleForm.speciesItem;
  736. if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && mainSpecies && farmCity.value) {
  737. ruleForm.name = farmCity.value + mainSpecies.name + "农场";
  738. }
  739. }
  740. // 回填编辑数据
  741. function populateEditData() {
  742. const editData = store.state.home.editFarmData;
  743. if (!editData) {
  744. return;
  745. }
  746. // 回填基本信息
  747. ruleForm.name = editData.name || "";
  748. ruleForm.fzr = editData.fzr || "";
  749. ruleForm.tel = editData.tel || "";
  750. ruleForm.baseType = editData.baseType || null;
  751. ruleForm.defaultFarm = editData.defaultOption || 0;
  752. ruleForm.mu = editData.mianji || "";
  753. ruleForm.address = editData.address || "";
  754. // 设置地图中心点
  755. if (editData.pointWkt) {
  756. centerPoint.value = editData.pointWkt;
  757. const arr = convertPointToArray(editData.pointWkt);
  758. indexMap.setMapPosition(arr);
  759. }
  760. // 处理地块数据
  761. if (editData.geomWkt) {
  762. polygonArr.value = [editData.geomWkt];
  763. indexMap.setAreaGeometry([editData.geomWkt]);
  764. hasDefaultPolygon.value = true;
  765. isFromEditMap.value = true; // 编辑模式下锁定面积输入
  766. }
  767. // 处理作物数据(兼容多选)
  768. if (Array.isArray(editData.regionList) && editData.regionList.length > 0) {
  769. const selected = [];
  770. const speciesIdSet = new Set();
  771. editData.regionList.forEach((region) => {
  772. const targetId = region.speciesId;
  773. if (targetId == null) return;
  774. // 去重:同一个 speciesId 只保留一条
  775. const speciesKey = String(targetId);
  776. if (speciesIdSet.has(speciesKey)) return;
  777. const match = specieList.value.find((s) => String(s.id) === String(targetId));
  778. if (match) {
  779. speciesIdSet.add(speciesKey);
  780. selected.push({
  781. value: match.id,
  782. ...match,
  783. });
  784. }
  785. });
  786. if (selected.length > 0) {
  787. ruleForm.speciesItem = selected;
  788. }
  789. } else if (editData.speciesId && editData.speciesName) {
  790. // 兼容旧数据:只有单个品类字段时,按原逻辑回显一个
  791. const species = {
  792. value: editData.speciesId,
  793. id: editData.speciesId,
  794. name: editData.speciesName,
  795. defaultContainerId: editData.containerId,
  796. };
  797. ruleForm.speciesItem = [species];
  798. }
  799. // 设置地址信息
  800. if (editData.district) {
  801. try {
  802. const districtInfo = JSON.parse(editData.district);
  803. pointAddress.value = districtInfo.province + districtInfo.city + (districtInfo?.district || '');
  804. } catch (e) {
  805. console.warn("解析地址信息失败:", e);
  806. }
  807. }
  808. }
  809. // 处理面积输入
  810. let mianjiInputTimer = null;
  811. function handleMianjiInput(value) {
  812. // 如果是从编辑地图返回的,不允许手动修改
  813. if (isFromEditMap.value) {
  814. return;
  815. }
  816. // 过滤非法字符,只保留数字和小数点
  817. let filteredValue = value.replace(/[^\d.]/g, "");
  818. // 确保只有一个小数点
  819. const parts = filteredValue.split(".");
  820. if (parts.length > 2) {
  821. filteredValue = parts[0] + "." + parts.slice(1).join("");
  822. }
  823. // 限制小数点后最多2位
  824. if (parts.length === 2 && parts[1].length > 2) {
  825. filteredValue = parts[0] + "." + parts[1].substring(0, 2);
  826. }
  827. // 更新输入框的值(如果被过滤了)
  828. if (filteredValue !== value) {
  829. ruleForm.mu = filteredValue;
  830. return;
  831. }
  832. // 清除之前的定时器
  833. if (mianjiInputTimer) {
  834. clearTimeout(mianjiInputTimer);
  835. }
  836. // 防抖处理,用户停止输入500ms后再更新地块
  837. mianjiInputTimer = setTimeout(() => {
  838. const mu = parseFloat(filteredValue);
  839. // 验证输入的有效性
  840. if (!mu || isNaN(mu) || mu <= 0) {
  841. return;
  842. }
  843. // 根据亩数重新生成地块
  844. if (centerPoint.value) {
  845. const arr = convertPointToArray(centerPoint.value);
  846. const squareData = generateSquarePolygonByMu(arr, mu);
  847. const geometryData = {
  848. geometryArr: [squareData.wkt],
  849. mianji: squareData.area,
  850. };
  851. // 清除旧地块
  852. indexMap.clearLayer();
  853. // 绘制新地块
  854. indexMap.setAreaGeometry(geometryData.geometryArr);
  855. // 更新状态
  856. polygonArr.value = geometryData.geometryArr;
  857. // 标记已创建默认地块
  858. hasDefaultPolygon.value = true;
  859. }
  860. }, 500);
  861. }
  862. </script>
  863. <style lang="scss" scoped>
  864. ::v-deep {
  865. .el-form-item--default {
  866. margin-bottom: 20px;
  867. }
  868. }
  869. .create-farm {
  870. position: relative;
  871. width: 100%;
  872. height: 100vh;
  873. overflow: hidden;
  874. .map-container {
  875. width: 100%;
  876. height: calc(100% - 320px);
  877. }
  878. .checkbox {
  879. padding: 0 12px 6px;
  880. font-size: 15px;
  881. }
  882. .farm-content {
  883. position: absolute;
  884. top: 40px;
  885. left: 0;
  886. width: 100%;
  887. height: calc(100% - 40px);
  888. pointer-events: none;
  889. z-index: 2;
  890. .top-mask {
  891. height: 100px;
  892. position: absolute;
  893. z-index: 2;
  894. top: 0;
  895. left: 0;
  896. width: 100%;
  897. background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
  898. }
  899. }
  900. .farm-filter {
  901. pointer-events: all;
  902. margin: 12px;
  903. position: relative;
  904. z-index: 10;
  905. background: rgba(0, 0, 0, 0.3);
  906. border-radius: 20px;
  907. border: 1px solid rgba(255, 255, 255, 0.4);
  908. &::before {
  909. content: "";
  910. position: absolute;
  911. left: 12px;
  912. top: 9px;
  913. width: 14px;
  914. height: 14px;
  915. background: url("@/assets/img/home/search.png") no-repeat center center / 100% 100%;
  916. }
  917. ::v-deep {
  918. .el-select__wrapper {
  919. background: none;
  920. box-shadow: none;
  921. padding-left: 34px;
  922. font-size: 12px;
  923. .el-select__selected-item,
  924. .el-select__placeholder,
  925. .el-select__input {
  926. color: rgba(255, 255, 255, 0.6);
  927. &.is-transparent {
  928. color: #ccc;
  929. font-size: 12px;
  930. }
  931. }
  932. }
  933. .el-select {
  934. transition: all 0.3s;
  935. .el-input.is-focus .el-input__wrapper {
  936. box-shadow: none !important;
  937. }
  938. }
  939. .el-input {
  940. .el-input__wrapper {
  941. background: none;
  942. box-shadow: none;
  943. padding-left: 18px;
  944. font-size: 11px;
  945. .el-input__inner {
  946. color: rgba(255, 255, 255, 0.6);
  947. }
  948. &.is-focus {
  949. .el-input__inner {
  950. color: #ccc;
  951. font-size: 11px;
  952. }
  953. }
  954. }
  955. }
  956. }
  957. }
  958. .create-wrap {
  959. position: absolute;
  960. bottom: 0px;
  961. left: 0;
  962. width: 100%;
  963. background: linear-gradient(180deg, transparent 0%, #f5f7fb 20%);
  964. }
  965. .create-box {
  966. pointer-events: all;
  967. margin: 0 12px 10px 12px;
  968. width: calc(100% - 24px);
  969. background: #e0f1fe;
  970. border-radius: 14px;
  971. .box-content {
  972. position: relative;
  973. &::after {
  974. position: absolute;
  975. right: 10px;
  976. top: 2px;
  977. content: "";
  978. width: 79px;
  979. height: 72px;
  980. background: url("@/assets/img/home/creat-bg.png") no-repeat center / 100% 100%;
  981. }
  982. }
  983. .create-title {
  984. display: flex;
  985. align-items: center;
  986. padding: 12px 6px 12px 12px;
  987. color: #0089f5;
  988. font-size: 18px;
  989. font-weight: bold;
  990. .title-icon {
  991. width: 18px;
  992. padding-right: 10px;
  993. }
  994. }
  995. .create-content {
  996. background: #fff;
  997. border-radius: 14px;
  998. padding: 12px;
  999. position: relative;
  1000. z-index: 2;
  1001. .create-from {
  1002. .select-wrap {
  1003. display: flex;
  1004. // &.specie-wrap {
  1005. // flex: 1;
  1006. // gap: 12px;
  1007. // .specie-select {
  1008. // min-width: 56px;
  1009. // width: 56px;
  1010. // }
  1011. // }
  1012. // width: 86%;
  1013. ::v-deep {
  1014. .el-input__wrapper {
  1015. background: none;
  1016. box-shadow: none;
  1017. }
  1018. .el-input__inner {
  1019. font-size: 14px;
  1020. color: rgba(0, 0, 0, 0.5);
  1021. }
  1022. .el-select__wrapper {
  1023. background: none;
  1024. box-shadow: none;
  1025. gap: 2px;
  1026. padding: 4px 2px;
  1027. justify-content: center;
  1028. }
  1029. // .el-select__selection {
  1030. // flex: none;
  1031. // width: min-content;
  1032. // }
  1033. .el-select__placeholder {
  1034. color: #000;
  1035. position: static;
  1036. transform: none;
  1037. width: fit-content;
  1038. }
  1039. }
  1040. &.client-wrap {
  1041. ::v-deep {
  1042. .el-select__wrapper {
  1043. justify-content: flex-start;
  1044. }
  1045. }
  1046. }
  1047. .select-item {
  1048. min-width: 80px;
  1049. margin-left: 10px;
  1050. }
  1051. }
  1052. ::v-deep {
  1053. .el-form-item__label {
  1054. color: #000;
  1055. }
  1056. .el-form-item__error {
  1057. top: 117%;
  1058. }
  1059. .el-form-item {
  1060. position: relative;
  1061. &::after {
  1062. content: "";
  1063. position: absolute;
  1064. left: 60px;
  1065. bottom: -5px;
  1066. width: calc(100% - 60px);
  1067. height: 1px;
  1068. background: rgba(0, 0, 0, 0.08);
  1069. }
  1070. }
  1071. .el-input__wrapper {
  1072. box-shadow: none;
  1073. padding: 1px 6px;
  1074. }
  1075. }
  1076. .area-box {
  1077. display: flex;
  1078. align-items: center;
  1079. width: 100%;
  1080. .unit {
  1081. padding-right: 10px;
  1082. }
  1083. }
  1084. .position-wrap {
  1085. width: 100%;
  1086. display: flex;
  1087. justify-content: space-between;
  1088. align-items: center;
  1089. .draw-btn {
  1090. flex: none;
  1091. padding: 0 12px;
  1092. height: 30px;
  1093. line-height: 30px;
  1094. box-sizing: border-box;
  1095. color: #2199f8;
  1096. border: 1px solid #2199f8;
  1097. background: rgba(33, 153, 248, 0.1);
  1098. border-radius: 20px;
  1099. font-size: 12px;
  1100. }
  1101. }
  1102. }
  1103. .create-btn {
  1104. display: flex;
  1105. align-items: center;
  1106. width: 100%;
  1107. padding-top: 10px;
  1108. .btn-item {
  1109. flex: 1;
  1110. text-align: center;
  1111. padding: 0 11px;
  1112. height: 40px;
  1113. line-height: 40px;
  1114. border-radius: 34px;
  1115. font-size: 16px;
  1116. box-sizing: border-box;
  1117. &.sencond-btn {
  1118. border: 1px solid rgba(153, 153, 153, 0.5);
  1119. color: #666666;
  1120. }
  1121. &.primary-btn {
  1122. background: linear-gradient(180deg, #76c3ff, #2199f8);
  1123. color: #fff;
  1124. }
  1125. }
  1126. .btn-item+.btn-item {
  1127. margin-left: 5px;
  1128. }
  1129. }
  1130. }
  1131. }
  1132. }
  1133. </style>
  1134. <style lang="scss">
  1135. .location-search-popper {
  1136. .el-select-dropdown__list {
  1137. max-width: 96vw;
  1138. overflow-x: auto;
  1139. }
  1140. .sub-title {
  1141. padding-left: 6px;
  1142. font-size: 12px;
  1143. color: #ccc;
  1144. }
  1145. }
  1146. </style>