prescriptionPage.vue 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327
  1. <template>
  2. <div class="prescription-page">
  3. <div class="prescription-title">
  4. <img @click="goBack" src="@/assets/img/home/back.png" alt="" />
  5. <div class="title-name">农场基本信息</div>
  6. <div class="title-desc">请认真核对一下内容</div>
  7. </div>
  8. <div class="prescription-box" v-loading="loadingPage">
  9. <!-- <div class="box-title">
  10. <img src="@/assets/img/home/label-icon.png" />
  11. 农场情况
  12. </div> -->
  13. <div class="box-content">
  14. <div class="box-item">
  15. <div class="item-name">
  16. <span>请选择您的果园土壤类型</span>
  17. </div>
  18. <div class="tips">土壤类型确认最佳施肥策略</div>
  19. <div class="item-checkbox">
  20. <el-radio-group v-model="basicForm.soilTypes">
  21. <el-radio-button v-for="(item, index) in basicFarmFormData.soilTypes" :key="index"
  22. :label="item.name" :value="item.code" />
  23. </el-radio-group>
  24. </div>
  25. </div>
  26. <div class="box-item">
  27. <div class="item-name">
  28. <span>请选择您的灌溉方式</span>
  29. <span class="sub-name"> (可多选)</span>
  30. </div>
  31. <div class="tips">灌溉方式确认药肥最佳配比</div>
  32. <div class="item-checkbox">
  33. <div class="tag-group add-tag-group">
  34. <div class="tag-item" :class="{ self: item.custom === true, selected: item.selected }"
  35. @click="handleSelect('irrigationMethods', idx, item.custom)"
  36. v-for="(item, idx) in basicFarmFormData.irrigationMethods" :key="'ce-' + idx">
  37. <span class="text">{{ item.name }}</span>
  38. </div>
  39. <div class="tag-item last-add" @click="handleAdd('irrigationMethods')"><el-icon class="add-icon">
  40. <Plus />
  41. </el-icon>其它方式</div>
  42. </div>
  43. </div>
  44. </div>
  45. <div class="box-item">
  46. <div class="item-name">
  47. <span>请选择您的农机设备</span>
  48. <span class="sub-name"> (可多选)</span>
  49. </div>
  50. <div class="tips">农闲时可将闲置农机加入农事服务联盟,获取额外服务收益</div>
  51. <div class="item-checkbox">
  52. <div class="tag-group add-tag-group">
  53. <div class="tag-item" :class="{ self: item.custom === true, selected: item.selected }"
  54. @click="handleMachinerySelect('machinery', idx, item)"
  55. v-for="(item, idx) in basicFarmFormData.machinery" :key="'nj-' + idx">
  56. <span class="text">{{ item.name }}</span>
  57. <span class="quantity-text" v-show="item.quantity">{{ item.quantity }}</span>
  58. </div>
  59. <div class="tag-item last-add" @click="handleMachineryAdd('machinery', item)"><el-icon class="add-icon">
  60. <Plus />
  61. </el-icon>其它设备</div>
  62. </div>
  63. <!-- <el-checkbox-group v-model="basicForm.machineryWithQuantity">
  64. <el-checkbox-button v-for="(item, index) in basicFarmFormData.machinery" :key="index"
  65. :label="item.name" :value="item.code" />
  66. </el-checkbox-group> -->
  67. </div>
  68. </div>
  69. <!-- 农场规模 -->
  70. <div class="farm-scale-header">
  71. <div class="farm-scale-title">请填写您的农场规模</div>
  72. <div class="farm-scale-desc">农闲时可以提供农事服务,获取额外收益</div>
  73. </div>
  74. <div class="farm-scale-form">
  75. <div class="farm-scale-item">
  76. <div class="farm-scale-label">
  77. 农场固定长工
  78. <div class="sub-label">(会操作各种小型农机)</div>
  79. </div>
  80. <el-input v-model="farmScale.regularWorkerCount" placeholder="请输入人数" class="farm-scale-input">
  81. <template #append>人</template>
  82. </el-input>
  83. </div>
  84. <div class="farm-scale-item">
  85. <label class="farm-scale-label">无人机植保人员</label>
  86. <el-input v-model="farmScale.plantProtectionWorkerCount" placeholder="请输入人数" class="farm-scale-input">
  87. <template #append>人</template>
  88. </el-input>
  89. </div>
  90. <div class="farm-scale-item">
  91. <label class="farm-scale-label">专业树形修剪人员</label>
  92. <el-input v-model="farmScale.pruningWorkerCount" placeholder="请输入人数" class="farm-scale-input">
  93. <template #append>人</template>
  94. </el-input>
  95. </div>
  96. <div class="farm-scale-item">
  97. <label class="farm-scale-label">专业嫁接换种人员</label>
  98. <el-input v-model="farmScale.graftingWorkerCount" placeholder="请输入人数" class="farm-scale-input">
  99. <template #append>人</template>
  100. </el-input>
  101. </div>
  102. <div class="farm-scale-item">
  103. <label class="farm-scale-label">农忙可调度人员</label>
  104. <el-input v-model="farmScale.tempDispatchWorkerCount" placeholder="请输入人数" class="farm-scale-input">
  105. <template #append>人</template>
  106. </el-input>
  107. </div>
  108. </div>
  109. <div class="box-item">
  110. <div class="item-name">
  111. <span>希望专家帮助解决的种植难题</span>
  112. <span class="sub-name"> (可多选)</span>
  113. </div>
  114. <div class="item-checkbox">
  115. <div class="tag-group add-tag-group">
  116. <div class="tag-item" :class="{ self: item.custom === true, selected: item.selected }"
  117. @click="handleSelect('improvementAreas', idx, item.custom)"
  118. v-for="(item, idx) in basicFarmFormData.improvementAreas" :key="'wt-' + idx">
  119. <span class="text">{{ item.name }}</span>
  120. </div>
  121. </div>
  122. <!-- <el-checkbox-group v-model="basicForm.improvementAreas">
  123. <el-checkbox-button v-for="(item, index) in basicFarmFormData.improvementAreas" :key="index"
  124. :label="item.name" :value="item.code" />
  125. </el-checkbox-group> -->
  126. </div>
  127. </div>
  128. <div class="box-item">
  129. <div class="item-name">
  130. <span>您最希望得到哪位荔枝专家的农事处方</span>
  131. </div>
  132. <div class="item-checkbox">
  133. <!-- <div class="tag-group add-tag-group">
  134. <div class="tag-item" :class="{ self: item.custom === true, selected: item.selected }"
  135. @click="handleSelect('expertOptions', idx, item.custom)"
  136. v-for="(item, idx) in basicFarmFormData.expertOptions" :key="'zj-' + idx">
  137. <span class="text">{{ item.name }}</span>
  138. </div>
  139. </div> -->
  140. <el-radio-group v-model="basicForm.expertCode">
  141. <el-radio-button v-for="(item, index) in basicFarmFormData.expertOptions" :key="index"
  142. :label="item.name" :value="item.code" />
  143. </el-radio-group>
  144. <!-- <el-checkbox-group v-model="basicForm.improvementAreas">
  145. <el-checkbox-button v-for="(item, index) in basicFarmFormData.improvementAreas" :key="index"
  146. :label="item.name" :value="item.code" />
  147. </el-checkbox-group> -->
  148. </div>
  149. </div>
  150. </div>
  151. </div>
  152. <!-- 按钮 -->
  153. <div class="custom-bottom-fixed-btns">
  154. <!-- <div class="bottom-btn secondary-btn" @click="handlePage">跳过</div> -->
  155. <div
  156. class="bottom-btn primary-btn"
  157. @click.stop="handlePage"
  158. @touchstart.stop
  159. @touchend.stop
  160. >确认信息</div>
  161. </div>
  162. </div>
  163. <popup class="add-tag-popup" round v-model:show="showAddPopup">
  164. <div class="tag-item" v-if="addTypeKey !== 'machinery' || (addTypeKey === 'machinery' && isMachineSelf)">
  165. <div class="popup-title">编辑<span class="name-text">{{ formNameObj[addTypeKey] }}</span><span class="ml-2">名称</span></div>
  166. <el-input class="popup-input" v-model="popupInputVal" :placeholder="'请输入' + formNameObj[addTypeKey]" size="large" />
  167. </div>
  168. <div class="device-item" v-if="addTypeKey === 'machinery'">
  169. <div class="popup-title">编辑
  170. <span class="name-text" v-show="isMachineSelf">{{ formNameObj[addTypeKey] }}</span>
  171. <span class="name-text" v-show="!isMachineSelf">{{ popupInputVal }}</span>
  172. <span class="ml-2">数量</span>
  173. </div>
  174. <el-input class="popup-input" v-model="popupInputNum" :placeholder="'请输入' + formNameObj[addTypeKey]+'数量'" size="large">
  175. <template #append>{{ machineryUnit || '辆' }}</template>
  176. </el-input>
  177. </div>
  178. <div class="popup-button">
  179. <div class="delete" v-if="isEditPopup && isMachineSelf" @click="handleDelete">删除</div>
  180. <div class="delete" v-else-if="isEditPopup && !isMachineSelf" @click="handleCancelSelect">取消选中</div>
  181. <div class="cancel" v-else @click="showAddPopup = false">取消</div>
  182. <div @click="handleConfirm">确认</div>
  183. </div>
  184. </popup>
  185. <!-- 物候期信息 -->
  186. <popup class="period-popup" round v-model:show="showPeriodPopup">
  187. <div class="period-header">为了{{ basicForm?.expertInfo?.name }}专家的处方体系适用于您的果园管理,请完善基本物候信息</div>
  188. <div class="period-content">
  189. <div class="period-item" v-for="(item, i) in phenologyList" :key="i">
  190. <div class="period-item-name">{{ item.typeName }}</div>
  191. <div class="period-item-label">
  192. <div class="label-item-group">
  193. <div class="label-item">当下物候期</div>
  194. <div class="label-item-value period-display">{{ item.phenologyName }}</div>
  195. </div>
  196. <div class="label-item-group">
  197. <div class="label-item">{{ item.startDateLabel || '起始时间' }}</div>
  198. <div class="label-item-value">
  199. <el-date-picker
  200. v-model="item.phenologyStartDate"
  201. type="date"
  202. :default-value="new Date(2026, 1, 1)"
  203. placeholder="选择时间"
  204. :disabled-date="disabledDate"
  205. :clearable="false"
  206. :editable="false"
  207. format="YYYY-MM-DD"
  208. value-format="YYYY-MM-DD"
  209. style="width: 100%"
  210. />
  211. </div>
  212. </div>
  213. </div>
  214. </div>
  215. </div>
  216. <div class="period-footer">
  217. <div class="period-footer-btn" @click="handlePeriodConfirm">确认信息</div>
  218. </div>
  219. </popup>
  220. <tip-popup
  221. v-model:show="showSuccessPopup"
  222. type="success"
  223. text="专属处方已经生成"
  224. buttonText="点击查看"
  225. @closedPopup="handleSuccessConfirm"
  226. @confirm="handleSuccessConfirm"
  227. >
  228. <template #default>
  229. <div class="expert-info">
  230. <!-- <img class="expert-img" src="@/assets/img/home/zj-1.png" alt=""> -->
  231. <el-avatar class="expert-img" :size="26" :src="basicForm?.expertInfo?.avatar" />
  232. {{ basicForm?.expertInfo?.name }} 专家的
  233. </div>
  234. </template>
  235. </tip-popup>
  236. </template>
  237. <script setup>
  238. import { ref, onActivated, onBeforeUnmount, onDeactivated, onMounted } from "vue";
  239. import { ElMessage } from "element-plus";
  240. import { useRouter, useRoute } from "vue-router";
  241. import tipPopup from "@/components/popup/tipPopup.vue";
  242. import { Popup } from "vant";
  243. import { useStore } from "vuex";
  244. const router = useRouter();
  245. const route = useRoute();
  246. const store = useStore();
  247. const outputList = ref([
  248. { name: "低温冻害" },
  249. { name: "干旱" },
  250. { name: "暴雨渍水" },
  251. { name: "病虫害" },
  252. { name: "阴天寡照" },
  253. ]);
  254. // 默认选中前两项
  255. const outputVal = ref([]);
  256. const disasterDesc = ref("");
  257. // 农场规模
  258. const farmScale = ref({
  259. regularWorkerCount: "",
  260. plantProtectionWorkerCount: "",
  261. pruningWorkerCount: "",
  262. graftingWorkerCount: "",
  263. tempDispatchWorkerCount: "",
  264. });
  265. // 初始化默认选中第一项
  266. onActivated(() => {
  267. getBasicFarmFormData();
  268. // 为 productList 的每个 group 设置默认选中第一项
  269. });
  270. onMounted(() => {
  271. getCurrentAndNextPhenology();
  272. });
  273. const loadingPage = ref(false);
  274. const basicForm = ref({
  275. soilTypes: "",
  276. irrigationMethods: [],
  277. machineryWithQuantity: [],
  278. improvementAreas: [],
  279. expertCode: "",
  280. expertInfo: {},
  281. });
  282. const basicFarmFormData = ref({});
  283. const getBasicFarmFormData = () => {
  284. loadingPage.value = true;
  285. VE_API.basic_farm.fetchBasicFarmFormData().then(({ data }) => {
  286. basicFarmFormData.value = data;
  287. // 根据返回的数据进行默认赋值
  288. // 1. 土壤类型 - 找到 selected: true 的项
  289. if (data.soilTypes && Array.isArray(data.soilTypes)) {
  290. const selectedSoilType = data.soilTypes.find(item => item.selected);
  291. if (selectedSoilType) {
  292. basicForm.value.soilTypes = selectedSoilType.code;
  293. }
  294. }
  295. // 2. 专家选项 - 找到 selected: true 的项
  296. if (data.expertOptions && Array.isArray(data.expertOptions)) {
  297. const selectedExpert = data.expertOptions.find(item => item.selected);
  298. if (selectedExpert) {
  299. basicForm.value.expertCode = selectedExpert.code;
  300. basicForm.value.expertInfo = selectedExpert;
  301. }
  302. }
  303. // 3. 农场规模数据
  304. if (data.farmScale) {
  305. farmScale.value = {
  306. regularWorkerCount: data.farmScale.regularWorkerCount || "",
  307. plantProtectionWorkerCount: data.farmScale.plantProtectionWorkerCount || "",
  308. pruningWorkerCount: data.farmScale.pruningWorkerCount || "",
  309. tempDispatchWorkerCount: data.farmScale.tempDispatchWorkerCount || "",
  310. graftingWorkerCount: data.farmScale.graftingWorkerCount || "",
  311. };
  312. }
  313. // 4. irrigationMethods, improvementAreas, machinery 的 selected 状态已经在 data 中设置好了
  314. // 这些数据会直接显示在页面上,因为页面是通过 basicFarmFormData 来渲染的
  315. }).finally(() => {
  316. loadingPage.value = false;
  317. });
  318. }
  319. // 收集表单数据并保存草稿
  320. const saveDraft = async () => {
  321. try {
  322. const draftData = handleSubmit();
  323. // 调用保存草稿接口
  324. await VE_API.basic_farm.saveDraft(draftData);
  325. } catch (error) {
  326. console.error("保存草稿失败:", error);
  327. // 静默失败,不显示错误提示
  328. }
  329. };
  330. // 离开页面时保存草稿
  331. onBeforeUnmount(() => {
  332. // saveDraft();
  333. });
  334. onDeactivated(() => {
  335. saveDraft();
  336. });
  337. const formNameObj = ref({
  338. irrigationMethods: '灌溉方式',
  339. crops: '作物',
  340. machinery: '农机设备',
  341. improvementAreas: '希望专家帮助解决的种植难题',
  342. soilTypes: '土壤类型',
  343. });
  344. const showAddPopup = ref(false);
  345. const addTypeKey = ref("");
  346. const popupInputVal = ref("");
  347. // 农机设备数量
  348. const popupInputNum = ref("");
  349. const machineryUnit = ref("");
  350. const isEditPopup = ref(false);
  351. const currentEditIndex = ref(-1);
  352. function handleAdd(type) {
  353. isEditPopup.value = false;
  354. showAddPopup.value = true;
  355. addTypeKey.value = type;
  356. popupInputVal.value = "";
  357. popupInputNum.value = "";
  358. }
  359. function handleMachineryAdd(type) {
  360. handleAdd(type);
  361. isMachineSelf.value = true;
  362. }
  363. function handleSelect(type, index, custom) {
  364. isMachineSelf.value = custom;
  365. if (custom === true) {
  366. showAddPopup.value = true;
  367. isEditPopup.value = true;
  368. addTypeKey.value = type;
  369. popupInputVal.value = basicFarmFormData.value[type][index].name;
  370. currentEditIndex.value = index;
  371. }
  372. basicFarmFormData.value[type][index].selected = !basicFarmFormData.value[type][index].selected;
  373. }
  374. // 农机设备
  375. const isMachineSelf = ref(false);
  376. function handleMachinerySelect(type, index, item) {
  377. showAddPopup.value = true;
  378. isMachineSelf.value = item.custom;
  379. isEditPopup.value = item.selected || item.custom;
  380. addTypeKey.value = type;
  381. popupInputVal.value = item.name;
  382. popupInputNum.value = item.quantity;
  383. machineryUnit.value = item.unit;
  384. currentEditIndex.value = index;
  385. }
  386. function handleConfirm() {
  387. if (addTypeKey.value === 'machinery') {
  388. if (!popupInputNum.value.trim()) {
  389. return;
  390. }
  391. if (isMachineSelf.value && !isEditPopup.value) {
  392. saveCustomOption(popupInputVal.value);
  393. } else {
  394. const option = basicFarmFormData.value[addTypeKey.value][currentEditIndex.value]
  395. option.name = popupInputVal.value;
  396. option.quantity = popupInputNum.value;
  397. option.selected = true;
  398. // 更新名称
  399. console.log('更新名称', basicFarmFormData.value[addTypeKey.value][currentEditIndex.value]);
  400. if (option.custom) {
  401. // updateCustomOption(popupInputVal.value, option.id || option.code);
  402. updateCustomOption(popupInputVal.value, basicFarmFormData.value[addTypeKey.value][currentEditIndex.value].id);
  403. }
  404. }
  405. currentEditIndex.value = -1;
  406. isEditPopup.value = false;
  407. showAddPopup.value = false;
  408. return;
  409. }
  410. if (!popupInputVal.value.trim()) {
  411. return;
  412. }
  413. if (isEditPopup.value) {
  414. const option = basicFarmFormData.value[addTypeKey.value][currentEditIndex.value]
  415. option.name = popupInputVal.value;
  416. currentEditIndex.value = -1;
  417. isEditPopup.value = false;
  418. showAddPopup.value = false;
  419. updateCustomOption(popupInputVal.value, option.id || option.code);
  420. return;
  421. }
  422. // basicFarmFormData.value[addTypeKey.value].push({ name: popupInputVal.value, code: popupInputVal.value, selected: true, custom: true });
  423. saveCustomOption(popupInputVal.value);
  424. showAddPopup.value = false;
  425. }
  426. const configTypeObj = {
  427. irrigationMethods: 'IRRIGATION',
  428. machinery: 'MACHINERY',
  429. }
  430. function saveCustomOption(name) {
  431. VE_API.basic_farm.addCustomOption({ configType: configTypeObj[addTypeKey.value], name: name }).then(({ data }) => {
  432. console.log(data);
  433. ElMessage.success('保存成功');
  434. basicFarmFormData.value[addTypeKey.value].push({ id: data.id, name: popupInputVal.value, code: popupInputVal.value, selected: true, custom: true, quantity: popupInputNum.value });
  435. });
  436. }
  437. function updateCustomOption(name, id) {
  438. VE_API.basic_farm.updateCustomOption({ id, configType: configTypeObj[addTypeKey.value], name: name }).then(({ data }) => {
  439. console.log(data);
  440. ElMessage.success('保存成功');
  441. });
  442. }
  443. function handleDelete() {
  444. const option = basicFarmFormData.value[addTypeKey.value][currentEditIndex.value]
  445. VE_API.basic_farm.deleteCustomOption({ optionId: option.id || option.code }).then(({ data }) => {
  446. ElMessage.success('删除成功');
  447. basicFarmFormData.value[addTypeKey.value].splice(currentEditIndex.value, 1);
  448. currentEditIndex.value = -1;
  449. showAddPopup.value = false;
  450. });
  451. }
  452. function handleCancelSelect() {
  453. const option = basicFarmFormData.value[addTypeKey.value][currentEditIndex.value]
  454. option.selected = false;
  455. option.quantity = null;
  456. currentEditIndex.value = -1;
  457. showAddPopup.value = false;
  458. }
  459. const goBack = () => {
  460. // saveDraft();
  461. // router.go(-1);
  462. router.replace(`/create_farm?from=${route.query.from}&type=${route.query.type}`)
  463. };
  464. function handleSubmit() {
  465. // 收集土壤类型(转换为数组)
  466. const soilTypes = basicForm.value.soilTypes
  467. ? (Array.isArray(basicForm.value.soilTypes) ? basicForm.value.soilTypes : [basicForm.value.soilTypes])
  468. : [];
  469. // 收集灌溉方式(获取选中的 code)
  470. const irrigationMethods = (basicFarmFormData.value.irrigationMethods || [])
  471. .filter(item => item.selected)
  472. .map(item => item.code || item.id);
  473. // 收集农机设备(包含 code 和 quantity)
  474. const machineryWithQuantity = (basicFarmFormData.value.machinery || [])
  475. .filter(item => item.selected && item.quantity)
  476. .map(item => ({
  477. code: item.code || item.id,
  478. quantity: Number(item.quantity) || 0
  479. }));
  480. // 收集希望专家帮助解决的种植难题(获取选中的 code)
  481. const improvementAreas = (basicFarmFormData.value.improvementAreas || [])
  482. .filter(item => item.selected)
  483. .map(item => item.code || item.id);
  484. // 收集农场规模数据
  485. const draftData = {
  486. soilTypes: soilTypes.map(code => Number(code)),
  487. irrigationMethods: irrigationMethods.map(code => Number(code)),
  488. machineryWithQuantity: machineryWithQuantity.map(item => ({
  489. code: Number(item.code),
  490. quantity: item.quantity
  491. })),
  492. regularWorkerCount: Number(farmScale.value.regularWorkerCount) || 0,
  493. plantProtectionWorkerCount: Number(farmScale.value.plantProtectionWorkerCount) || 0,
  494. pruningWorkerCount: Number(farmScale.value.pruningWorkerCount) || 0,
  495. graftingWorkerCount: Number(farmScale.value.graftingWorkerCount) || 0,
  496. tempDispatchWorkerCount: Number(farmScale.value.tempDispatchWorkerCount) || 0,
  497. improvementAreas: improvementAreas.map(code => Number(code)),
  498. };
  499. // 如果有选择的专家,添加 preferredExpertCode
  500. if (basicForm.value.expertCode) {
  501. draftData.preferredExpertCode = Number(basicForm.value.expertCode);
  502. }
  503. return draftData;
  504. }
  505. // 校验必填项
  506. function validateForm() {
  507. // 校验土壤类型
  508. if (!basicForm.value.soilTypes) {
  509. ElMessage.warning('请选择您的果园土壤类型');
  510. return false;
  511. }
  512. // 校验灌溉方式
  513. const irrigationMethods = (basicFarmFormData.value.irrigationMethods || [])
  514. .filter(item => item.selected);
  515. if (irrigationMethods.length === 0) {
  516. ElMessage.warning('请选择您的灌溉方式');
  517. return false;
  518. }
  519. // 校验农机设备
  520. const machineryWithQuantity = (basicFarmFormData.value.machinery || [])
  521. .filter(item => item.selected && item.quantity);
  522. if (machineryWithQuantity.length === 0) {
  523. ElMessage.warning('请选择您的农机设备并填写数量');
  524. return false;
  525. }
  526. // 校验希望专家帮助解决的种植难题
  527. const improvementAreas = (basicFarmFormData.value.improvementAreas || [])
  528. .filter(item => item.selected);
  529. if (improvementAreas.length === 0) {
  530. ElMessage.warning('请选择希望专家帮助解决的种植难题');
  531. return false;
  532. }
  533. // 校验专家选项
  534. if (!basicForm.value.expertCode) {
  535. ElMessage.warning('请选择您最希望得到哪位荔枝专家的农事处方');
  536. return false;
  537. }
  538. return true;
  539. }
  540. const showSuccessPopup = ref(false);
  541. const handleSuccessConfirm = () => {
  542. router.push('/agri_record');
  543. }
  544. async function submit() {
  545. try {
  546. // 正式提交
  547. const draftData = handleSubmit();
  548. const params = {
  549. ...route.query,
  550. phenologyId: phenologyList.value[0].phenologyId,
  551. phenologyStartDate: phenologyList.value[0].phenologyStartDate,
  552. basicInfo: draftData,
  553. expertMiniUserId: '81881',
  554. }
  555. const res = await VE_API.basic_farm.saveBasicFarmInfoByExpert(params);
  556. if (res.code === 0) {
  557. showSuccessPopup.value = true;
  558. // 设置选中当前新增的农场
  559. localStorage.setItem("selectedFarmId", res.data.id);
  560. localStorage.setItem("selectedFarmName", res.data.name);
  561. return true;
  562. } else {
  563. ElMessage.error(res.msg || '提交失败,请重试');
  564. return false;
  565. }
  566. } catch (error) {
  567. console.error('提交失败:', error);
  568. ElMessage.error('提交失败,请重试');
  569. return false;
  570. }
  571. }
  572. const showPeriodPopup = ref(false);
  573. const phenologyList = ref([]);
  574. const disabledDate = (time) => {
  575. // 获取今天的开始时间(00:00:00)
  576. const today = new Date();
  577. today.setHours(0, 0, 0, 0);
  578. // 获取明天的开始时间(00:00:00)
  579. const tomorrow = new Date(today);
  580. tomorrow.setDate(tomorrow.getDate() + 1);
  581. // 设置最小日期为 2025-01-01
  582. const minDate = new Date(2025, 10, 1); // 月份从0开始,0表示1月
  583. minDate.setHours(0, 0, 0, 0);
  584. // 如果时间 < 2025-10-01 或 >= 明天的开始时间,则禁用
  585. // 只能选择 2025-10-01 到今天的日期范围
  586. return time.getTime() < minDate.getTime() || time.getTime() >= tomorrow.getTime();
  587. }
  588. const handlePage = async () => {
  589. try {
  590. // 先进行校验
  591. if (!validateForm()) {
  592. return;
  593. }
  594. // 安全解析 typeNames
  595. let typeNames = [];
  596. try {
  597. if (route.query.typeNames) {
  598. typeNames = JSON.parse(route.query.typeNames);
  599. } else {
  600. ElMessage.warning('缺少必要参数,请重新进入页面');
  601. return;
  602. }
  603. } catch (e) {
  604. console.error('解析 typeNames 失败:', e);
  605. ElMessage.warning('参数格式错误,请重新进入页面');
  606. return;
  607. }
  608. // 检查 firstPhenology 是否已初始化
  609. if (!firstPhenology.value || !firstPhenology.value.phenologyId) {
  610. ElMessage.warning('物候期数据未加载完成,请稍候再试');
  611. return;
  612. }
  613. phenologyList.value = typeNames.map(item => ({
  614. typeName: item,
  615. phenologyId: firstPhenology.value.phenologyId,
  616. startDateLabel: firstPhenology.value.startDateLabel,
  617. phenologyName: firstPhenology.value.phenologyName,
  618. phenologyStartDate: '2026-01-01',
  619. }));
  620. // 如果有选择的专家,添加 preferredExpertCode
  621. if (basicForm.value.expertCode) {
  622. const selectedExpert = basicFarmFormData.value.expertOptions?.find(item => item.code === basicForm.value.expertCode);
  623. if (selectedExpert) {
  624. basicForm.value.expertInfo = selectedExpert;
  625. }
  626. }
  627. showPeriodPopup.value = true;
  628. } catch (error) {
  629. console.error('handlePage 执行失败:', error);
  630. ElMessage.error('操作失败,请重试');
  631. }
  632. };
  633. // 获取当前日期(YYYY-MM-DD格式)
  634. const getTodayDate = () => {
  635. const today = new Date();
  636. const year = today.getFullYear();
  637. const month = String(today.getMonth() + 1).padStart(2, "0");
  638. const day = String(today.getDate()).padStart(2, "0");
  639. return `${year}-${month}-${day}`;
  640. };
  641. const firstPhenology = ref({});
  642. const phenologyData = ref({});
  643. // 获取当前和下一个物候期
  644. const getCurrentAndNextPhenology = async () => {
  645. try {
  646. const { data } = await VE_API.home.getCurrentAndNextPhenology({
  647. expertMiniUserId: '81881',
  648. containerId: route.query.containerId,
  649. });
  650. if (data && Array.isArray(data)) {
  651. // 初始化物候期表单数据,日期使用第一个物候期的 startDate
  652. firstPhenology.value = data[0];
  653. }
  654. } catch (error) {
  655. console.error("获取物候期数据失败", error);
  656. ElMessage.error("获取物候期数据失败");
  657. }
  658. };
  659. // 确认物候期信息
  660. const handlePeriodConfirm = async () => {
  661. // 校验所有物候期信息是否填写完整
  662. const hasEmptyDate = phenologyList.value.some(item => !item.phenologyStartDate);
  663. if (hasEmptyDate) {
  664. ElMessage.warning('请选择时间');
  665. return;
  666. }
  667. // 关闭弹窗并提交表单
  668. showPeriodPopup.value = false;
  669. loadingPage.value = true;
  670. // 正式提交
  671. const success = await submit();
  672. loadingPage.value = false;
  673. if (!success) {
  674. return;
  675. }
  676. // 提交成功后跳转
  677. // if (route.query.type === 'farmer') {
  678. // router.push('/agri_record')
  679. // return
  680. // }
  681. // // 获取所有需要传递的参数,包括 from 参数
  682. // const queryParams = {
  683. // containerId: route.query.containerId,
  684. // };
  685. // // 如果存在 from 参数,继续传递
  686. // if (route.query.from) {
  687. // queryParams.from = route.query.from;
  688. // }
  689. // // 传递所有农场相关的参数,以便在 agricultural_plan 页面创建农场
  690. // const farmParams = ['wkt', 'speciesId', 'containerId', 'agriculturalCreate', 'geom', 'address', 'mu', 'name', 'fzr', 'tel', 'defaultFarm', 'typeId', 'speciesName', 'userType'];
  691. // farmParams.forEach(key => {
  692. // if (route.query[key] !== undefined) {
  693. // queryParams[key] = route.query[key];
  694. // }
  695. // });
  696. // router.push({
  697. // path: '/agricultural_plan',
  698. // query: queryParams
  699. // });
  700. };
  701. </script>
  702. <style lang="scss" scoped>
  703. .prescription-page {
  704. position: relative;
  705. width: 100%;
  706. height: calc(100vh - 62px);
  707. overflow: auto;
  708. box-sizing: border-box;
  709. background: linear-gradient(to left, #e6f2ff, #8fc5fe);
  710. .prescription-title {
  711. padding: 16px 14px;
  712. background: url("@/assets/img/home/page-bg.png") no-repeat bottom right / 149px 116px;
  713. background-position-y: 30px;
  714. img {
  715. width: 24px;
  716. }
  717. .title-name {
  718. font-size: 22px;
  719. color: #2e2e2e;
  720. text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.05);
  721. font-weight: 500;
  722. padding: 21px 0 4px 6px;
  723. }
  724. .title-desc {
  725. font-size: 14px;
  726. color: rgba(49, 49, 49, 0.56);
  727. padding-left: 6px;
  728. }
  729. }
  730. .prescription-box {
  731. background: #ffffff;
  732. box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1);
  733. border-radius: 10px;
  734. margin: 0 10px 10px;
  735. box-sizing: border-box;
  736. padding-bottom: 10px;
  737. .box-title {
  738. margin: 0 10px;
  739. box-sizing: border-box;
  740. border-bottom: 1px solid rgba(0, 0, 0, 0.15);
  741. font-weight: 800;
  742. font-size: 18px;
  743. color: #222222;
  744. padding: 15px 0 10px;
  745. img {
  746. width: 14px;
  747. height: 9px;
  748. }
  749. }
  750. .pt-16 {
  751. padding-top: 16px;
  752. }
  753. .box-content {
  754. padding: 0 10px;
  755. .box-item {
  756. padding-top: 16px;
  757. .item-name {
  758. display: flex;
  759. align-items: center;
  760. font-size: 14px;
  761. color: rgba(0, 0, 0, 0.9);
  762. font-weight: 500;
  763. .required-icon {
  764. color: #ff0000;
  765. font-size: 16px;
  766. margin-right: 4px;
  767. }
  768. .sub-name {
  769. font-size: 12px;
  770. font-weight: 400;
  771. }
  772. }
  773. .tips {
  774. // margin-left: 10px;
  775. font-size: 12px;
  776. color: rgba(0, 0, 0, 0.4);
  777. }
  778. .item-checkbox {
  779. .tag-group {
  780. display: grid;
  781. grid-template-columns: repeat(3, 1fr);
  782. gap: 0 7px;
  783. font-size: 14px;
  784. .tag-item {
  785. margin-top: 10px;
  786. position: relative;
  787. border-radius: 4px;
  788. color: #2199f8;
  789. // padding: 0 4px;
  790. box-sizing: border-box;
  791. // min-width: 26vw;
  792. height: 40px;
  793. text-align: center;
  794. line-height: 40px;
  795. cursor: pointer;
  796. transition: all 0.3s;
  797. .quantity-text {
  798. font-size: 10px;
  799. color: #fff;
  800. position: absolute;
  801. right: -7px;
  802. top: -7px;
  803. width: 14px;
  804. text-align: center;
  805. line-height: 14px;
  806. height: 14px;
  807. border-radius: 50%;
  808. background: #2199F8;
  809. }
  810. .text {
  811. display: inline-flex;
  812. align-items: center;
  813. }
  814. .edit-icon {
  815. margin-left: 8px;
  816. }
  817. .del-icon {
  818. position: absolute;
  819. right: -8px;
  820. top: -8px;
  821. background: #2199F8;
  822. border-radius: 50%;
  823. width: 16px;
  824. height: 16px;
  825. font-size: 10px;
  826. display: flex;
  827. align-items: center;
  828. justify-content: center;
  829. color: #fff;
  830. }
  831. &.selected {
  832. border: 1px solid #2199F8;
  833. background: #E8F5FF;
  834. color: #2199F8;
  835. &::after {
  836. content: "";
  837. position: absolute;
  838. z-index: 9;
  839. bottom: -1px;
  840. right: 0;
  841. width: 18px;
  842. height: 13px;
  843. background: url("@/assets/img/home/checked-bg.png") no-repeat bottom right / 18px 13px;
  844. }
  845. }
  846. }
  847. &.add-tag-group {
  848. .tag-item {
  849. color: #000000;
  850. background: rgba(241, 241, 241, 0.12);
  851. border: 1px solid #EBEBEB;
  852. cursor: pointer;
  853. &.self {
  854. border: 1px solid #2199F8;
  855. background: #E8F5FF;
  856. color: #2199F8;
  857. &::after {
  858. content: "";
  859. position: absolute;
  860. z-index: 9;
  861. bottom: -1px;
  862. right: 0;
  863. width: 18px;
  864. height: 13px;
  865. background: url("@/assets/img/home/checked-bg.png") no-repeat bottom right / 18px 13px;
  866. }
  867. }
  868. &.selected {
  869. border: 1px solid #2199F8;
  870. background: #E8F5FF;
  871. color: #2199F8;
  872. }
  873. &.last-add {
  874. // background: #F7F7F7;
  875. // color: #343434;
  876. // border: none;
  877. display: flex;
  878. align-items: center;
  879. justify-content: center;
  880. cursor: pointer;
  881. .add-icon {
  882. font-size: 14px;
  883. font-weight: bold;
  884. margin-right: 3px;
  885. }
  886. }
  887. }
  888. }
  889. }
  890. ::v-deep {
  891. .el-radio-button,
  892. .el-checkbox-button {
  893. margin: 10px 0 0 7px;
  894. .el-radio-button__inner,
  895. .el-checkbox-button__inner {
  896. border: none;
  897. border-radius: 6px;
  898. // padding: 13px 21px;
  899. height: 40px;
  900. width: 107px;
  901. display: flex;
  902. align-items: center;
  903. justify-content: center;
  904. border: 1px solid #EBEBEB;
  905. color: #000000;
  906. background: rgba(241, 241, 241, 0.12);
  907. font-weight: 400;
  908. }
  909. &.is-active,
  910. &.is-checked {
  911. &::after {
  912. content: "";
  913. position: absolute;
  914. z-index: 9;
  915. bottom: -0.5px;
  916. right: 0;
  917. width: 18px;
  918. height: 13px;
  919. background: url("@/assets/img/home/checked-bg.png") no-repeat bottom right / 18px 13px;
  920. }
  921. .el-radio-button__inner,
  922. .el-checkbox-button__inner {
  923. background: rgba(33, 153, 248, 0.1) !important;
  924. color: #2199f8 !important;
  925. border: 1px solid #2199f8 !important;
  926. box-shadow: none;
  927. }
  928. }
  929. &.is-active {
  930. .el-radio-button__original-radio:not(:disabled)+.el-radio-button__inner {
  931. background: rgba(33, 153, 248, 0.1) !important;
  932. color: #2199f8 !important;
  933. border: 1px solid #2199f8 !important;
  934. box-shadow: none;
  935. font-weight: 500;
  936. }
  937. }
  938. &.is-checked {
  939. .el-checkbox-button__original-checkbox:not(:disabled)+.el-checkbox-button__inner {
  940. background: rgba(33, 153, 248, 0.1) !important;
  941. color: #2199f8 !important;
  942. border: 1px solid #2199f8 !important;
  943. box-shadow: none;
  944. font-weight: 500;
  945. }
  946. }
  947. }
  948. .el-radio-button:nth-child(3n-2) {
  949. margin-left: 0;
  950. }
  951. }
  952. }
  953. }
  954. }
  955. /* 农场规模(与农场情况同一卡片内) */
  956. .farm-scale-header {
  957. margin-top: 16px;
  958. .farm-scale-title {
  959. font-size: 15px;
  960. font-weight: 500;
  961. color: rgba(0, 0, 0, 0.9);
  962. }
  963. .farm-scale-desc {
  964. font-size: 13px;
  965. color: rgba(0, 0, 0, 0.4);
  966. }
  967. }
  968. .farm-scale-form {
  969. margin-top: 10px;
  970. border: 0.5px solid rgba(33, 153, 248, 0.5);
  971. border-radius: 8px;
  972. padding: 10px;
  973. .farm-scale-item {
  974. display: flex;
  975. align-items: center;
  976. margin-bottom: 14px;
  977. &:last-child {
  978. margin-bottom: 0;
  979. }
  980. .farm-scale-label {
  981. flex-shrink: 0;
  982. width: 116px;
  983. font-size: 14px;
  984. color: rgba(0, 0, 0, 0.9);
  985. // text-align: right;
  986. margin-right: 10px;
  987. .sub-label {
  988. font-size: 10px;
  989. color: rgba(0, 0, 0, 0.4);
  990. line-height: 15px;
  991. }
  992. }
  993. .farm-scale-input {
  994. flex: 1;
  995. ::v-deep .el-input-group__append {
  996. padding: 0 12px;
  997. background: #f5f7fa;
  998. color: rgba(0, 0, 0, 0.65);
  999. border-color: #dcdfe6;
  1000. border-radius: 0 4px 4px 0;
  1001. }
  1002. ::v-deep .el-input__inner {
  1003. border-radius: 4px 0 0 4px;
  1004. }
  1005. }
  1006. }
  1007. }
  1008. }
  1009. .disaster-desc-box {
  1010. padding: 10px;
  1011. display: flex;
  1012. align-items: center;
  1013. span {
  1014. font-size: 14px;
  1015. color: rgba(0, 0, 0, 0.9);
  1016. width: 80px;
  1017. }
  1018. }
  1019. }
  1020. .add-tag-popup {
  1021. width: 90%;
  1022. padding: 24px 16px 20px 16px;
  1023. background: linear-gradient(360deg, #FFFFFF 74.2%, #D1EBFF 100%);
  1024. .popup-title {
  1025. font-size: 16px;
  1026. font-weight: 400;
  1027. margin-bottom: 12px;
  1028. color: #000000;
  1029. .name-text{
  1030. font-weight: 500;
  1031. color: #2199F8;
  1032. padding: 0 2px;
  1033. }
  1034. }
  1035. .ml-2 {
  1036. margin-left: 3px;
  1037. }
  1038. .popup-input {
  1039. margin-bottom: 24px;
  1040. }
  1041. .popup-button {
  1042. display: flex;
  1043. div {
  1044. flex: 1;
  1045. font-size: 16px;
  1046. padding: 9px;
  1047. border-radius: 20px;
  1048. background: #2199F8;
  1049. color: #fff;
  1050. text-align: center;
  1051. cursor: pointer;
  1052. }
  1053. .cancel {
  1054. margin-right: 13px;
  1055. color: #000;
  1056. background: #fff;
  1057. border: 1px solid #999999;
  1058. }
  1059. .delete {
  1060. margin-right: 13px;
  1061. color: #FF3D3D;
  1062. background: #fff;
  1063. border: 1px solid rgba(255, 61, 61, 0.4);
  1064. }
  1065. }
  1066. }
  1067. .period-popup {
  1068. width: 90%;
  1069. padding: 24px 16px 20px 16px;
  1070. background: linear-gradient(360deg, #FFFFFF 74.2%, #D1EBFF 100%);
  1071. .period-header {
  1072. font-size: 16px;
  1073. font-weight: 400;
  1074. margin-bottom: 16px;
  1075. color: #121212;
  1076. line-height: 22px;
  1077. }
  1078. .period-content {
  1079. margin-bottom: 16px;
  1080. .period-item {
  1081. background: rgba(33, 153, 248, 0.05);
  1082. border: 1px solid rgba(33, 153, 248, 0.2);
  1083. border-radius: 5px;
  1084. padding: 10px;
  1085. margin-bottom: 16px;
  1086. position: relative;
  1087. &:last-child {
  1088. margin-bottom: 0;
  1089. }
  1090. .period-item-name {
  1091. background: rgba(33, 153, 248, 0.1);
  1092. font-size: 12px;
  1093. color: #2199F8;
  1094. padding: 2px 10px;
  1095. border-radius: 2px;
  1096. font-weight: 400;
  1097. width: fit-content;
  1098. }
  1099. .period-item-label {
  1100. margin-top: 10px;
  1101. .label-item-group {
  1102. display: flex;
  1103. align-items: center;
  1104. margin-bottom: 12px;
  1105. &:last-child {
  1106. margin-bottom: 0;
  1107. }
  1108. .label-item {
  1109. font-size: 14px;
  1110. color: #1D2129;
  1111. min-width: 100px;
  1112. flex-shrink: 0;
  1113. padding-right: 10px;
  1114. }
  1115. .label-item-value {
  1116. border-radius: 2px;
  1117. flex: 1;
  1118. font-size: 14px;
  1119. color: #1D2129;
  1120. min-height: 31px;
  1121. line-height: 31px;
  1122. display: flex;
  1123. align-items: center;
  1124. &.period-display {
  1125. background: #FFFFFF;
  1126. border: 0.5px solid rgba(0, 0, 0, 0.2);
  1127. padding: 0 8px;
  1128. }
  1129. }
  1130. }
  1131. }
  1132. }
  1133. }
  1134. .period-footer {
  1135. .period-footer-btn {
  1136. width: 100%;
  1137. background: #2199F8;
  1138. color: #ffffff;
  1139. font-size: 16px;
  1140. text-align: center;
  1141. height: 40px;
  1142. line-height: 40px;
  1143. border-radius: 4px;
  1144. cursor: pointer;
  1145. font-weight: 500;
  1146. &:active {
  1147. opacity: 0.8;
  1148. }
  1149. }
  1150. }
  1151. }
  1152. .expert-info {
  1153. display: flex;
  1154. align-items: center;
  1155. justify-content: center;
  1156. font-size: 20px;
  1157. line-height: 36px;
  1158. .expert-img {
  1159. // width: 26px;
  1160. // height: 26px;
  1161. // border-radius: 50%;
  1162. // object-fit: cover;
  1163. margin-right: 5px;
  1164. }
  1165. }
  1166. // 优化按钮点击体验,解决移动端点击无响应问题
  1167. .custom-bottom-fixed-btns {
  1168. z-index: 99 !important; // 提高 z-index,确保不被其他元素遮挡
  1169. pointer-events: auto; // 确保可以接收点击事件
  1170. .bottom-btn {
  1171. cursor: pointer;
  1172. user-select: none; // 防止文本选择
  1173. touch-action: manipulation; // 优化移动端触摸响应
  1174. -webkit-tap-highlight-color: transparent; // 移除移动端点击高亮
  1175. position: relative; // 确保点击区域正确
  1176. z-index: 1;
  1177. // 添加点击反馈效果
  1178. &:active {
  1179. opacity: 0.8;
  1180. transform: scale(0.98);
  1181. }
  1182. &.primary-btn {
  1183. // 确保按钮可点击
  1184. pointer-events: auto;
  1185. }
  1186. }
  1187. }
  1188. </style>
  1189. <style lang="scss">
  1190. .el-message--warning .el-message__content {
  1191. line-height: 20px;
  1192. }
  1193. </style>