completedWork.vue 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. <template>
  2. <div class="completed-work">
  3. <custom-header name="农事详情" :isClose="showBack" :showClose="false"></custom-header>
  4. <div class="work-content">
  5. <!-- <div class="step-wrap" v-show="query.status !== 'warning'">
  6. <farm-steps :currentStep="currentStep" />
  7. </div> -->
  8. <div class="content-status">
  9. <div class="status-l">
  10. <div class="stauts-text">待完成</div>
  11. <div class="stauts-sub-text" v-if="detailData?.executeDeadlineDate && !detailData?.expectedExecuteDate">
  12. 截止到 <span class="time-text">{{ detailData?.executeDeadlineDate }}</span>
  13. </div>
  14. <div class="stauts-sub-text" v-if="detailData?.expectedExecuteDate">
  15. <template v-if="daysDiff > 0">
  16. 距离执行时间还剩 <span class="time-text">{{ daysDiff }} 天</span>
  17. </template>
  18. <template v-else-if="daysDiff === 0">
  19. 执行时间为今天
  20. </template>
  21. <template v-else>
  22. 执行时间已过
  23. </template>
  24. </div>
  25. </div>
  26. <!-- <template v-if="query.status !== 'warning'">
  27. <div class="status-r" v-if="curRole == 0">{{ status === 0 ? "设置提醒" : "去评价" }}</div>
  28. <div class="status-r" v-if="curRole == 1">{{ status === 0 ? "提醒执行" : "提醒复核" }}</div>
  29. <div class="status-r" v-if="curRole == 2">{{ status === 0 ? "提醒执行" : "提醒复核" }}</div>
  30. </template> -->
  31. </div>
  32. <div class="work-wrap" v-if="query?.farmWorkOrderId || detailData?.flowStatus === 4">
  33. <div class="box-wrap executor-info" v-if="query.status === 'warning' || curRole == 0 || curRole == 2">
  34. <!-- <div class="executor-title">执行人</div> -->
  35. <div class="executor-content">
  36. <div class="executor-info mt-0">
  37. <div class="executor-avatar">
  38. <img
  39. src="https://birdseye-img-ali-cdn.sysuimars.com/16926861-1e20-4cbd-8bf2-90208db5a2d0/806080da-1a30-4b5b-b64b-b22e722c6cb6/DJI_202509010800_001_806080da-1a30-4b5b-b64b-b22e722c6cb6/DJI_20250901080536_0045_V_code-ws0fsmge97gh.jpeg?x-oss-process=image/resize,w_500"
  40. alt="执行人"
  41. />
  42. </div>
  43. <div class="executor-details">
  44. <div class="org-name">
  45. <span class="name">{{ quotationData.agriculturalStoreName || "--" }}</span>
  46. <span class="rating">{{
  47. quotationData.score ? quotationData.score + "分" : ""
  48. }}</span>
  49. </div>
  50. <div class="service-info">
  51. <div class="service-item">
  52. 服务品种:
  53. <span v-if="speciesList.length">
  54. <span v-for="(sp, sIdx) in speciesList" :key="sIdx">
  55. {{ sp }}<template v-if="sIdx < speciesList.length - 1">、</template>
  56. </span>
  57. </span>
  58. <span v-else>--</span>
  59. </div>
  60. <div class="service-item">
  61. 服务设备:
  62. <span v-if="equipmentList.length">
  63. <span v-for="(eq, eIdx) in equipmentList" :key="eIdx">
  64. {{ eq }}<template v-if="eIdx < equipmentList.length - 1">、</template>
  65. </span>
  66. </span>
  67. <span v-else>--</span>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. <div class="farm-info">
  73. <div class="info-title-wrap">
  74. <div class="sub-title">药肥费用</div>
  75. <div class="info-more">
  76. {{ pesticideCostTotal ? formatArea(pesticideCostTotal) : "--"
  77. }}<span class="unit-text">元</span>
  78. </div>
  79. </div>
  80. <div class="info-content-wrap">
  81. <price-table
  82. :prescriptionData="quotationData.prescriptionList"
  83. :area="detailData?.area"
  84. :isArrList="true"
  85. >
  86. <template #bottomContent>
  87. <div class="price-bottom">
  88. <div class="info-title-wrap">
  89. <div class="sub-title">服务费用</div>
  90. <div class="info-more">
  91. {{ detailData?.serviceMuPrice ? serviceCostTotal : "--" }}
  92. <span class="unit-text">元</span>
  93. </div>
  94. </div>
  95. <div class="price-info">
  96. <div class="info-l">
  97. 单亩价格<span class="main-text">{{
  98. detailData?.serviceMuPrice
  99. ? detailData?.serviceMuPrice + "元/亩"
  100. : "--"
  101. }}</span>
  102. </div>
  103. </div>
  104. <div class="price-total">
  105. 报价合计:<span class="main-val">
  106. {{ totalCost ? formatArea(totalCost) : "--" }} </span
  107. >元
  108. </div>
  109. </div>
  110. </template>
  111. </price-table>
  112. </div>
  113. </div>
  114. <!-- <div class="contact-buttons">
  115. <button class="contact-btn">电话联系</button>
  116. <button class="contact-btn">在线联系</button>
  117. </div> -->
  118. </div>
  119. </div>
  120. </div>
  121. <div class="work-wrap info-wrap">
  122. <div class="box-wrap farm-data">
  123. <div class="card-title">
  124. <div>{{ detailData?.farmWorkName }}<span class="type-tag">标准农事</span></div>
  125. <!-- <div class="point-wrap">
  126. <div class="point"></div>
  127. <span>2区</span>
  128. </div> -->
  129. <div class="tag-text">托管农事</div>
  130. </div>
  131. <div class="data-content">
  132. <div class="form-item">
  133. <div class="item-name">农事编号</div>
  134. <div class="item-text">{{ detailData?.code }}</div>
  135. </div>
  136. <div class="form-item">
  137. <div class="item-name">服务亩数</div>
  138. <div class="item-text">
  139. {{ detailData?.area ? formatArea(detailData?.area) + "亩" : "--" }}
  140. </div>
  141. </div>
  142. <div class="form-item">
  143. <div class="item-name">服务区域</div>
  144. <div class="item-text">{{ detailData?.serviceRegion }}</div>
  145. </div>
  146. <!-- <div class="form-item">
  147. <div class="item-name">触发条件</div>
  148. <div class="item-text">{{ detailData?.condition }}</div>
  149. </div> -->
  150. <div class="form-item" v-if="detailData?.expectedExecuteDate">
  151. <div class="item-name">执行时间</div>
  152. <div class="item-text">
  153. {{detailData?.expectedExecuteDate}}
  154. </div>
  155. </div>
  156. <div class="form-item" v-else-if="detailData?.executeDeadlineDate">
  157. <div class="item-name">截止日期</div>
  158. <div class="item-text">
  159. {{detailData?.executeDeadlineDate}}
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. <div class="box-wrap farm-table">
  165. <div class="card-title">药物处方</div>
  166. <div class="table-item">
  167. <div class="form-item">
  168. <div class="item-name">施用方式</div>
  169. <div class="item-text">{{ detailData?.usageMode }}</div>
  170. </div>
  171. <div class="form-item">
  172. <div class="item-name">执行方式</div>
  173. <div class="item-text">
  174. {{ quotationData?.executionMethodName }}
  175. </div>
  176. </div>
  177. </div>
  178. <div class="new-wrap">
  179. <div class="new-title">
  180. <div class="title-1"><div class="table-name">药肥类型</div></div>
  181. <div class="title-2"><div class="table-name">药肥名称</div></div>
  182. <div class="title-4"><div class="table-name">药肥配比</div></div>
  183. <div class="title-5"><div class="table-name">单亩用量</div></div>
  184. </div>
  185. <div
  186. class="new-table-wrap"
  187. v-for="(prescriptionItem, prescriptionI) in detailData?.prescriptionList"
  188. :key="prescriptionI"
  189. >
  190. <div
  191. class="new-prescription"
  192. v-for="(subP, subI) in prescriptionItem.pesticideFertilizerList"
  193. :key="subI"
  194. >
  195. <div class="new-table">
  196. <div class="line-l">
  197. <div class="line-1 title-1">{{ subP.typeName }}</div>
  198. <div class="line-2">{{ subP.defaultName || subP.pesticideFertilizerName }}</div>
  199. </div>
  200. <div class="line-r">
  201. <div class="line-3">
  202. <div class="sub-line title-4">{{ quotationData.executionMethod === 1 ? subP.ratio2 : subP.ratio }}倍</div>
  203. <div class="sub-line title-5">{{ quotationData.executionMethod === 1 ? subP.muUsage2 : subP.muUsage }}{{ subP.unit }}</div>
  204. </div>
  205. </div>
  206. </div>
  207. <div class="note-text" v-if="subP.remark">{{ subP.remark }}</div>
  208. </div>
  209. </div>
  210. </div>
  211. </div>
  212. <!-- <div class="work-map">
  213. <div class="card-title">执行农事区域</div>
  214. <div class="map-content">
  215. <div class="map-dom" ref="areaRef"></div>
  216. </div>
  217. </div> -->
  218. </div>
  219. <!-- 农资,步骤:农资已执行,请求确认 -->
  220. <div class="fixed-btn-wrap" :class="{ 'center': !getButtonText() }" v-if="curRole == 2 && !detailData?.expectedExecuteDate">
  221. <div class="fixed-btn orange primary-text" @click="handleRemindExecuteTime" v-if="getButtonText()">提醒确认执行时间</div>
  222. <div class="fixed-btn orange" @click="selectExecuteTime">确认执行时间</div>
  223. </div>
  224. <div class="fixed-btn-wrap" :class="{ 'center': !getButtonText() }" v-if="curRole == 2 && detailData?.expectedExecuteDate">
  225. <div class="fixed-btn primary primary-text width-120" @click="handleExecute" v-if="getButtonText()">提醒执行</div>
  226. <div class="fixed-btn primary" @click="showOfferPopup(detailData)">上传照片</div>
  227. </div>
  228. <!-- 农资,步骤:农事已确认 -->
  229. <!-- <div class="fixed-btn-wrap" v-if="curRole == 2 && detailData?.expectedExecuteDate">
  230. <div class="fixed-btn second" @click="showPriceSheetPopup">生成报价单</div>
  231. <div class="fixed-btn" @click="handleTimelineAction(detailData)">转入农事任务</div>
  232. </div> -->
  233. </div>
  234. </div>
  235. <!-- 报价弹窗 -->
  236. <!-- <offer-popup :showPopup="showPopup" :executionData="executionData"></offer-popup> -->
  237. <!-- 服务报价单 -->
  238. <price-sheet-popup ref="priceSheetPopupRef"></price-sheet-popup>
  239. <!-- 需求发送成功 -->
  240. <popup v-model:show="showTaskPopup" round class="task-tips-popup">
  241. <template v-if="taskPopupType === 'warning'">
  242. <img class="create-farm-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
  243. <div class="create-farm-text">
  244. <div>您确认取消已发起的农事需求吗</div>
  245. </div>
  246. </template>
  247. <template v-else-if="taskPopupType === 'warningIgnore'">
  248. <img class="create-farm-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
  249. <div class="create-farm-text">
  250. <div>
  251. 您确认忽略 <span class="main-text">{{ detailData?.farmName }}</span> 的
  252. <span class="main-text">{{ detailData?.farmWorkName }}</span> 农事吗
  253. </div>
  254. </div>
  255. </template>
  256. <template v-else>
  257. <img class="farm-check-icon" src="@/assets/img/home/right.png" alt="" />
  258. <div class="create-farm-text success-text">{{ successText }}</div>
  259. </template>
  260. <div class="create-farm-btn" @click="handlePopupBtn">
  261. {{ taskPopupType === "warning" ? "确认" : taskPopupType === "warningIgnore" ? "确认忽略" : "我知道了" }}
  262. </div>
  263. </popup>
  264. <!-- 确认执行时间 -->
  265. <calendar v-model:show="showCalendar" @confirm="onConfirmExecuteTime" :min-date="minDate" :max-date="maxDate" />
  266. <!-- 上传照片弹窗 -->
  267. <review-upload-popup :key="10" v-model="showUpload" :record-id="sectionId" @success="handleSelfDone" />
  268. <!-- 新增:激活上传弹窗 -->
  269. <active-upload-popup ref="activeUploadPopupRef" @handleUploadSuccess="handleUploadSuccess"></active-upload-popup>
  270. <offer-popup ref="offerPopupRef" @uploadSuccess="handleUploadSuccess"></offer-popup>
  271. </template>
  272. <script setup>
  273. import customHeader from "@/components/customHeader.vue";
  274. import { ref, computed, onActivated, onDeactivated } from "vue";
  275. // import NewFarmMap from "./newFarmMap";
  276. import { useStore } from "vuex";
  277. import { Popup, Calendar } from "vant";
  278. import offerPopup from "@/components/popup/offerPopup.vue";
  279. import { useRouter, useRoute } from "vue-router";
  280. import farmSteps from "@/components/farmSteps.vue";
  281. import priceTable from "../agri_work/components/priceTable.vue";
  282. import priceSheetPopup from "@/components/popup/priceSheetPopup.vue";
  283. import uploadExecute from "@/views/old_mini/task_condition/components/uploadExecute.vue";
  284. import { base_img_url2 } from "@/api/config";
  285. import { ElMessage } from "element-plus";
  286. import { formatArea, formatDate } from "@/common/commonFun";
  287. import wx from "weixin-js-sdk";
  288. import reviewUploadPopup from "@/components/popup/reviewUploadPopup.vue";
  289. import FnShareSheet from "@/components/pageComponents/FnShareSheet.vue";
  290. import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
  291. import eventBus from "@/api/eventBus";
  292. const router = useRouter();
  293. const store = useStore();
  294. const query = ref({});
  295. // 角色
  296. // const curRole = store.state.app.curRole
  297. const curRole = ref(localStorage.getItem("SET_USER_CUR_ROLE"));
  298. // const curRole = 2;
  299. // 农资待生成报价单--currentStep:1;curRole:2
  300. // 农资已执行,有执行照片,请求确认--currentStep:2;curRole:2
  301. const showUpload = ref(false);
  302. const sectionId = ref(null);
  303. // 上传照片处理函数
  304. const handleUploadPhoto = ({ id }) => {
  305. showUpload.value = true;
  306. sectionId.value = id;
  307. };
  308. onDeactivated(() => {
  309. showUpload.value = false;
  310. sectionId.value = null;
  311. });
  312. const handleSelfDone = () => {
  313. VE_API.z_farm_work_record.updateFlowStatus({ id: query.value.id, targetFlowStatus: 5 }).then((res) => {
  314. if (res.code === 0) {
  315. showUpload.value = false;
  316. sectionId.value = null;
  317. setTimeout(() => {
  318. router.replace({
  319. path: "/review_work",
  320. query: {
  321. miniJson: JSON.stringify({ id: query.value.id, goBack: true }),
  322. },
  323. });
  324. }, 500);
  325. }
  326. });
  327. };
  328. const successText = ref("");
  329. const taskPopupActionType = ref(0);
  330. const showTaskPopup = ref(false);
  331. const taskPopupType = ref("");
  332. const handlePopupBtn = () => {
  333. showTaskPopup.value = false;
  334. if (taskPopupType.value === "warning") {
  335. // 取消发起
  336. VE_API.z_farm_work_record.updatePublicStatus({ recordId: query.value.id, isPublic: 0 }).then((res) => {
  337. if (res.code === 0) {
  338. getDetail(query.value.id);
  339. }
  340. });
  341. } else if (taskPopupType.value === "warningIgnore") {
  342. // 确认忽略
  343. VE_API.z_farm_work_record.ignoreFarmWorkRecord({ farmWorkRecordId: query.value.id }).then((res) => {
  344. if (res.code === 0) {
  345. ElMessage.success("操作成功");
  346. router.back();
  347. }
  348. });
  349. } else {
  350. if (taskPopupActionType.value === 0) {
  351. getDetail(query.value.id);
  352. } else {
  353. showUpload.value = false;
  354. sectionId.value = null;
  355. setTimeout(() => {
  356. router.replace({
  357. path: "/review_work",
  358. query: {
  359. miniJson: JSON.stringify({ id: query.value.id, goBack: true }),
  360. },
  361. });
  362. }, 500);
  363. }
  364. }
  365. };
  366. const cancelDemand = () => {
  367. taskPopupType.value = "warning";
  368. showTaskPopup.value = true;
  369. };
  370. const priceSheetPopupRef = ref(null);
  371. const showPriceSheetPopup = () => {
  372. priceSheetPopupRef.value.handleShowPopup(detailData.value);
  373. };
  374. const getButtonText = () => {
  375. return agriculturalRole.value === 1 || (agriculturalRole.value === 2 && detailData.value.executorUserId != userId.value);
  376. };
  377. const offerPopupRef = ref(null);
  378. const isGoBack = ref(false);
  379. const showOfferPopup = (item) => {
  380. isGoBack.value = true;
  381. offerPopupRef.value.openPopup(item);
  382. };
  383. const handleExecute = () => {
  384. const query = {
  385. askInfo: { title: "农事提醒", content: "是否分享该农事提醒给好友" },
  386. shareText: '向您分享了一条农事执行提醒,请您尽快执行',
  387. targetUrl: `completed_work`,
  388. paramsPage: JSON.stringify({id: detailData.value.id}),
  389. imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/invite_bg.png',
  390. };
  391. wx.miniProgram.navigateTo({
  392. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  393. });
  394. };
  395. const showCalendar = ref(false);
  396. const maxDate = ref(new Date());
  397. // 最小日期设置为今天,今天可以选择
  398. const minDate = new Date();
  399. const selectExecuteTime = () => {
  400. maxDate.value = new Date(detailData.value.executeDeadlineDate);
  401. showCalendar.value = true;
  402. };
  403. const handleRemindExecuteTime = () => {
  404. const query = {
  405. askInfo: { title: "农事提醒", content: "是否分享该农事提醒给好友" },
  406. shareText: '向您分享了一条农事提醒,请您尽快确认执行时间',
  407. targetUrl: `completed_work`,
  408. paramsPage: JSON.stringify({id: detailData.value.id}),
  409. imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/invite_bg.png',
  410. };
  411. wx.miniProgram.navigateTo({
  412. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  413. });
  414. };
  415. const onConfirmExecuteTime = (date) => {
  416. showCalendar.value = false;
  417. VE_API.z_farm_work_record
  418. .updateExpectedExecuteDate({ recordId: query.value.id, expectedExecuteDate: formatDate(date) })
  419. .then((res) => {
  420. if (res.code === 0) {
  421. getDetail(query.value.id);
  422. }
  423. });
  424. };
  425. const handleUploadSuccess = () => {
  426. if (isGoBack.value) {
  427. isGoBack.value = false;
  428. router.back();
  429. } else {
  430. getDetail(query.value.id);
  431. }
  432. };
  433. const quotationData = ref({});
  434. const toList = (val) => {
  435. if (!val) return [];
  436. return JSON.parse(val);
  437. };
  438. const speciesList = computed(() => toList(quotationData.value.serviceSpecies));
  439. const equipmentList = computed(() => toList(quotationData.value.serviceEquipment));
  440. const showBack = ref(false);
  441. const agriculturalRole = ref(null);
  442. const userId = ref(null);
  443. onActivated(async () => {
  444. //
  445. const userInfo = JSON.parse(localStorage.getItem("localUserInfo"));
  446. agriculturalRole.value = userInfo.agriculturalRole;
  447. userId.value = userInfo.id;
  448. //
  449. query.value = useRoute().query?.miniJson ? JSON.parse(useRoute().query?.miniJson) : {};
  450. let id = null;
  451. if(query.value?.id){
  452. id = query.value?.id;
  453. showBack.value = false;
  454. }else{
  455. const data = JSON.parse(query.value?.paramsPage);
  456. id = data?.id;
  457. showBack.value = true;
  458. }
  459. if (id) {
  460. await getDetail(id);
  461. //查询农场状态信息
  462. getFarmWorkArrangeDetail(detailData.value.farmWorkArrangeId);
  463. }
  464. });
  465. const farmStatusText = ref("");
  466. const getFarmWorkArrangeDetail = (id) => {
  467. VE_API.farm.getFarmWorkArrangeDetail({ id }).then(({ data }) => {
  468. farmStatusText.value = data.farmStatus;
  469. });
  470. };
  471. // 药肥费用总计:∑(单价 * 单亩用量 * 亩数)
  472. const pesticideCostTotal = computed(() => {
  473. const list = settingData();
  474. const area = Number(detailData.value?.area || 0);
  475. if (!list.length || !area) return 0;
  476. const sum = list.reduce((acc, item) => {
  477. const price = Number(item?.muPrice || 0);
  478. const dosage = Number(quotationData.value.executionMethod === 1 ? item?.muUsage2 : item?.muUsage || 0);
  479. if (!price || !dosage) return acc;
  480. return acc + price * dosage * area;
  481. }, 0);
  482. return Number(sum.toFixed(2));
  483. });
  484. // 报价合计 = 药肥费用 + 服务费用
  485. const totalCost = computed(() => {
  486. const pesticide = Number(pesticideCostTotal.value || 0);
  487. const service = Number(serviceCostTotal.value || 0);
  488. if (!pesticide && !service) return '--';
  489. return Number((pesticide + service).toFixed(2));
  490. });
  491. // 服务费用总计(数值):亩单价 * 亩数
  492. const serviceCostTotal = computed(() => {
  493. const price = Number(detailData.value?.serviceMuPrice || 0);
  494. const area = Number(detailData.value?.area || 0);
  495. if (!price || !area) return 0;
  496. return Number((price * area).toFixed(2));
  497. });
  498. const settingData = () => {
  499. return flattenDomains(quotationData.value.prescriptionList);
  500. };
  501. function flattenDomains(data) {
  502. if (!data || !Array.isArray(data)) return [];
  503. // 将所有 prescriptionList 中的 pesticideFertilizerList 合并成一个数组
  504. return data.reduce((acc, item) => {
  505. const list = item?.pesticideFertilizerList || [];
  506. return acc.concat(list);
  507. }, []);
  508. }
  509. const detailData = ref({});
  510. // 计算距离执行时间的天数差
  511. const daysDiff = computed(() => {
  512. if (!detailData.value?.expectedExecuteDate) {
  513. return 0;
  514. }
  515. const executeDate = new Date(detailData.value.expectedExecuteDate);
  516. const today = new Date();
  517. // 将时间设置为 00:00:00,只比较日期
  518. executeDate.setHours(0, 0, 0, 0);
  519. today.setHours(0, 0, 0, 0);
  520. // 计算天数差(毫秒转天数)
  521. const diffTime = executeDate.getTime() - today.getTime();
  522. const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
  523. return diffDays;
  524. });
  525. const getDetail = async (id) => {
  526. const { data } = await VE_API.z_farm_work_record.getDetail({ id });
  527. if (data && data.length > 0) {
  528. detailData.value = data[0];
  529. // 报价信息
  530. handlePriceInfo();
  531. }
  532. };
  533. const handlePriceInfo = async () => {
  534. let priceDataObj = {};
  535. const { data } = await VE_API.z_farm_work_record_cost.getByRecordId({ farmWorkRecordId: detailData.value.id });
  536. priceDataObj = data;
  537. // 执行方式为1:无人机,则使用无人机价格,否则使用人工价格
  538. detailData.value.serviceMuPrice = data?.executionMethod === 1 ? data?.uavServicePrice : data?.manualServicePrice;
  539. if (priceDataObj && Object.keys(priceDataObj).length > 0) {
  540. // 合并外层字段
  541. quotationData.value = {
  542. ...detailData.value,
  543. ...priceDataObj,
  544. };
  545. // 根据 itemsList 的 pesticideFertilizerId 匹配并赋值品牌和价格
  546. if (priceDataObj.itemsList && Array.isArray(priceDataObj.itemsList) && detailData.value.prescriptionList) {
  547. // 创建价格映射表
  548. const priceMap = new Map();
  549. priceDataObj.itemsList.forEach((item) => {
  550. priceMap.set(String(item.pesticideFertilizerId), {
  551. brand: item.brand || "",
  552. price: item.price || 0,
  553. });
  554. });
  555. // 遍历处方列表,赋值品牌和价格,并计算格式化字段供 price-table 使用
  556. quotationData.value.prescriptionList = detailData.value.prescriptionList.map((prescription) => {
  557. return {
  558. ...prescription,
  559. pesticideFertilizerList: prescription.pesticideFertilizerList.map((pesticide) => {
  560. const pesticideId = String(pesticide.pesticideFertilizerId || "");
  561. const priceInfo = priceMap.get(pesticideId);
  562. if (priceInfo) {
  563. const price = priceInfo.price || 0;
  564. const muUsage = pesticide.muUsage || 0;
  565. const unit = pesticide.unit || "";
  566. const area = detailData.value.area || 0;
  567. // 计算总价:优先使用 totalPrice,否则计算 price * muUsage * area
  568. const total = price * muUsage * area;
  569. return {
  570. ...pesticide,
  571. brand: priceInfo.brand || "--",
  572. // 格式化字段供 price-table 组件使用
  573. price: price > 0 ? `${price}元/${unit}` : "--", // 格式化单价显示
  574. dosage: muUsage > 0 ? `${muUsage}${unit}` : "--", // 格式化用量显示
  575. total: total > 0 ? total.toFixed(2) : "--", // 格式化总价显示
  576. };
  577. }
  578. return pesticide;
  579. }),
  580. };
  581. });
  582. }
  583. }
  584. };
  585. </script>
  586. <style lang="scss" scoped>
  587. .completed-work {
  588. height: 100vh;
  589. position: relative;
  590. overflow: auto;
  591. font-size: 14px;
  592. background: #f2f3f5;
  593. .work-content {
  594. height: calc(100% - 40px);
  595. overflow: auto;
  596. box-sizing: border-box;
  597. &.hasBottom {
  598. padding-bottom: 60px;
  599. }
  600. .info-wrap {
  601. margin-bottom: 80px;
  602. }
  603. .work-wrap {
  604. position: relative;
  605. z-index: 3;
  606. padding: 0 12px;
  607. top: -16px;
  608. }
  609. .pt-10 {
  610. padding-top: 10px;
  611. }
  612. .fixed-btn-wrap {
  613. position: fixed;
  614. z-index: 10;
  615. bottom: 0;
  616. left: 0;
  617. width: 100%;
  618. padding: 10px 12px 25px;
  619. box-sizing: border-box;
  620. display: flex;
  621. align-items: center;
  622. justify-content: space-between;
  623. background: #fff;
  624. box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.4);
  625. &.center {
  626. justify-content: center;
  627. }
  628. .fixed-btn {
  629. width: 120px;
  630. text-align: center;
  631. height: 40px;
  632. line-height: 40px;
  633. background: linear-gradient(180deg, #70bffe, #2199f8);
  634. border-radius: 25px;
  635. color: #fff;
  636. font-size: 14px;
  637. box-sizing: border-box;
  638. &.orange {
  639. color: #ff953d;
  640. border: 1px solid #ff953d;
  641. background: rgba(255, 149, 61, 0.1);
  642. }
  643. &.excute {
  644. background: linear-gradient(180deg, #ffd887, #ed9e1e);
  645. }
  646. &.second {
  647. background: #ffffff;
  648. border: 1px solid rgba(153, 153, 153, 0.5);
  649. color: #666666;
  650. }
  651. &.primary-text {
  652. width: 160px;
  653. background: rgba(33, 153, 248, 0.1);
  654. color: #2199f8;
  655. border: 1px solid #2199f8;
  656. }
  657. &.width-120 {
  658. width: 120px;
  659. }
  660. }
  661. }
  662. .card-title {
  663. font-size: 16px;
  664. font-weight: bold;
  665. color: #000;
  666. display: flex;
  667. align-items: center;
  668. justify-content: space-between;
  669. .tag-text {
  670. color: #2199f8;
  671. font-size: 12px;
  672. font-weight: normal;
  673. }
  674. .point-wrap {
  675. display: flex;
  676. align-items: center;
  677. color: #393939;
  678. font-size: 12px;
  679. font-weight: normal;
  680. .point {
  681. width: 6px;
  682. height: 6px;
  683. border-radius: 50%;
  684. background: #393939;
  685. margin-right: 4px;
  686. }
  687. }
  688. .type-tag {
  689. margin-left: 5px;
  690. font-size: 12px;
  691. color: #000000;
  692. padding: 4px 10px;
  693. background: rgba(119, 119, 119, 0.1);
  694. border-radius: 20px;
  695. font-weight: normal;
  696. height: 26px;
  697. line-height: 26px;
  698. }
  699. }
  700. .box-wrap {
  701. background: #fff;
  702. padding: 10px;
  703. border-radius: 8px;
  704. }
  705. .step-wrap {
  706. padding: 18px 0;
  707. }
  708. .content-status {
  709. position: relative;
  710. padding: 26px 12px 20px 12px;
  711. color: #fff;
  712. z-index: 2;
  713. display: flex;
  714. justify-content: space-between;
  715. align-items: center;
  716. &::after {
  717. content: "";
  718. z-index: -1;
  719. position: absolute;
  720. left: 0;
  721. top: 0;
  722. height: 136px;
  723. background: #2199f8;
  724. width: 100%;
  725. }
  726. .status-l {
  727. .stauts-text {
  728. font-size: 22px;
  729. display: flex;
  730. align-items: center;
  731. .status-icon {
  732. width: 24px;
  733. height: 24px;
  734. background: #ffffff;
  735. border-radius: 50%;
  736. margin-right: 8px;
  737. }
  738. }
  739. .stauts-sub-text {
  740. // color: rgba(255, 255, 255, 0.51);
  741. font-size: 14px;
  742. }
  743. .time-text {
  744. color: #ffff;
  745. }
  746. }
  747. &.warning {
  748. &::after {
  749. background: #ff953d;
  750. }
  751. .stauts-sub-text {
  752. color: #fff;
  753. }
  754. }
  755. .status-r {
  756. height: 32px;
  757. line-height: 32px;
  758. padding: 0 16px;
  759. color: #2199f8;
  760. font-size: 16px;
  761. background: #fff;
  762. border-radius: 20px;
  763. }
  764. }
  765. .info-content {
  766. padding: 10px 0;
  767. position: relative;
  768. }
  769. .info-title-wrap {
  770. display: flex;
  771. align-items: center;
  772. justify-content: space-between;
  773. color: #000;
  774. .info-more {
  775. font-size: 18px;
  776. }
  777. .unit-text {
  778. font-size: 12px;
  779. }
  780. }
  781. .info-content-wrap {
  782. .price-bottom {
  783. padding-top: 8px;
  784. .price-info {
  785. padding: 8px 0;
  786. display: flex;
  787. align-items: center;
  788. justify-content: space-between;
  789. color: rgba(0, 0, 0, 0.2);
  790. .main-text {
  791. padding-left: 20px;
  792. color: rgba(0, 0, 0, 0.8);
  793. }
  794. .info-c {
  795. flex: 1;
  796. text-align: center;
  797. }
  798. }
  799. .price-total {
  800. height: 38px;
  801. display: flex;
  802. align-items: center;
  803. justify-content: center;
  804. border: 1px solid rgba(33, 153, 248, 0.5);
  805. background: rgba(33, 153, 248, 0.1);
  806. color: #000000;
  807. border-radius: 4px;
  808. .main-val {
  809. font-size: 20px;
  810. font-weight: bold;
  811. color: #2199f8;
  812. padding-right: 2px;
  813. }
  814. }
  815. }
  816. }
  817. .executor-info {
  818. margin-top: 14px;
  819. &.mt-0 {
  820. margin-top: 0;
  821. }
  822. .executor-title {
  823. font-size: 18px;
  824. font-weight: bold;
  825. color: #000;
  826. margin-bottom: 12px;
  827. }
  828. .executor-content {
  829. .executor-info {
  830. display: flex;
  831. align-items: flex-start;
  832. gap: 12px;
  833. }
  834. .executor-avatar {
  835. flex-shrink: 0;
  836. img {
  837. width: 60px;
  838. height: 60px;
  839. border-radius: 8px;
  840. object-fit: cover;
  841. }
  842. }
  843. .executor-details {
  844. flex: 1;
  845. .org-name {
  846. display: flex;
  847. align-items: center;
  848. gap: 8px;
  849. margin-bottom: 3px;
  850. .name {
  851. font-size: 16px;
  852. font-weight: bold;
  853. color: #000;
  854. }
  855. .rating {
  856. font-size: 16px;
  857. color: #ff953d;
  858. font-weight: bold;
  859. }
  860. }
  861. .service-info {
  862. .service-item {
  863. font-size: 12px;
  864. color: #b6b6b6;
  865. line-height: 1.3;
  866. margin-bottom: 2px;
  867. span {
  868. color: #666666;
  869. }
  870. }
  871. }
  872. }
  873. }
  874. .contact-buttons {
  875. display: flex;
  876. justify-content: flex-end;
  877. gap: 8px;
  878. margin-top: 16px;
  879. .contact-btn {
  880. width: 88px;
  881. height: 32px;
  882. border-radius: 20px;
  883. color: rgba(0, 0, 0, 0.5);
  884. background: #fff;
  885. border: 1px solid rgba(0, 0, 0, 0.1);
  886. }
  887. }
  888. }
  889. .farm-info {
  890. color: rgba(0, 0, 0, 0.6);
  891. font-size: 14px;
  892. margin-top: 14px;
  893. .info-title {
  894. display: flex;
  895. align-items: center;
  896. justify-content: space-between;
  897. color: rgba(41, 41, 41, 0.3);
  898. .info-more {
  899. display: flex;
  900. align-items: center;
  901. }
  902. }
  903. }
  904. .farm-photo {
  905. margin-top: 10px;
  906. .photo-list {
  907. display: flex;
  908. align-items: center;
  909. width: 100%;
  910. overflow: auto;
  911. padding-bottom: 10px;
  912. .photo-item {
  913. width: 92px;
  914. height: 92px;
  915. border-radius: 8px;
  916. object-fit: cover;
  917. }
  918. .img-item {
  919. img {
  920. width: 92px;
  921. height: 92px;
  922. border-radius: 8px;
  923. object-fit: cover;
  924. margin-right: 12px;
  925. }
  926. }
  927. .view-box {
  928. width: 92px;
  929. height: 92px;
  930. border-radius: 8px;
  931. object-fit: cover;
  932. }
  933. }
  934. .list-text {
  935. text-align: center;
  936. color: rgba(0, 0, 0, 0.5);
  937. padding-top: 2px;
  938. }
  939. }
  940. .farm-data {
  941. margin-top: 10px;
  942. .data-content {
  943. margin-top: 8px;
  944. border-top: 1px solid #f5f5f5;
  945. padding: 8px 0;
  946. }
  947. }
  948. .form-item {
  949. display: flex;
  950. align-items: center;
  951. font-size: 14px;
  952. color: #767676;
  953. height: 24px;
  954. .item-name {
  955. width: 80px;
  956. color: rgba(0, 0, 0, 0.2);
  957. }
  958. }
  959. .form-item + .form-item {
  960. padding-top: 2px;
  961. }
  962. .farm-table {
  963. margin-top: 10px;
  964. .table-item {
  965. padding: 10px 0 12px 0;
  966. }
  967. }
  968. .new-wrap {
  969. border-radius: 5px;
  970. text-align: center;
  971. border: 1px solid rgba(225, 225, 225, 0.5);
  972. .new-title {
  973. background: rgba(241, 241, 241, 0.4);
  974. border-radius: 5px 5px 0 0;
  975. border-bottom: 1px solid rgba(225, 225, 225, 0.5);
  976. display: flex;
  977. color: #767676;
  978. // justify-content: space-around;
  979. padding: 5px 6px;
  980. font-size: 12px;
  981. .table-name {
  982. width: 24px;
  983. font-size: 12px;
  984. margin: 0 auto;
  985. }
  986. }
  987. .title-1 {
  988. width: 46px;
  989. }
  990. .title-2 {
  991. flex: 1;
  992. }
  993. .title-3 {
  994. width: 52px;
  995. }
  996. .title-4 {
  997. width: 56px;
  998. }
  999. .title-5 {
  1000. width: 52px;
  1001. }
  1002. .new-table-wrap {
  1003. .new-prescription {
  1004. .new-table {
  1005. height: 45px;
  1006. display: flex;
  1007. align-items: center;
  1008. background: #fff;
  1009. border-radius: 5px;
  1010. color: rgba(0, 0, 0, 0.6);
  1011. font-size: 11px;
  1012. .line-l {
  1013. display: flex;
  1014. flex: 1;
  1015. .line-2 {
  1016. flex: 1;
  1017. padding: 0 2px;
  1018. }
  1019. }
  1020. .line-r {
  1021. &.has-border {
  1022. border-left: 1px solid rgba(225, 225, 225, 0.8);
  1023. }
  1024. .line-3 {
  1025. display: flex;
  1026. align-items: center;
  1027. }
  1028. .sub-line {
  1029. padding: 10px 0;
  1030. }
  1031. .line-4 {
  1032. display: flex;
  1033. align-items: center;
  1034. border-top: 1px solid rgba(225, 225, 225, 0.8);
  1035. }
  1036. .execute-line {
  1037. border-right: 1px solid rgba(225, 225, 225, 0.8);
  1038. }
  1039. }
  1040. }
  1041. .note-text {
  1042. margin: 8px 0 4px 0;
  1043. color: rgba(0, 0, 0, 0.4);
  1044. background: #fff;
  1045. padding: 6px 8px;
  1046. border-radius: 5px;
  1047. text-align: left;
  1048. font-size: 11px;
  1049. }
  1050. }
  1051. .new-prescription + .new-prescription {
  1052. .new-table {
  1053. border-top: 1px solid rgba(0, 0, 0, 0.08);
  1054. }
  1055. }
  1056. }
  1057. }
  1058. .work-map {
  1059. padding: 10px 0;
  1060. .map-content {
  1061. padding-top: 8px;
  1062. .map-dom {
  1063. height: 166px;
  1064. width: 100%;
  1065. clip-path: inset(0px round 8px);
  1066. }
  1067. }
  1068. }
  1069. }
  1070. }
  1071. .task-tips-popup {
  1072. width: 75%;
  1073. padding: 28px 28px 20px;
  1074. display: flex;
  1075. flex-direction: column;
  1076. align-items: center;
  1077. justify-content: center;
  1078. .create-farm-icon {
  1079. width: 40px;
  1080. height: 40px;
  1081. margin-bottom: 12px;
  1082. }
  1083. .farm-check-icon {
  1084. width: 68px;
  1085. height: 68px;
  1086. margin-bottom: 12px;
  1087. }
  1088. .create-farm-text {
  1089. font-size: 20px;
  1090. font-weight: 500;
  1091. line-height: 40px;
  1092. margin-bottom: 32px;
  1093. text-align: center;
  1094. &.success-text {
  1095. font-size: 23px;
  1096. font-weight: 400;
  1097. }
  1098. }
  1099. .main-text {
  1100. color: #2199f8;
  1101. }
  1102. .create-farm-btn {
  1103. width: 100%;
  1104. box-sizing: border-box;
  1105. padding: 8px;
  1106. border-radius: 25px;
  1107. font-size: 16px;
  1108. background: #2199f8;
  1109. color: #fff;
  1110. text-align: center;
  1111. }
  1112. }
  1113. </style>