index.vue 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299
  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. if (litchiList.length === 0) {
  626. ruleForm.speciesItem = [];
  627. return litchiList;
  628. }
  629. //列表有值时,默认选中第一项
  630. if (litchiList.length > 0) {
  631. const first = { value: litchiList[0].id, ...litchiList[0] };
  632. ruleForm.speciesItem = [first];
  633. // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
  634. changeSpecie(first);
  635. }
  636. return litchiList;
  637. });
  638. }
  639. const baseTypeList = ref([]);
  640. function getBaseTypeList() {
  641. return VE_API.farm.fetchBaseTypeList().then(({ data }) => {
  642. baseTypeList.value = data || [];
  643. return data;
  644. });
  645. }
  646. async function changeSpecie(v) {
  647. const current = Array.isArray(v) ? v[v.length - 1] : v;
  648. // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
  649. if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
  650. ruleForm.name = farmCity.value + current.name + "农场";
  651. }
  652. }
  653. /**
  654. * 格式化日期为 YYYY-MM-DD 格式
  655. * @param {Date} date - 日期对象
  656. * @returns {string} 格式化后的日期字符串
  657. */
  658. function formatDate(date) {
  659. const year = date.getFullYear();
  660. const month = String(date.getMonth() + 1).padStart(2, "0");
  661. const day = String(date.getDate()).padStart(2, "0");
  662. return `${year}-${month}-${day}`;
  663. }
  664. function backgToHome() {
  665. ruleFormRef.value?.resetFields();
  666. // 根据来源页面决定返回目标
  667. const fromPage = route.query?.from;
  668. if (route.query.miniJson) {
  669. const json = JSON.parse(route.query.miniJson);
  670. if (json.isMini) {
  671. const dropdownGardenItem = ref({
  672. organId: json.farmId,
  673. periodId: json.periodId,
  674. name: json.name,
  675. page: "create_farm",
  676. showFarmSelect: true,
  677. images: json.images,
  678. });
  679. wx.miniProgram.reLaunch({
  680. url: `/pages/subPages/new_recognize/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
  681. });
  682. }
  683. json?.from && router.replace(`/${json.from}`);
  684. // const paramsPage = JSON.parse(json.paramsPage);
  685. // if (paramsPage.isFarmer) {
  686. // router.replace('/home');
  687. // return;
  688. // }
  689. } else {
  690. if (fromPage && fromPage !== "details") {
  691. if (route.query?.type === "farmer") {
  692. router.go(-1);
  693. } else {
  694. router.replace(`/${fromPage}`);
  695. }
  696. return;
  697. } else {
  698. router.go(-1);
  699. }
  700. }
  701. }
  702. // 处理面积按键输入 - 只允许数字和小数点
  703. function handleMianjiKeypress(event) {
  704. // 如果是从编辑地图返回的,不允许手动修改
  705. if (isFromEditMap.value) {
  706. event.preventDefault();
  707. return;
  708. }
  709. const charCode = event.which ? event.which : event.keyCode;
  710. const char = String.fromCharCode(charCode);
  711. // 允许数字 (0-9) 和小数点 (.)
  712. if (!/^[0-9.]$/.test(char)) {
  713. event.preventDefault();
  714. return;
  715. }
  716. // 如果已经有小数点,不允许再输入小数点
  717. const currentValue = ruleForm.mu || "";
  718. if (char === "." && currentValue.includes(".")) {
  719. event.preventDefault();
  720. return;
  721. }
  722. // 不允许以小数点开头
  723. if (char === "." && !currentValue) {
  724. event.preventDefault();
  725. return;
  726. }
  727. }
  728. // 处理农场名称输入
  729. function handleFarmNameInput() {
  730. // 标记用户已手动修改过农场名称
  731. isFarmNameManuallyModified.value = true;
  732. }
  733. // 根据地址更新农场名称(如果满足条件)
  734. function updateFarmNameIfNeeded() {
  735. // 只有在创建模式下且用户没有手动修改过农场名称且已选择品类时,才自动更新农场名称
  736. const mainSpecies = Array.isArray(ruleForm.speciesItem)
  737. ? ruleForm.speciesItem[0]
  738. : ruleForm.speciesItem;
  739. if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && mainSpecies && farmCity.value) {
  740. ruleForm.name = farmCity.value + mainSpecies.name + "农场";
  741. }
  742. }
  743. // 回填编辑数据
  744. function populateEditData() {
  745. const editData = store.state.home.editFarmData;
  746. if (!editData) {
  747. return;
  748. }
  749. // 回填基本信息
  750. ruleForm.name = editData.name || "";
  751. ruleForm.fzr = editData.fzr || "";
  752. ruleForm.tel = editData.tel || "";
  753. ruleForm.baseType = editData.baseType || null;
  754. ruleForm.defaultFarm = editData.defaultOption || 0;
  755. ruleForm.mu = editData.mianji || "";
  756. ruleForm.address = editData.address || "";
  757. // 设置地图中心点
  758. if (editData.pointWkt) {
  759. centerPoint.value = editData.pointWkt;
  760. const arr = convertPointToArray(editData.pointWkt);
  761. indexMap.setMapPosition(arr);
  762. }
  763. // 处理地块数据
  764. if (editData.geomWkt) {
  765. polygonArr.value = [editData.geomWkt];
  766. indexMap.setAreaGeometry([editData.geomWkt]);
  767. hasDefaultPolygon.value = true;
  768. isFromEditMap.value = true; // 编辑模式下锁定面积输入
  769. }
  770. // 处理作物数据(兼容多选)
  771. if (Array.isArray(editData.regionList) && editData.regionList.length > 0) {
  772. const selected = [];
  773. const speciesIdSet = new Set();
  774. editData.regionList.forEach((region) => {
  775. const targetId = region.speciesId;
  776. if (targetId == null) return;
  777. // 去重:同一个 speciesId 只保留一条
  778. const speciesKey = String(targetId);
  779. if (speciesIdSet.has(speciesKey)) return;
  780. const match = specieList.value.find((s) => String(s.id) === String(targetId));
  781. if (match) {
  782. speciesIdSet.add(speciesKey);
  783. selected.push({
  784. value: match.id,
  785. ...match,
  786. });
  787. }
  788. });
  789. if (selected.length > 0) {
  790. ruleForm.speciesItem = selected;
  791. }
  792. } else if (editData.speciesId && editData.speciesName) {
  793. // 兼容旧数据:只有单个品类字段时,按原逻辑回显一个
  794. const species = {
  795. value: editData.speciesId,
  796. id: editData.speciesId,
  797. name: editData.speciesName,
  798. defaultContainerId: editData.containerId,
  799. };
  800. ruleForm.speciesItem = [species];
  801. }
  802. // 设置地址信息
  803. if (editData.district) {
  804. try {
  805. const districtInfo = JSON.parse(editData.district);
  806. pointAddress.value = districtInfo.province + districtInfo.city + (districtInfo?.district || '');
  807. } catch (e) {
  808. console.warn("解析地址信息失败:", e);
  809. }
  810. }
  811. }
  812. // 处理面积输入
  813. let mianjiInputTimer = null;
  814. function handleMianjiInput(value) {
  815. // 如果是从编辑地图返回的,不允许手动修改
  816. if (isFromEditMap.value) {
  817. return;
  818. }
  819. // 过滤非法字符,只保留数字和小数点
  820. let filteredValue = value.replace(/[^\d.]/g, "");
  821. // 确保只有一个小数点
  822. const parts = filteredValue.split(".");
  823. if (parts.length > 2) {
  824. filteredValue = parts[0] + "." + parts.slice(1).join("");
  825. }
  826. // 限制小数点后最多2位
  827. if (parts.length === 2 && parts[1].length > 2) {
  828. filteredValue = parts[0] + "." + parts[1].substring(0, 2);
  829. }
  830. // 更新输入框的值(如果被过滤了)
  831. if (filteredValue !== value) {
  832. ruleForm.mu = filteredValue;
  833. return;
  834. }
  835. // 清除之前的定时器
  836. if (mianjiInputTimer) {
  837. clearTimeout(mianjiInputTimer);
  838. }
  839. // 防抖处理,用户停止输入500ms后再更新地块
  840. mianjiInputTimer = setTimeout(() => {
  841. const mu = parseFloat(filteredValue);
  842. // 验证输入的有效性
  843. if (!mu || isNaN(mu) || mu <= 0) {
  844. return;
  845. }
  846. // 根据亩数重新生成地块
  847. if (centerPoint.value) {
  848. const arr = convertPointToArray(centerPoint.value);
  849. const squareData = generateSquarePolygonByMu(arr, mu);
  850. const geometryData = {
  851. geometryArr: [squareData.wkt],
  852. mianji: squareData.area,
  853. };
  854. // 清除旧地块
  855. indexMap.clearLayer();
  856. // 绘制新地块
  857. indexMap.setAreaGeometry(geometryData.geometryArr);
  858. // 更新状态
  859. polygonArr.value = geometryData.geometryArr;
  860. // 标记已创建默认地块
  861. hasDefaultPolygon.value = true;
  862. }
  863. }, 500);
  864. }
  865. </script>
  866. <style lang="scss" scoped>
  867. ::v-deep {
  868. .el-form-item--default {
  869. margin-bottom: 20px;
  870. }
  871. }
  872. .create-farm {
  873. position: relative;
  874. width: 100%;
  875. height: 100vh;
  876. overflow: hidden;
  877. .map-container {
  878. width: 100%;
  879. height: calc(100% - 320px);
  880. }
  881. .checkbox {
  882. padding: 0 12px 6px;
  883. font-size: 15px;
  884. }
  885. .farm-content {
  886. position: absolute;
  887. top: 40px;
  888. left: 0;
  889. width: 100%;
  890. height: calc(100% - 40px);
  891. pointer-events: none;
  892. z-index: 2;
  893. .top-mask {
  894. height: 100px;
  895. position: absolute;
  896. z-index: 2;
  897. top: 0;
  898. left: 0;
  899. width: 100%;
  900. background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
  901. }
  902. }
  903. .farm-filter {
  904. pointer-events: all;
  905. margin: 12px;
  906. position: relative;
  907. z-index: 10;
  908. background: rgba(0, 0, 0, 0.3);
  909. border-radius: 20px;
  910. border: 1px solid rgba(255, 255, 255, 0.4);
  911. &::before {
  912. content: "";
  913. position: absolute;
  914. left: 12px;
  915. top: 9px;
  916. width: 14px;
  917. height: 14px;
  918. background: url("@/assets/img/home/search.png") no-repeat center center / 100% 100%;
  919. }
  920. ::v-deep {
  921. .el-select__wrapper {
  922. background: none;
  923. box-shadow: none;
  924. padding-left: 34px;
  925. font-size: 12px;
  926. .el-select__selected-item,
  927. .el-select__placeholder,
  928. .el-select__input {
  929. color: rgba(255, 255, 255, 0.6);
  930. &.is-transparent {
  931. color: #ccc;
  932. font-size: 12px;
  933. }
  934. }
  935. }
  936. .el-select {
  937. transition: all 0.3s;
  938. .el-input.is-focus .el-input__wrapper {
  939. box-shadow: none !important;
  940. }
  941. }
  942. .el-input {
  943. .el-input__wrapper {
  944. background: none;
  945. box-shadow: none;
  946. padding-left: 18px;
  947. font-size: 11px;
  948. .el-input__inner {
  949. color: rgba(255, 255, 255, 0.6);
  950. }
  951. &.is-focus {
  952. .el-input__inner {
  953. color: #ccc;
  954. font-size: 11px;
  955. }
  956. }
  957. }
  958. }
  959. }
  960. }
  961. .create-wrap {
  962. position: absolute;
  963. bottom: 0px;
  964. left: 0;
  965. width: 100%;
  966. background: linear-gradient(180deg, transparent 0%, #f5f7fb 20%);
  967. }
  968. .create-box {
  969. pointer-events: all;
  970. margin: 0 12px 10px 12px;
  971. width: calc(100% - 24px);
  972. background: #e0f1fe;
  973. border-radius: 14px;
  974. .box-content {
  975. position: relative;
  976. &::after {
  977. position: absolute;
  978. right: 10px;
  979. top: 2px;
  980. content: "";
  981. width: 79px;
  982. height: 72px;
  983. background: url("@/assets/img/home/creat-bg.png") no-repeat center / 100% 100%;
  984. }
  985. }
  986. .create-title {
  987. display: flex;
  988. align-items: center;
  989. padding: 12px 6px 12px 12px;
  990. color: #0089f5;
  991. font-size: 18px;
  992. font-weight: bold;
  993. .title-icon {
  994. width: 18px;
  995. padding-right: 10px;
  996. }
  997. }
  998. .create-content {
  999. background: #fff;
  1000. border-radius: 14px;
  1001. padding: 12px;
  1002. position: relative;
  1003. z-index: 2;
  1004. .create-from {
  1005. .select-wrap {
  1006. display: flex;
  1007. // &.specie-wrap {
  1008. // flex: 1;
  1009. // gap: 12px;
  1010. // .specie-select {
  1011. // min-width: 56px;
  1012. // width: 56px;
  1013. // }
  1014. // }
  1015. // width: 86%;
  1016. ::v-deep {
  1017. .el-input__wrapper {
  1018. background: none;
  1019. box-shadow: none;
  1020. }
  1021. .el-input__inner {
  1022. font-size: 14px;
  1023. color: rgba(0, 0, 0, 0.5);
  1024. }
  1025. .el-select__wrapper {
  1026. background: none;
  1027. box-shadow: none;
  1028. gap: 2px;
  1029. padding: 4px 2px;
  1030. justify-content: center;
  1031. }
  1032. // .el-select__selection {
  1033. // flex: none;
  1034. // width: min-content;
  1035. // }
  1036. .el-select__placeholder {
  1037. color: #000;
  1038. position: static;
  1039. transform: none;
  1040. width: fit-content;
  1041. }
  1042. }
  1043. &.client-wrap {
  1044. ::v-deep {
  1045. .el-select__wrapper {
  1046. justify-content: flex-start;
  1047. }
  1048. }
  1049. }
  1050. .select-item {
  1051. min-width: 80px;
  1052. margin-left: 10px;
  1053. }
  1054. }
  1055. ::v-deep {
  1056. .el-form-item__label {
  1057. color: #000;
  1058. }
  1059. .el-form-item__error {
  1060. top: 117%;
  1061. }
  1062. .el-form-item {
  1063. position: relative;
  1064. &::after {
  1065. content: "";
  1066. position: absolute;
  1067. left: 60px;
  1068. bottom: -5px;
  1069. width: calc(100% - 60px);
  1070. height: 1px;
  1071. background: rgba(0, 0, 0, 0.08);
  1072. }
  1073. }
  1074. .el-input__wrapper {
  1075. box-shadow: none;
  1076. padding: 1px 6px;
  1077. }
  1078. }
  1079. .area-box {
  1080. display: flex;
  1081. align-items: center;
  1082. width: 100%;
  1083. .unit {
  1084. padding-right: 10px;
  1085. }
  1086. }
  1087. .position-wrap {
  1088. width: 100%;
  1089. display: flex;
  1090. justify-content: space-between;
  1091. align-items: center;
  1092. .draw-btn {
  1093. flex: none;
  1094. padding: 0 12px;
  1095. height: 30px;
  1096. line-height: 30px;
  1097. box-sizing: border-box;
  1098. color: #2199f8;
  1099. border: 1px solid #2199f8;
  1100. background: rgba(33, 153, 248, 0.1);
  1101. border-radius: 20px;
  1102. font-size: 12px;
  1103. }
  1104. }
  1105. }
  1106. .create-btn {
  1107. display: flex;
  1108. align-items: center;
  1109. width: 100%;
  1110. padding-top: 10px;
  1111. .btn-item {
  1112. flex: 1;
  1113. text-align: center;
  1114. padding: 0 11px;
  1115. height: 40px;
  1116. line-height: 40px;
  1117. border-radius: 34px;
  1118. font-size: 16px;
  1119. box-sizing: border-box;
  1120. &.sencond-btn {
  1121. border: 1px solid rgba(153, 153, 153, 0.5);
  1122. color: #666666;
  1123. }
  1124. &.primary-btn {
  1125. background: linear-gradient(180deg, #76c3ff, #2199f8);
  1126. color: #fff;
  1127. }
  1128. }
  1129. .btn-item+.btn-item {
  1130. margin-left: 5px;
  1131. }
  1132. }
  1133. }
  1134. }
  1135. }
  1136. </style>
  1137. <style lang="scss">
  1138. .location-search-popper {
  1139. .el-select-dropdown__list {
  1140. max-width: 96vw;
  1141. overflow-x: auto;
  1142. }
  1143. .sub-title {
  1144. padding-left: 6px;
  1145. font-size: 12px;
  1146. color: #ccc;
  1147. }
  1148. }
  1149. </style>