index.vue 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311
  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 client-wrap">
  42. <el-select v-model="ruleForm.speciesItem" filterable class="select-item specie-select"
  43. multiple :max-collapse-tags="3" 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. locationVal.value = null;
  274. centerPoint.value = store.state.home.miniUserLocationPoint;
  275. const arr = convertPointToArray(centerPoint.value);
  276. getLocationName(`${arr[1]},${arr[0]}`);
  277. indexMap.setMapPosition(arr);
  278. indexMap.clearLayer();
  279. // 不再自动生成默认地块,等待用户点击"新增地块"
  280. polygonArr.value = null;
  281. ruleForm.mu = "";
  282. ruleForm.defaultFarm = 0;
  283. return; // 直接返回,不执行下面的逻辑
  284. }
  285. indexMap.clearLayer();
  286. // 绘制勾画范围(从edit_map返回的情况)
  287. const polygonData = store.state.home.polygonData;
  288. // 优先处理从编辑地图页面返回的数据
  289. if (polygonData) {
  290. // 检查地块数据是否有效(数组存在且不为空)
  291. const hasValidGeometry =
  292. polygonData.geometryArr && Array.isArray(polygonData.geometryArr) && polygonData.geometryArr.length > 0;
  293. if (hasValidGeometry) {
  294. // 用户从edit_map返回,且有有效的地块数据
  295. // 根据 isConfirmed 判断是否锁定输入框
  296. if (polygonData.isConfirmed) {
  297. // 用户点击了"确认"按钮,锁定输入框并显示精确面积
  298. isFromEditMap.value = true;
  299. ruleForm.mu = polygonData.mianji;
  300. } else {
  301. // 用户点击了"取消",不锁定输入框,面积保持原样
  302. isFromEditMap.value = false;
  303. // 面积输入框保持之前的值,不更新
  304. }
  305. // 使用 nextTick 确保地图渲染完成后再设置地块
  306. nextTick(() => {
  307. indexMap.setAreaGeometry(polygonData.geometryArr);
  308. });
  309. polygonArr.value = polygonData.geometryArr;
  310. // 有地块数据时,标记已创建默认地块
  311. hasDefaultPolygon.value = true;
  312. } else {
  313. // 用户在编辑页面删除了所有地块
  314. // 重置所有状态
  315. polygonArr.value = null;
  316. ruleForm.mu = "";
  317. isFromEditMap.value = false;
  318. hasDefaultPolygon.value = false;
  319. ruleForm.defaultFarm = 0;
  320. }
  321. } else if (route.query.type === "edit" && store.state.home.editFarmData) {
  322. // 如果是编辑模式且没有从编辑地图返回的数据,回填原始编辑数据
  323. populateEditData();
  324. } else if (centerPoint.value && polygonArr.value) {
  325. // 没有新的编辑数据,保持当前地块
  326. // 同样需要检查数据有效性
  327. if (Array.isArray(polygonArr.value) && polygonArr.value.length > 0) {
  328. nextTick(() => {
  329. indexMap.setAreaGeometry(polygonArr.value);
  330. });
  331. // 保持 hasDefaultPolygon 状态
  332. }
  333. }
  334. }
  335. // 搜索
  336. const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
  337. const locationVal = ref(null);
  338. const locationOptions = reactive({
  339. list: [],
  340. });
  341. const loading = ref(false);
  342. const remoteMethod = async (keyword) => {
  343. if (keyword) {
  344. locationOptions.list = [];
  345. loading.value = true;
  346. const params = {
  347. key: MAP_KEY,
  348. keyword,
  349. location: route.query.userLocation || "113.61702297075017,23.584863449735067",
  350. };
  351. await VE_API.old_mini_map.getCtiyList({ word: keyword }).then(({ data }) => {
  352. if (data && data.length) {
  353. data.forEach((item) => {
  354. item.point = item.location.lat + "," + item.location.lng;
  355. locationOptions.list.push(item);
  356. });
  357. }
  358. });
  359. VE_API.old_mini_map.search(params).then(({ data }) => {
  360. loading.value = false;
  361. data.forEach((item) => {
  362. item.point = item.location.lat + "," + item.location.lng;
  363. locationOptions.list.push(item);
  364. });
  365. });
  366. } else {
  367. locationOptions.list = [];
  368. }
  369. };
  370. const handleSearchRes = (v) => {
  371. const parts = v.value.split(",");
  372. let { latitude, longitude } = transformFromGCJToWGS(parseFloat(parts[0]), parseFloat(parts[1]));
  373. const coordinateArray = [longitude, latitude];
  374. indexMap.setMapPosition(coordinateArray);
  375. centerPoint.value = `POINT (${coordinateArray[0]} ${coordinateArray[1]})`;
  376. ruleForm.address = v.item?.title || v.item?.address;
  377. pointAddress.value = v.item?.province + v.item?.city + (v.item?.district || '');
  378. // 更新farmCity以便后续更新农场名称
  379. farmCity.value = v.item?.city + (v.item?.district || "");
  380. // 地址修改后,如果满足条件则自动更新农场名称
  381. updateFarmNameIfNeeded();
  382. getSpecieList();
  383. };
  384. // 表单
  385. const ruleFormRef = ref(null);
  386. const ruleForm = reactive({
  387. address: "",
  388. mu: "",
  389. // 多选品类:数组
  390. speciesItem: [],
  391. name: "",
  392. fzr: "",
  393. tel: "",
  394. baseType: null,
  395. defaultFarm: 0, // 0:否 1:是
  396. });
  397. // 自定义验证规则:验证面积必须是大于0的数字
  398. const validateMianji = (rule, value, callback) => {
  399. if (!value) {
  400. callback(new Error("请输入农场面积"));
  401. } else {
  402. const num = parseFloat(value);
  403. if (isNaN(num)) {
  404. callback(new Error("面积必须是数字"));
  405. } else if (num <= 0) {
  406. callback(new Error("面积必须大于0"));
  407. } else {
  408. callback();
  409. }
  410. }
  411. };
  412. const rules = reactive({
  413. address: [{ required: true, message: "请选择农场位置", trigger: "blur" }],
  414. mu: [
  415. { required: true, message: "请输入农场面积", trigger: "blur" },
  416. { validator: validateMianji, trigger: ["blur", "change"] },
  417. ],
  418. speciesItem: [{ required: true, message: "请选择种植作物", trigger: ["blur", "change"] }],
  419. name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
  420. fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
  421. tel: [
  422. { required: true, message: "请输入联系人电话", trigger: ["blur"] },
  423. {
  424. pattern: /^1[3-9]\d{9}$/,
  425. message: "请输入正确的手机号码",
  426. trigger: ["blur"],
  427. },
  428. ],
  429. defaultFarm: [{ required: true, message: "请选择是否为默认农场", trigger: "blur" }],
  430. baseType: [{ required: true, message: "请选择基地类别", trigger: "blur" }],
  431. });
  432. const curRole = localStorage.getItem("SET_USER_CUR_ROLE");
  433. const submitForm = (formEl) => {
  434. if (!formEl) return;
  435. formEl.validate((valid) => {
  436. if (valid) {
  437. const speciesList = Array.isArray(ruleForm.speciesItem) ? ruleForm.speciesItem : (ruleForm.speciesItem ? [ruleForm.speciesItem] : []);
  438. const mainSpecies = speciesList[0];
  439. const speciesContainer = speciesList.map((item) => ({
  440. speciesId: item?.id ?? null,
  441. containerId: item?.defaultContainerId ?? null,
  442. }));
  443. const params = {
  444. ...ruleForm,
  445. wkt: centerPoint.value,
  446. speciesContainer,
  447. agriculturalCreate: route.query.type === "client" ? 1 : 0,
  448. // 编辑时geom不是数组,新增时是数组
  449. geom:
  450. route.query.type === "edit"
  451. ? polygonArr.value && polygonArr.value.length > 0
  452. ? polygonArr.value[0]
  453. : null
  454. : polygonArr.value,
  455. };
  456. // 如果是编辑模式,添加农场ID
  457. if (route.query.type === "edit" && route.query.farmId) {
  458. params.id = route.query.farmId;
  459. }
  460. // let pageData = null;
  461. // if (route.query.miniJson) {
  462. // const json = JSON.parse(route.query.miniJson);
  463. // console.log('json', json);
  464. // // pageData = JSON.parse(json.paramsPage);
  465. // }
  466. if (route.query.type !== "edit") {
  467. // 处理 geom 参数,如果是数组需要序列化
  468. const queryParams = {
  469. ...params,
  470. ...route.query,
  471. };
  472. // 如果 geom 是数组,需要序列化为 JSON 字符串
  473. if (Array.isArray(queryParams.geom)) {
  474. queryParams.geom = JSON.stringify(queryParams.geom);
  475. }
  476. // speciesContainer 为对象时需序列化,否则 query 传递后无法正确解析
  477. if (queryParams.speciesContainer && typeof queryParams.speciesContainer === 'object') {
  478. queryParams.speciesContainer = JSON.stringify(queryParams.speciesContainer);
  479. }
  480. delete queryParams.speciesItem;
  481. queryParams.speciesName = mainSpecies?.name;
  482. // if (pageData?.type === 'add') {
  483. // queryParams.invite = true;
  484. // }
  485. router.push({
  486. path: "/prescription",
  487. query: queryParams,
  488. });
  489. return;
  490. }
  491. // const apiCall = route.query.type === "edit" ? VE_API.basic_farm.saveBasicFarmInfoByExpertV3({...params, expertMiniUserId: '81881'}) : VE_API.farm.saveFarm(params);
  492. // apiCall.then((res) => {
  493. // if (res.code === 0) {
  494. // ElMessage.success(route.query.type === "edit" ? "修改成功" : "创建成功");
  495. // // 重置表单和地块数据
  496. // ruleFormRef.value.resetFields();
  497. // store.commit("home/SET_FARM_POLYGON", null);
  498. // store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
  499. // polygonArr.value = null;
  500. // isFromEditMap.value = false;
  501. // if (route.query.type !== "edit" && curRole == 0) {
  502. // localStorage.setItem("selectedFarmId", res.data.id);
  503. // localStorage.setItem("selectedFarmName", res.data.name);
  504. // }
  505. // // 根据来源页面决定跳转目标
  506. // const fromPage = route.query.from;
  507. // if (fromPage && fromPage !== "details") {
  508. // // 如果是从monitor页面来的
  509. // router.replace(`/${fromPage}`);
  510. // } else if (fromPage === "details") {
  511. // router.go(-1);
  512. // } else {
  513. // if (route.query.miniJson) {
  514. // const json = JSON.parse(route.query.miniJson);
  515. // //上传图片
  516. // VE_API.ali
  517. // .uploadImg({
  518. // farmId: res.data.id,
  519. // images: json.images,
  520. // uploadDate: formatDate(new Date()),
  521. // })
  522. // .then(({ code, msg }) => {
  523. // if (code === 0) {
  524. // showSuccessPopup.value = true;
  525. // } else {
  526. // ElMessage.error(msg);
  527. // }
  528. // });
  529. // } else {
  530. // router.replace(`/growth_report`);
  531. // }
  532. // }
  533. // } else {
  534. // ElMessage.error(res.msg);
  535. // }
  536. // });
  537. }
  538. });
  539. };
  540. const resetForm = (formEl) => {
  541. if (!formEl) return;
  542. formEl.resetFields();
  543. // 清除地块数据
  544. store.commit("home/SET_FARM_POLYGON", null);
  545. polygonArr.value = null;
  546. isFromEditMap.value = false;
  547. hasDefaultPolygon.value = false; // 重置默认地块状态
  548. // 根据来源页面决定返回目标
  549. const fromPage = route.query.from;
  550. if (fromPage && fromPage !== "details") {
  551. router.replace(`/${fromPage}`);
  552. return;
  553. }
  554. router.go(-1);
  555. };
  556. const centerPoint = ref(null);
  557. function toSubPage() {
  558. // 如果还没有默认地块,先创建默认地块
  559. if (!hasDefaultPolygon.value) {
  560. if (centerPoint.value) {
  561. const arr = convertPointToArray(centerPoint.value);
  562. const squareData = generateSquarePolygonBySideLength(arr, 200); // 200米边长
  563. const geometryData = {
  564. geometryArr: [squareData.wkt],
  565. mianji: squareData.area,
  566. };
  567. // 绘制默认地块
  568. indexMap.setAreaGeometry(geometryData.geometryArr);
  569. // 保存地块数据
  570. polygonArr.value = geometryData.geometryArr;
  571. // 标记已创建默认地块
  572. hasDefaultPolygon.value = true;
  573. // 不跳转,停留在当前页面
  574. return;
  575. }
  576. }
  577. // 如果已有默认地块,则跳转到编辑页面
  578. // 保存到store中以便在编辑页面回显
  579. if (polygonArr.value) {
  580. const polygonData = {
  581. geometryArr: polygonArr.value,
  582. mianji: ruleForm.mu || "", // 保存用户输入的面积,如果没有输入则为空
  583. isConfirmed: false, // 标记:还未从编辑页面确认返回
  584. };
  585. store.commit("home/SET_FARM_POLYGON", polygonData);
  586. }
  587. router.push(
  588. `/edit_map?mapCenter=${centerPoint.value}&pointName=${ruleForm.address}&pointAddress=${pointAddress.value}&from=${route.query.from}&type=${route.query.type}`
  589. );
  590. }
  591. const pointAddress = ref(null);
  592. const farmCity = ref(null);
  593. function getLocationName(location) {
  594. const params = {
  595. key: MAP_KEY,
  596. location,
  597. };
  598. VE_API.old_mini_map.location(params).then(({ result }) => {
  599. // locationVal.value = result.formatted_addresses.recommend;
  600. const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
  601. ruleForm.address = add;
  602. pointAddress.value = result.address;
  603. farmCity.value = result.address_component?.city + (result.address_component?.district || "");
  604. // 地址修改后,如果满足条件则自动更新农场名称
  605. updateFarmNameIfNeeded();
  606. });
  607. }
  608. watch(
  609. () => mapLocation.data,
  610. (newValue, oldValue) => {
  611. if (newValue) {
  612. let { latitude, longitude } = transformFromWGSToGCJ(newValue[1], newValue[0]);
  613. centerPoint.value = `POINT (${newValue[0]} ${newValue[1]})`;
  614. getLocationName(`${latitude},${longitude}`);
  615. }
  616. }
  617. );
  618. const specieList = ref([]);
  619. function getSpecieList() {
  620. return VE_API.farm.fetchSpecieList({ point: centerPoint.value }).then(({ data }) => {
  621. const list = Array.isArray(data) ? data : [];
  622. // 只保留名称包含“水稻”或“荔枝”的品类
  623. const litchiList = list.filter(
  624. (item) => item?.name && (item.name.includes("水稻") || item.name.includes("荔枝")|| item.name.includes("大豆")|| item.name.includes("茶叶"))
  625. );
  626. specieList.value = litchiList;
  627. // 返回空列表时,重置已选品类,避免保留上一次默认项
  628. if (litchiList.length === 0) {
  629. ruleForm.speciesItem = [];
  630. return litchiList;
  631. }
  632. // //列表有值时,默认选中第一项
  633. // if (litchiList.length > 0) {
  634. // const first = { value: litchiList[0].id, ...litchiList[0] };
  635. // ruleForm.speciesItem = [first];
  636. // // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
  637. // changeSpecie(first);
  638. // }
  639. return litchiList;
  640. });
  641. }
  642. const baseTypeList = ref([]);
  643. function getBaseTypeList() {
  644. return VE_API.farm.fetchBaseTypeList().then(({ data }) => {
  645. baseTypeList.value = data || [];
  646. return data;
  647. });
  648. }
  649. async function changeSpecie(v) {
  650. const current = Array.isArray(v) ? v[v.length - 1] : v;
  651. // 选择后立即更新该字段的校验状态,避免错误提示残留
  652. nextTick(() => {
  653. if (!ruleFormRef.value) return;
  654. if (Array.isArray(ruleForm.speciesItem) && ruleForm.speciesItem.length > 0) {
  655. ruleFormRef.value.clearValidate(["speciesItem"]);
  656. return;
  657. }
  658. ruleFormRef.value.validateField("speciesItem");
  659. });
  660. // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
  661. if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
  662. ruleForm.name = farmCity.value + current.name + "农场";
  663. }
  664. }
  665. /**
  666. * 格式化日期为 YYYY-MM-DD 格式
  667. * @param {Date} date - 日期对象
  668. * @returns {string} 格式化后的日期字符串
  669. */
  670. function formatDate(date) {
  671. const year = date.getFullYear();
  672. const month = String(date.getMonth() + 1).padStart(2, "0");
  673. const day = String(date.getDate()).padStart(2, "0");
  674. return `${year}-${month}-${day}`;
  675. }
  676. function backgToHome() {
  677. ruleFormRef.value?.resetFields();
  678. // 根据来源页面决定返回目标
  679. const fromPage = route.query?.from;
  680. if (route.query.miniJson) {
  681. const json = JSON.parse(route.query.miniJson);
  682. if (json.isMini) {
  683. const dropdownGardenItem = ref({
  684. organId: json.farmId,
  685. periodId: json.periodId,
  686. name: json.name,
  687. page: "create_farm",
  688. showFarmSelect: true,
  689. images: json.images,
  690. });
  691. wx.miniProgram.reLaunch({
  692. url: `/pages/subPages/new_recognize/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
  693. });
  694. }
  695. json?.from && router.replace(`/${json.from}`);
  696. // const paramsPage = JSON.parse(json.paramsPage);
  697. // if (paramsPage.isFarmer) {
  698. // router.replace('/home');
  699. // return;
  700. // }
  701. } else {
  702. if (fromPage && fromPage !== "details") {
  703. if (route.query?.type === "farmer") {
  704. router.go(-1);
  705. } else {
  706. router.replace(`/${fromPage}`);
  707. }
  708. return;
  709. } else {
  710. router.go(-1);
  711. }
  712. }
  713. }
  714. // 处理面积按键输入 - 只允许数字和小数点
  715. function handleMianjiKeypress(event) {
  716. // 如果是从编辑地图返回的,不允许手动修改
  717. if (isFromEditMap.value) {
  718. event.preventDefault();
  719. return;
  720. }
  721. const charCode = event.which ? event.which : event.keyCode;
  722. const char = String.fromCharCode(charCode);
  723. // 允许数字 (0-9) 和小数点 (.)
  724. if (!/^[0-9.]$/.test(char)) {
  725. event.preventDefault();
  726. return;
  727. }
  728. // 如果已经有小数点,不允许再输入小数点
  729. const currentValue = ruleForm.mu || "";
  730. if (char === "." && currentValue.includes(".")) {
  731. event.preventDefault();
  732. return;
  733. }
  734. // 不允许以小数点开头
  735. if (char === "." && !currentValue) {
  736. event.preventDefault();
  737. return;
  738. }
  739. }
  740. // 处理农场名称输入
  741. function handleFarmNameInput() {
  742. // 标记用户已手动修改过农场名称
  743. isFarmNameManuallyModified.value = true;
  744. }
  745. // 根据地址更新农场名称(如果满足条件)
  746. function updateFarmNameIfNeeded() {
  747. // 只有在创建模式下且用户没有手动修改过农场名称且已选择品类时,才自动更新农场名称
  748. const mainSpecies = Array.isArray(ruleForm.speciesItem)
  749. ? ruleForm.speciesItem[0]
  750. : ruleForm.speciesItem;
  751. if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && mainSpecies && farmCity.value) {
  752. ruleForm.name = farmCity.value + mainSpecies.name + "农场";
  753. }
  754. }
  755. // 回填编辑数据
  756. function populateEditData() {
  757. const editData = store.state.home.editFarmData;
  758. if (!editData) {
  759. return;
  760. }
  761. // 回填基本信息
  762. ruleForm.name = editData.name || "";
  763. ruleForm.fzr = editData.fzr || "";
  764. ruleForm.tel = editData.tel || "";
  765. ruleForm.baseType = editData.baseType || null;
  766. ruleForm.defaultFarm = editData.defaultOption || 0;
  767. ruleForm.mu = editData.mianji || "";
  768. ruleForm.address = editData.address || "";
  769. // 设置地图中心点
  770. if (editData.pointWkt) {
  771. centerPoint.value = editData.pointWkt;
  772. const arr = convertPointToArray(editData.pointWkt);
  773. indexMap.setMapPosition(arr);
  774. }
  775. // 处理地块数据
  776. if (editData.geomWkt) {
  777. polygonArr.value = [editData.geomWkt];
  778. indexMap.setAreaGeometry([editData.geomWkt]);
  779. hasDefaultPolygon.value = true;
  780. isFromEditMap.value = true; // 编辑模式下锁定面积输入
  781. }
  782. // 处理作物数据(兼容多选)
  783. if (Array.isArray(editData.regionList) && editData.regionList.length > 0) {
  784. const selected = [];
  785. const speciesIdSet = new Set();
  786. editData.regionList.forEach((region) => {
  787. const targetId = region.speciesId;
  788. if (targetId == null) return;
  789. // 去重:同一个 speciesId 只保留一条
  790. const speciesKey = String(targetId);
  791. if (speciesIdSet.has(speciesKey)) return;
  792. const match = specieList.value.find((s) => String(s.id) === String(targetId));
  793. if (match) {
  794. speciesIdSet.add(speciesKey);
  795. selected.push({
  796. value: match.id,
  797. ...match,
  798. });
  799. }
  800. });
  801. if (selected.length > 0) {
  802. ruleForm.speciesItem = selected;
  803. }
  804. } else if (editData.speciesId && editData.speciesName) {
  805. // 兼容旧数据:只有单个品类字段时,按原逻辑回显一个
  806. const species = {
  807. value: editData.speciesId,
  808. id: editData.speciesId,
  809. name: editData.speciesName,
  810. defaultContainerId: editData.containerId,
  811. };
  812. ruleForm.speciesItem = [species];
  813. }
  814. // 设置地址信息
  815. if (editData.district) {
  816. try {
  817. const districtInfo = JSON.parse(editData.district);
  818. pointAddress.value = districtInfo.province + districtInfo.city + (districtInfo?.district || '');
  819. } catch (e) {
  820. console.warn("解析地址信息失败:", e);
  821. }
  822. }
  823. }
  824. // 处理面积输入
  825. let mianjiInputTimer = null;
  826. function handleMianjiInput(value) {
  827. // 如果是从编辑地图返回的,不允许手动修改
  828. if (isFromEditMap.value) {
  829. return;
  830. }
  831. // 过滤非法字符,只保留数字和小数点
  832. let filteredValue = value.replace(/[^\d.]/g, "");
  833. // 确保只有一个小数点
  834. const parts = filteredValue.split(".");
  835. if (parts.length > 2) {
  836. filteredValue = parts[0] + "." + parts.slice(1).join("");
  837. }
  838. // 限制小数点后最多2位
  839. if (parts.length === 2 && parts[1].length > 2) {
  840. filteredValue = parts[0] + "." + parts[1].substring(0, 2);
  841. }
  842. // 更新输入框的值(如果被过滤了)
  843. if (filteredValue !== value) {
  844. ruleForm.mu = filteredValue;
  845. return;
  846. }
  847. // 清除之前的定时器
  848. if (mianjiInputTimer) {
  849. clearTimeout(mianjiInputTimer);
  850. }
  851. // 防抖处理,用户停止输入500ms后再更新地块
  852. mianjiInputTimer = setTimeout(() => {
  853. const mu = parseFloat(filteredValue);
  854. // 验证输入的有效性
  855. if (!mu || isNaN(mu) || mu <= 0) {
  856. return;
  857. }
  858. // 根据亩数重新生成地块
  859. if (centerPoint.value) {
  860. const arr = convertPointToArray(centerPoint.value);
  861. const squareData = generateSquarePolygonByMu(arr, mu);
  862. const geometryData = {
  863. geometryArr: [squareData.wkt],
  864. mianji: squareData.area,
  865. };
  866. // 清除旧地块
  867. indexMap.clearLayer();
  868. // 绘制新地块
  869. indexMap.setAreaGeometry(geometryData.geometryArr);
  870. // 更新状态
  871. polygonArr.value = geometryData.geometryArr;
  872. // 标记已创建默认地块
  873. hasDefaultPolygon.value = true;
  874. }
  875. }, 500);
  876. }
  877. </script>
  878. <style lang="scss" scoped>
  879. ::v-deep {
  880. .el-form-item--default {
  881. margin-bottom: 20px;
  882. }
  883. }
  884. .create-farm {
  885. position: relative;
  886. width: 100%;
  887. height: 100vh;
  888. overflow: hidden;
  889. .map-container {
  890. width: 100%;
  891. height: calc(100% - 320px);
  892. }
  893. .checkbox {
  894. padding: 0 12px 6px;
  895. font-size: 15px;
  896. }
  897. .farm-content {
  898. position: absolute;
  899. top: 40px;
  900. left: 0;
  901. width: 100%;
  902. height: calc(100% - 40px);
  903. pointer-events: none;
  904. z-index: 2;
  905. .top-mask {
  906. height: 100px;
  907. position: absolute;
  908. z-index: 2;
  909. top: 0;
  910. left: 0;
  911. width: 100%;
  912. background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
  913. }
  914. }
  915. .farm-filter {
  916. pointer-events: all;
  917. margin: 12px;
  918. position: relative;
  919. z-index: 10;
  920. background: rgba(0, 0, 0, 0.3);
  921. border-radius: 20px;
  922. border: 1px solid rgba(255, 255, 255, 0.4);
  923. &::before {
  924. content: "";
  925. position: absolute;
  926. left: 12px;
  927. top: 9px;
  928. width: 14px;
  929. height: 14px;
  930. background: url("@/assets/img/home/search.png") no-repeat center center / 100% 100%;
  931. }
  932. ::v-deep {
  933. .el-select__wrapper {
  934. background: none;
  935. box-shadow: none;
  936. padding-left: 34px;
  937. font-size: 12px;
  938. .el-select__selected-item,
  939. .el-select__placeholder,
  940. .el-select__input {
  941. color: rgba(255, 255, 255, 0.6);
  942. &.is-transparent {
  943. color: #ccc;
  944. font-size: 12px;
  945. }
  946. }
  947. }
  948. .el-select {
  949. transition: all 0.3s;
  950. .el-input.is-focus .el-input__wrapper {
  951. box-shadow: none !important;
  952. }
  953. }
  954. .el-input {
  955. .el-input__wrapper {
  956. background: none;
  957. box-shadow: none;
  958. padding-left: 18px;
  959. font-size: 11px;
  960. .el-input__inner {
  961. color: rgba(255, 255, 255, 0.6);
  962. }
  963. &.is-focus {
  964. .el-input__inner {
  965. color: #ccc;
  966. font-size: 11px;
  967. }
  968. }
  969. }
  970. }
  971. }
  972. }
  973. .create-wrap {
  974. position: absolute;
  975. bottom: 0px;
  976. left: 0;
  977. width: 100%;
  978. background: linear-gradient(180deg, transparent 0%, #f5f7fb 20%);
  979. }
  980. .create-box {
  981. pointer-events: all;
  982. margin: 0 12px 10px 12px;
  983. width: calc(100% - 24px);
  984. background: #e0f1fe;
  985. border-radius: 14px;
  986. .box-content {
  987. position: relative;
  988. &::after {
  989. position: absolute;
  990. right: 10px;
  991. top: 2px;
  992. content: "";
  993. width: 79px;
  994. height: 72px;
  995. background: url("@/assets/img/home/creat-bg.png") no-repeat center / 100% 100%;
  996. }
  997. }
  998. .create-title {
  999. display: flex;
  1000. align-items: center;
  1001. padding: 12px 6px 12px 12px;
  1002. color: #0089f5;
  1003. font-size: 18px;
  1004. font-weight: bold;
  1005. .title-icon {
  1006. width: 18px;
  1007. padding-right: 10px;
  1008. }
  1009. }
  1010. .create-content {
  1011. background: #fff;
  1012. border-radius: 14px;
  1013. padding: 12px;
  1014. position: relative;
  1015. z-index: 2;
  1016. .create-from {
  1017. .select-wrap {
  1018. display: flex;
  1019. // &.specie-wrap {
  1020. // flex: 1;
  1021. // gap: 12px;
  1022. // .specie-select {
  1023. // min-width: 56px;
  1024. // width: 56px;
  1025. // }
  1026. // }
  1027. width: 100%;
  1028. ::v-deep {
  1029. .el-input__wrapper {
  1030. background: none;
  1031. box-shadow: none;
  1032. }
  1033. .el-input__inner {
  1034. font-size: 14px;
  1035. color: rgba(0, 0, 0, 0.5);
  1036. }
  1037. .el-select__wrapper {
  1038. background: none;
  1039. box-shadow: none;
  1040. gap: 2px;
  1041. padding: 4px 2px;
  1042. justify-content: center;
  1043. }
  1044. // .el-select__selection {
  1045. // flex: none;
  1046. // width: min-content;
  1047. // }
  1048. .el-select__placeholder {
  1049. color: #000;
  1050. position: static;
  1051. transform: none;
  1052. width: fit-content;
  1053. }
  1054. }
  1055. &.client-wrap {
  1056. ::v-deep {
  1057. .el-select__wrapper {
  1058. justify-content: flex-start;
  1059. }
  1060. }
  1061. }
  1062. // .select-item {
  1063. // min-width: 80px;
  1064. // margin-left: 10px;
  1065. // }
  1066. }
  1067. ::v-deep {
  1068. .el-form-item__label {
  1069. color: #000;
  1070. }
  1071. .el-form-item__error {
  1072. top: 117%;
  1073. }
  1074. .el-form-item {
  1075. position: relative;
  1076. &::after {
  1077. content: "";
  1078. position: absolute;
  1079. left: 60px;
  1080. bottom: -5px;
  1081. width: calc(100% - 60px);
  1082. height: 1px;
  1083. background: rgba(0, 0, 0, 0.08);
  1084. }
  1085. }
  1086. .el-input__wrapper {
  1087. box-shadow: none;
  1088. padding: 1px 6px;
  1089. }
  1090. }
  1091. .area-box {
  1092. display: flex;
  1093. align-items: center;
  1094. width: 100%;
  1095. .unit {
  1096. padding-right: 10px;
  1097. }
  1098. }
  1099. .position-wrap {
  1100. width: 100%;
  1101. display: flex;
  1102. justify-content: space-between;
  1103. align-items: center;
  1104. .draw-btn {
  1105. flex: none;
  1106. padding: 0 12px;
  1107. height: 30px;
  1108. line-height: 30px;
  1109. box-sizing: border-box;
  1110. color: #2199f8;
  1111. border: 1px solid #2199f8;
  1112. background: rgba(33, 153, 248, 0.1);
  1113. border-radius: 20px;
  1114. font-size: 12px;
  1115. }
  1116. }
  1117. }
  1118. .create-btn {
  1119. display: flex;
  1120. align-items: center;
  1121. width: 100%;
  1122. padding-top: 10px;
  1123. .btn-item {
  1124. flex: 1;
  1125. text-align: center;
  1126. padding: 0 11px;
  1127. height: 40px;
  1128. line-height: 40px;
  1129. border-radius: 34px;
  1130. font-size: 16px;
  1131. box-sizing: border-box;
  1132. &.sencond-btn {
  1133. border: 1px solid rgba(153, 153, 153, 0.5);
  1134. color: #666666;
  1135. }
  1136. &.primary-btn {
  1137. background: linear-gradient(180deg, #76c3ff, #2199f8);
  1138. color: #fff;
  1139. }
  1140. }
  1141. .btn-item+.btn-item {
  1142. margin-left: 5px;
  1143. }
  1144. }
  1145. }
  1146. }
  1147. }
  1148. </style>
  1149. <style lang="scss">
  1150. .location-search-popper {
  1151. .el-select-dropdown__list {
  1152. max-width: 96vw;
  1153. overflow-x: auto;
  1154. }
  1155. .sub-title {
  1156. padding-left: 6px;
  1157. font-size: 12px;
  1158. color: #ccc;
  1159. }
  1160. }
  1161. </style>