index.vue 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. <template>
  2. <div class="work-detail">
  3. <custom-header :name="$t('workDetail.title')" v-if="!miniJson?.hideDraw" :showClose="false" isGoBack @goback="handleBack" />
  4. <div class="work-detail-content">
  5. <!-- 顶部状态 -->
  6. <div class="content-status" :class="['status-' + farmData?.work_status]">
  7. <div class="status-l">
  8. <div class="status-title">{{ farmData.work_name }}</div>
  9. <!-- <div class="status-sub" v-if="triggerDateText && (detail?.flowStatus == -1 || detail?.flowStatus == -2)">
  10. 执行时间已经过去 {{ Math.abs(daysDiff) }} 天了
  11. </div>
  12. <div class="status-sub" v-if="detail?.flowStatus === 3 && getAuditStatusPriority(detail?.executeEvidenceAuditStatus) !== 2">
  13. 04/19 执行最佳
  14. </div>
  15. <div class="status-sub" v-if="!detail?.flowStatus || detail?.flowStatus === 0">
  16. 预计{{ detail?.executeDate || "--" }}执行,执行时间需巡园校准
  17. </div> -->
  18. <div class="status-sub">
  19. {{ farmData.best_time }}
  20. </div>
  21. </div>
  22. </div>
  23. <div class="work-wrap has-bottom warning-info-show">
  24. <!-- 农事组信息 -->
  25. <div class="group-info group-box" v-if="farmData.interaction_reason">
  26. <div class="group-name">
  27. {{ farmData.interaction_reason }}
  28. </div>
  29. </div>
  30. <!-- 每一段农事 -->
  31. <!-- <div v-for="(prescription, index) in stageList" :key="index" class="box-wrap stage-card">
  32. <div class="work-info">
  33. <div class="stage-header">
  34. <div class="stage-title">{{ detail.farmWorkName }}</div>
  35. </div>
  36. <div class="stage-info">
  37. <div class="form-item">
  38. <div class="item-name">{{ $t('workDetail.purpose') }}</div>
  39. <div class="item-text">
  40. {{ prescription.purpose || prescription.purposeName || "--" }}
  41. </div>
  42. </div>
  43. <div class="form-item">
  44. <div class="item-name">{{ $t('农事时间') }}</div>
  45. <div class="item-text">
  46. {{ detail.executeDate || "--" }}
  47. </div>
  48. </div>
  49. <div class="form-item">
  50. <div class="item-name">{{ $t('执行区域') }}</div>
  51. <div class="item-text light-text area-text">
  52. {{ detail?.executionRegion?.regionName }}种植区域
  53. <div class="area-btn" v-if="detail?.executionRegion?.regionRange" @click="handleViewArea">{{ $t('查看区域') }}</div>
  54. <div class="area-btn area-btn-right" @click="toDraw" v-if="!detail?.executionRegion?.regionRange && !miniJson?.hideDraw">{{ $t('建议勾选') }}<el-icon><ArrowRight /></el-icon></div>
  55. </div>
  56. </div>
  57. <div class="form-item">
  58. <div class="item-name">{{ $t('workDetail.notes') }}</div>
  59. <div class="item-text">
  60. {{ detail.remark || "--" }}
  61. </div>
  62. </div>
  63. <div class="form-item" v-if="hasAnyAvailableExecutionMethod(prescription)">
  64. <div class="item-name">{{ $t('药肥处方') }}</div>
  65. </div>
  66. </div>
  67. <div class="stage-tabs" v-if="hasAnyAvailableExecutionMethod(prescription)">
  68. <div v-for="tab in getAvailableExecutionTabs(prescription)" :key="tab.value" class="tab-pill"
  69. :class="{ active: getStageExecutionMethod(index) === tab.value }"
  70. @click="changeExecutionMethod(index, tab.value)">
  71. {{ tab.label }}
  72. </div>
  73. </div>
  74. <div class="prescription-wrap"
  75. v-if="prescription.pesticideList && prescription.pesticideList.length && hasAnyAvailableExecutionMethod(prescription)">
  76. <div class="prescription-table">
  77. <div class="table-header">
  78. <div class="col col-type">{{ $t('使用功效') }}</div>
  79. <div class="col col-name">{{ $t('药肥名称') }}</div>
  80. <div class="col col-ratio">{{ $t('药肥配比') }}</div>
  81. </div>
  82. <div v-for="(item, i) in prescription.pesticideList" :key="i" class="table-row">
  83. <div class="col col-type">
  84. {{ item.typeName || "--" }}
  85. </div>
  86. <div class="col col-name">
  87. {{ item.name || item.pesticideFertilizerName || "--" }}
  88. </div>
  89. <div class="col col-ratio">
  90. {{ getPesticideParam(item, index)?.ratio || "--" }}倍
  91. </div>
  92. </div>
  93. </div>
  94. <div v-if="hasRemark(prescription, index)" class="prescription-remark">
  95. <span v-for="(item, idx) in [prescription.pesticideList[0]]" :key="idx">
  96. <template v-if="getParamRemark(item, index)">
  97. {{ getParamRemark(item, index) }}
  98. <br />
  99. </template>
  100. </span>
  101. </div>
  102. </div>
  103. </div>
  104. <div class="work-info photo-box" v-if="prescription.cropAlbum && prescription.cropAlbum.length">
  105. <div class="photo-title">{{ $t('农事凭证') }}</div>
  106. <div class="tips-text" v-if="detail?.imageAuditRejectReason">{{ detail?.imageAuditRejectReason }}</div>
  107. <div class="photo-sub-title" v-if="info?.appType === 1">来自于 {{ detail?.executorOrganizationName || "--" }}</div>
  108. <div class="photo-img-wrap">
  109. <photo-provider :photo-closable="true">
  110. <photo-consumer v-for="(src, index) in prescription.cropAlbum" intro="农事凭证" :key="index"
  111. :src="base_img_url2 + src.filename">
  112. <div class="photo-img">
  113. <img :src="base_img_url2 + src.filename" />
  114. <div class="fail-icon" v-if="failIndex(index) === 2">
  115. <el-icon size="24" color="#FF953D">
  116. <WarningFilled />
  117. </el-icon>
  118. <div class="fail-icon-text">{{ $t('审核失败') }}</div>
  119. </div>
  120. </div>
  121. </photo-consumer>
  122. </photo-provider>
  123. </div>
  124. </div>
  125. </div> -->
  126. <div class="box-wrap stage-card">
  127. <div class="work-info">
  128. <div class="map-box">
  129. <div class="map-title">{{ $t('workDetail.executionArea') }}</div>
  130. <div class="map-container" ref="mapContainer"></div>
  131. </div>
  132. <div class="area-list">
  133. <div
  134. v-for="(zone, zoneIndex) in farmZones"
  135. :key="`${zone.zone_name || 'zone'}-${zoneIndex}`"
  136. class="area-item"
  137. >
  138. <div class="area-l">
  139. {{ zone.zone_name }}
  140. <span> {{ zone.execute_time }}</span>
  141. <span
  142. class="area-tag"
  143. :style="backgroundFarmWorkStatus(farmData.work_status)"
  144. >{{ workStatusObj[farmData.work_status] }}</span>
  145. </div>
  146. </div>
  147. </div>
  148. <!-- <div class="ecological-plant-card">
  149. <div class="ecological-plant-text">
  150. <div class="ecological-plant-line">
  151. 生态种植方式:{{ ecologicalPlantingMethodText }}
  152. </div>
  153. <div class="ecological-plant-line ecological-plant-line--sub">
  154. 来自于 {{ ecologicalExecutorOrg }}
  155. </div>
  156. </div>
  157. <div class="ecological-plant-gallery">
  158. <img v-for="(src, idx) in ecologicalPlantThumbUrls" :key="idx"
  159. class="ecological-plant-thumb" :src="src" alt="" />
  160. </div>
  161. </div> -->
  162. </div>
  163. </div>
  164. <div class="box-wrap stage-card">
  165. <div class="work-info">
  166. <div class="info-item">
  167. <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.purpose') }}</div>
  168. <div class="info-value">{{ farmData.work_purpose }}</div>
  169. </div>
  170. <div class="info-item">
  171. <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.notes') }}</div>
  172. <div class="info-value">{{ farmData.precautions }}</div>
  173. </div>
  174. <div class="info-item">
  175. <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.prescription') }}</div>
  176. <div class="info-value">{{ farmData.drug_prescription }}</div>
  177. </div>
  178. <div class="info-item">
  179. <div class="info-title"><span class="title-block"></span>{{ $t('workDetail.executionMethod') }}</div>
  180. <div class="info-value">{{ farmData.execution_method }}</div>
  181. </div>
  182. </div>
  183. </div>
  184. <!-- <div class="warning-info">
  185. <div class="warning-l">
  186. <div class="warning-title">
  187. <el-icon size="16" color="#FF953D"><WarningFilled /></el-icon>
  188. 存在继发危害需巡园
  189. </div>
  190. <div class="warning-text">{{ $t('文案文案文案文案文案') }}</div>
  191. </div>
  192. <div class="warning-r">{{ $t('异常记录') }}</div>
  193. </div> -->
  194. <!-- 底部按钮 -->
  195. <!-- <div class="fixed-btn-wrap center-btn" v-if="info?.appType === 2">
  196. <div class="fixed-btn" @click="handleConvert">
  197. 转发农事
  198. </div>
  199. </div>
  200. <div class="fixed-btn-wrap execute-action">
  201. <div class="action-item second" v-if="!miniJson?.hideDraw" @click="handleConvert">{{ $t('转发给执行人员') }}</div>
  202. </div> -->
  203. <!-- <div class="action-item primary" @click="handleExecute">{{ $t('溯源认证') }}</div> -->
  204. </div>
  205. </div>
  206. <ExecutePopup ref="executePopupRef" @executeSuccess="getDetail" />
  207. <upload-tips v-model:show="showUploadTipsPopup" />
  208. </div>
  209. <!-- 执行区域地图弹窗 -->
  210. <popup v-model:show="showMapPopup" closeable class="map-popup">
  211. <map-info :rangeWkt="detail?.executionRegion?.regionRange" />
  212. </popup>
  213. </template>
  214. <script setup>
  215. import { ElMessage } from "element-plus";
  216. import wx from "weixin-js-sdk";
  217. import customHeader from "@/components/customHeader.vue";
  218. import { ref, computed, watch, onMounted, onActivated, nextTick } from "vue";
  219. import { useI18n } from "@/i18n";
  220. import { useRouter } from "vue-router";
  221. import { formatDate } from "@/common/commonFun";
  222. import ExecutePopup from "./components/executePopup.vue";
  223. import { base_img_url2 } from "@/api/config";
  224. import UploadTips from "@/components/popup/uploadTips.vue";
  225. import { Popup } from "vant";
  226. import MapInfo from "./components/mapInfo.vue";
  227. import { useRoute } from "vue-router";
  228. import AreaMap from "./components/areaMap.js";
  229. import WKT from "ol/format/WKT.js";
  230. import { time } from "echarts";
  231. import imgFq1 from "@/assets/img/common/fq-1.png";
  232. import imgFq2 from "@/assets/img/common/fq-2.png";
  233. import imgYz1 from "@/assets/img/common/yz-1.png";
  234. import imgYz2 from "@/assets/img/common/yz-2.png";
  235. import imgJf1 from "@/assets/img/common/jf-1.png";
  236. import imgJf2 from "@/assets/img/common/jf-2.png";
  237. const route = useRoute();
  238. const { locale } = useI18n();
  239. const showUploadTipsPopup = ref(false);
  240. const headerTitle = ref('');
  241. const router = useRouter();
  242. // const info = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
  243. const info = { appType: 1 };
  244. /** 接口根级与 detail 合并后的详情(pesticideList、prescriptionList) */
  245. const detail = ref({
  246. farmId: null,
  247. farmWorkLibId: null,
  248. farmWorkName: "",
  249. flowStatus: null,
  250. purpose: "",
  251. speciesId: null,
  252. speciesName: "",
  253. executeDate: null,
  254. intervelTime: null,
  255. remark: "",
  256. pesticideList: [],
  257. prescriptionList: [],
  258. confirmPicture: [],
  259. executeEvidence: [],
  260. expertName: "",
  261. expertPrescription: "",
  262. id: null,
  263. postId: null,
  264. post: null,
  265. expertNameFromFarmBasicInfo: "",
  266. rangeWkt: null,
  267. });
  268. /** 凭证图片统一为 { filename } */
  269. const normalizeCropAlbum = (album) => {
  270. if (!album || !Array.isArray(album)) return [];
  271. return album
  272. .map((x) => {
  273. if (!x) return null;
  274. if (typeof x === "string") return { filename: x };
  275. if (x.filename) return x;
  276. return null;
  277. })
  278. .filter(Boolean);
  279. };
  280. const maybeShowUploadTips = () => {
  281. if (detail.value?.flowStatus !== 1) return;
  282. const shown = localStorage.getItem('upload_tips');
  283. if (shown === "1") return;
  284. localStorage.setItem('upload_tips', "1");
  285. showUploadTipsPopup.value = true;
  286. };
  287. onMounted(() => {
  288. maybeShowUploadTips();
  289. });
  290. const miniJson = ref(null);
  291. /** 生态种植卡片:方式文案(可后续接接口字段) */
  292. const ecologicalPlantingMethodText = ref("某某方式");
  293. /** 与设计稿一致的四宫格示例图 */
  294. const ecologicalPlantThumbUrls = ref([imgFq1, imgFq2]);
  295. const ecologicalExecutorOrg = computed(
  296. () => detail.value?.executorOrganizationName || "某某某农资机构"
  297. );
  298. const farmData = ref({});
  299. const wktFmt = new WKT();
  300. const MAP_CENTER_FALLBACK = "POINT(113.1093017627431 22.57454083668)";
  301. const farmZones = computed(() => {
  302. const zones = farmData.value?.zones;
  303. if (!Array.isArray(zones)) return [];
  304. return zones.filter(
  305. (z) => z && typeof z.zone_geometry === "string" && z.zone_geometry.trim()
  306. );
  307. });
  308. /** 从地块 WKT 取首坐标作为初始中心,initZones 会 fit 到全部地块 */
  309. function wktCenterPointFromZones(zones) {
  310. const list = Array.isArray(zones) ? zones : [];
  311. for (const zone of list) {
  312. const wkt = zone?.zone_geometry;
  313. if (!wkt || typeof wkt !== "string") continue;
  314. try {
  315. const c = wktFmt.readGeometry(wkt.trim()).getFirstCoordinate();
  316. return `POINT(${c[0]} ${c[1]})`;
  317. } catch {
  318. /* try next zone */
  319. }
  320. }
  321. return MAP_CENTER_FALLBACK;
  322. }
  323. function initWorkDetailMap() {
  324. nextTick(() => {
  325. if (!mapContainer.value) return;
  326. const zones = farmZones.value;
  327. areaMap.initMap(wktCenterPointFromZones(zones), mapContainer.value);
  328. areaMap.initZones(zones);
  329. });
  330. }
  331. onActivated(() => {
  332. headerTitle.value = route.query?.title || '返青追肥';
  333. if (headerTitle.value === '返青追肥') {
  334. ecologicalPlantThumbUrls.value = [imgFq1, imgFq2]
  335. } else if (headerTitle.value === '移栽防治') {
  336. ecologicalPlantThumbUrls.value = [imgYz1, imgYz2]
  337. } else {
  338. ecologicalPlantThumbUrls.value = [imgJf1, imgJf2]
  339. }
  340. // if (route.query?.miniJson) {
  341. // const miniJsonObj = JSON.parse(route.query.miniJson);
  342. // miniJson.value = JSON.parse(miniJsonObj.paramsPage);
  343. // } else {
  344. // miniJson.value = null;
  345. // }
  346. getDetail();
  347. });
  348. const workStatusObj = {
  349. 0: "待校准",
  350. 1: "机动执行",
  351. 2: "待执行",
  352. 3: "未激活",
  353. 4: "已认证",
  354. 5: "已失效",
  355. 6: "已执行",
  356. }
  357. const backgroundFarmWorkStatus = (status) => {
  358. let background = 'rgba(33, 153, 248, 0.1)';
  359. let color = '#2199F8';
  360. if (status === 0 || status === 1) {
  361. background = 'rgba(255, 149, 61, 0.1)';
  362. color = '#FF953D';
  363. }else if (status === 4) {
  364. background = 'rgba(55, 193, 27, 0.1)';
  365. color = '#37C11B';
  366. }else if (status === 3 || status === 5) {
  367. background = 'rgba(98, 98, 98, 0.1)';
  368. color = '#626262';
  369. }
  370. return {
  371. background,
  372. color,
  373. }
  374. }
  375. const getDetail = () => {
  376. if (!route.query.id) return;
  377. VE_API.monitor.getWorkDetail({ id: route.query.id }).then(res => {
  378. if (res.code === 200 && res.data.length) {
  379. farmData.value = res.data[0];
  380. initWorkDetailMap();
  381. }
  382. })
  383. // if (!miniJson.value) return;
  384. // const { farmWorkLibId, farmId, recordId, reproductiveId } = miniJson.value;
  385. // VE_API.z_farm_work_record
  386. // .getDetailById({ farmWorkLibId, farmId, farmWorkRecordId: recordId, reproductiveId })
  387. // .then(({ data }) => {
  388. // const inner =
  389. // data?.detail && typeof data.detail === "object"
  390. // ? { ...data.detail }
  391. // : {};
  392. // detail.value = {
  393. // ...inner,
  394. // post: data?.post ?? null,
  395. // executionRegion: data?.executionRegion ?? null,
  396. // expertNameFromFarmBasicInfo:
  397. // data?.expertNameFromFarmBasicInfo ?? "",
  398. // rangeWkt: data?.rangeWkt ?? null,
  399. // };
  400. // // 地图
  401. // areaMap.initMap("POINT(113.1093017627431 22.57454083668)", mapContainer.value);
  402. // });
  403. };
  404. watch(locale, () => {
  405. getDetail();
  406. });
  407. // 计算距离执行时间的天数差
  408. const daysDiff = computed(() => {
  409. if (!detail.value?.executeDate) {
  410. return '--';
  411. }
  412. const executeDate = new Date(detail.value.executeDate);
  413. const today = new Date();
  414. // 将时间设置为 00:00:00,只比较日期
  415. executeDate.setHours(0, 0, 0, 0);
  416. today.setHours(0, 0, 0, 0);
  417. // 计算天数差(毫秒转天数)
  418. const diffTime = executeDate.getTime() - today.getTime();
  419. const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
  420. return diffDays;
  421. });
  422. // 执行方式 Tab 配置
  423. const executionTabs = [
  424. { label: "植保机", value: 1 },
  425. { label: "人工手持", value: 2 },
  426. { label: "地面机械", value: 3 }
  427. ];
  428. // 每一段农事的当前执行方式(索引 -> 执行方式),默认 1:植保机
  429. const stageExecutionMethods = ref({});
  430. const executePopupRef = ref(null);
  431. const hasTextValue = (val) => {
  432. if (val === null || val === undefined) return false;
  433. return String(val).trim() !== "";
  434. };
  435. const hasValidParamForMethod = (item, methodValue) => {
  436. if (!item?.params || !Array.isArray(item.params)) return false;
  437. const methodParam = item.params.find(
  438. (param) => Number(param.executionMethod) === Number(methodValue)
  439. );
  440. if (!methodParam) return false;
  441. return hasTextValue(methodParam.ratio) || hasTextValue(methodParam.remark);
  442. };
  443. const getAvailableExecutionTabs = (prescription) => {
  444. const list = prescription?.pesticideList;
  445. if (!Array.isArray(list) || !list.length) return [];
  446. return executionTabs.filter((tab) =>
  447. list.some((item) => hasValidParamForMethod(item, tab.value))
  448. );
  449. };
  450. const hasAnyAvailableExecutionMethod = (prescription) => {
  451. return getAvailableExecutionTabs(prescription).length > 0;
  452. };
  453. const triggerDateText = computed(() => {
  454. if (!detail.value.executeDate) return "";
  455. const d = formatDate(detail.value.executeDate);
  456. return d.replace(/-/g, ".");
  457. });
  458. /**
  459. * 展示用「分段」列表:detail.pesticideList 或 prescriptionList 推导
  460. */
  461. const stageList = computed(() => {
  462. const d = detail.value;
  463. if (!d) return [];
  464. // if (Array.isArray(d.pesticideList) && d.pesticideList.length) {
  465. return [
  466. {
  467. ...d,
  468. pesticideList: d.pesticideList,
  469. cropAlbum: normalizeCropAlbum(
  470. d.confirmPicture?.length
  471. ? d.confirmPicture
  472. : d.executeEvidence
  473. ),
  474. },
  475. ];
  476. // }
  477. // return [];
  478. });
  479. const hasRemark = (prescription, stageIndex) => {
  480. if (!prescription?.pesticideList || !Array.isArray(prescription.pesticideList)) return false;
  481. const currentMethod = getStageExecutionMethod(stageIndex);
  482. return prescription.pesticideList.some((item) => {
  483. if (!item.params || !Array.isArray(item.params)) return false;
  484. const p = item.params.find((param) => param.executionMethod === currentMethod);
  485. return !!(p && p.remark);
  486. });
  487. };
  488. const handleTagType = (tagType) => {
  489. if (tagType == 0) return "待触发";
  490. if (tagType == -1) return "已过期";
  491. if (tagType == -2) return "已过期";
  492. if (tagType == 3) {
  493. const status = getAuditStatusPriority(detail.value?.executeEvidenceAuditStatus);
  494. if (status === 2) {
  495. return "审核失败";
  496. }
  497. return "待认证"
  498. }
  499. if (tagType == 5) {
  500. const status = getAuditStatusPriority(detail.value?.executeEvidenceAuditStatus);
  501. if (status === 2) {
  502. return "审核失败";
  503. }
  504. if (status === 0) {
  505. return "审核中";
  506. }
  507. return "已认证";
  508. }
  509. return "待触发"
  510. }
  511. // 审核状态优先级:2 > 0 > 1
  512. const getAuditStatusPriority = (auditStatusList) => {
  513. if (!Array.isArray(auditStatusList) || !auditStatusList.length) return 1;
  514. const normalized = auditStatusList.map((x) => Number(x)).filter((x) => [0, 1, 2].includes(x));
  515. if (!normalized.length) return 1;
  516. if (normalized.includes(0)) return 0;
  517. if (normalized.includes(2)) return 2;
  518. return 1;
  519. };
  520. // 审核是否失败
  521. const failIndex = (index) => {
  522. return detail.value?.executeEvidenceAuditStatus[index]
  523. }
  524. const handleExecute = () => {
  525. // const today = new Date();
  526. // const executeDate = new Date(detail.value.executeDate);
  527. // if (executeDate.getTime() > today.getTime()) {
  528. // ElMessage.warning('未到农事执行时间,无法溯源认证');
  529. // return;
  530. // }
  531. const evidenceList = normalizeCropAlbum(
  532. detail.value?.confirmPicture?.length
  533. ? detail.value.confirmPicture
  534. : detail.value.executeEvidence
  535. ).map((item) => item.filename);
  536. const auditStatusList = Array.isArray(detail.value?.executeEvidenceAuditStatus)
  537. ? detail.value.executeEvidenceAuditStatus
  538. : [];
  539. let imgs = []
  540. evidenceList.forEach((item, index) => {
  541. imgs.push({
  542. url: base_img_url2 + item,
  543. filePath: item,
  544. status: auditStatusList[index] === 2 ? 'failed' : 'success',
  545. message: auditStatusList[index] === 2 ? '审核失败' : '审核成功',
  546. });
  547. });
  548. console.log("evideimgsnceList", imgs);
  549. const isEdit = evidenceList?.length > 0 ? true : false;
  550. executePopupRef.value.openPopup(miniJson.value.recordId, {
  551. evidenceList: imgs,
  552. isEdit: isEdit,
  553. executeDate: detail.value.executeDate,
  554. executorOrganizationName: detail.value.executorOrganizationName,
  555. });
  556. };
  557. const showMapPopup = ref(false);
  558. const handleViewArea = () => {
  559. showMapPopup.value = true;
  560. }
  561. const toDraw = () => {
  562. router.push({
  563. path: "/draw_area",
  564. query: {
  565. subjectId: localStorage.getItem('selectedFarmId'),
  566. varietyId: miniJson.value.typeId,
  567. },
  568. });
  569. }
  570. const handleBack = () => {
  571. router.back();
  572. };
  573. const handleConvert = () => {
  574. const query = {
  575. askInfo: { title: "农事执行", content: "是否分享该农事给好友" },
  576. shareText: `${detail.value.farmWorkName}农事 已触发,请在最佳农时内执行`,
  577. targetUrl: `work_detail`,
  578. paramsPage: JSON.stringify({ ...miniJson.value, hideDraw: true }),
  579. imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/work_img.png',
  580. };
  581. wx.miniProgram.navigateTo({
  582. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  583. });
  584. };
  585. // 获取当前段的执行方式
  586. const getStageExecutionMethod = (stageIndex) => {
  587. const stage = stageList.value?.[stageIndex];
  588. const availableTabs = getAvailableExecutionTabs(stage);
  589. if (!availableTabs.length) return 1;
  590. const val = stageExecutionMethods.value[stageIndex];
  591. const isValidSelected = availableTabs.some(
  592. (tab) => Number(tab.value) === Number(val)
  593. );
  594. return isValidSelected ? val : availableTabs[0].value;
  595. };
  596. // 根据当前执行方式,获取对应的参数(配比、单亩用量、备注)
  597. const getPesticideParam = (item, stageIndex) => {
  598. if (!item?.params || !Array.isArray(item.params)) return null;
  599. const currentMethod = getStageExecutionMethod(stageIndex);
  600. return (
  601. item.params.find((param) => param.executionMethod === currentMethod) || null
  602. );
  603. };
  604. const getParamRemark = (item, stageIndex) => {
  605. const param = getPesticideParam(item, stageIndex);
  606. return param?.remark || item.remark || "";
  607. };
  608. const changeExecutionMethod = (stageIndex, value) => {
  609. const stage = stageList.value?.[stageIndex];
  610. const availableTabs = getAvailableExecutionTabs(stage);
  611. const isAllowed = availableTabs.some(
  612. (tab) => Number(tab.value) === Number(value)
  613. );
  614. if (!isAllowed) return;
  615. stageExecutionMethods.value = {
  616. ...stageExecutionMethods.value,
  617. [stageIndex]: value
  618. };
  619. };
  620. // 地图
  621. const mapContainer = ref(null);
  622. const areaMap = new AreaMap();
  623. </script>
  624. <style scoped lang="scss">
  625. .work-detail {
  626. height: 100vh;
  627. background: #f2f3f5;
  628. display: flex;
  629. flex-direction: column;
  630. .work-detail-content {
  631. flex: 1;
  632. overflow: auto;
  633. }
  634. }
  635. .content-status {
  636. position: relative;
  637. padding: 16px 12px 0 12px;
  638. color: #fff;
  639. z-index: 1;
  640. height: 92px;
  641. box-sizing: border-box;
  642. &::after {
  643. content: "";
  644. z-index: -1;
  645. position: absolute;
  646. left: 0;
  647. top: 0;
  648. height: 140px;
  649. background: #C7C7C7;
  650. width: 100%;
  651. }
  652. .status-l {
  653. .status-title {
  654. font-size: 22px;
  655. }
  656. .status-sub {
  657. margin-top: 10px;
  658. font-size: 12px;
  659. padding: 0 8px;
  660. background: #fff;
  661. color: rgba(62, 61, 61, 0.8);
  662. width: fit-content;
  663. height: 26px;
  664. line-height: 26px;
  665. border-radius: 2px;
  666. }
  667. }
  668. &.status-5 {
  669. &::after {
  670. background: #C7C7C7;
  671. }
  672. }
  673. &.status-0, &.status-1 {
  674. &::after {
  675. background: #FF953D;
  676. }
  677. }
  678. &.status-2 {
  679. &::after {
  680. background: #2199F8;
  681. }
  682. }
  683. &.status-6 {
  684. &::after {
  685. background: #2199F8;
  686. }
  687. }
  688. &.audit-2,
  689. &.audit-0 {
  690. &::after {
  691. background: #FF953D;
  692. }
  693. }
  694. &.audit-2 {
  695. padding-top: 30px;
  696. }
  697. &.status-4 {
  698. &::after {
  699. background: #37C11B;
  700. }
  701. .status-sub {
  702. color: #37C11B;
  703. }
  704. }
  705. }
  706. .work-wrap {
  707. position: relative;
  708. top: 0;
  709. padding: 0 12px 12px;
  710. z-index: 2;
  711. // &.has-bottom {
  712. // margin-bottom: 88px;
  713. // }
  714. // &.warning-info-show {
  715. // // margin-bottom: 168px;
  716. // margin-bottom: 88px;
  717. // }
  718. }
  719. .box-wrap {
  720. .work-info {
  721. background: #ffffff;
  722. border-radius: 8px;
  723. padding: 10px 10px;
  724. box-shadow: 0 2px 8px rgba(15, 35, 52, 0.06);
  725. }
  726. .photo-box {
  727. margin-top: 10px;
  728. padding: 11px 10px;
  729. font-size: 14px;
  730. .tips-text {
  731. color: #FA7406;
  732. padding: 5px 10px;
  733. border: 1px solid #FF953D;
  734. border-radius: 5px;
  735. margin-bottom: 10px;
  736. background: #fff;
  737. }
  738. .photo-title {
  739. color: #000;
  740. padding-bottom: 9px;
  741. }
  742. .photo-sub-title {
  743. padding-bottom: 9px;
  744. color: #767676;
  745. }
  746. }
  747. }
  748. .group-info {
  749. margin-bottom: 10px;
  750. background: #ffffff;
  751. border-radius: 8px;
  752. padding: 14px 10px 10px 10px;
  753. box-shadow: 0 2px 8px rgba(15, 35, 52, 0.06);
  754. &.group-box {
  755. padding: 10px;
  756. .group-name {
  757. font-size: 14px;
  758. color: #767676;
  759. .group-name-text {
  760. color: #000;
  761. }
  762. }
  763. }
  764. .group-name {
  765. font-size: 14px;
  766. color: rgba(0, 0, 0, 0.2);
  767. .group-name-text {
  768. color: #767676;
  769. }
  770. }
  771. .group-sub {
  772. margin-top: 6px;
  773. font-size: 12px;
  774. color: rgba(0, 0, 0, 0.45);
  775. line-height: 1.5;
  776. }
  777. }
  778. .stage-card+.stage-card {
  779. margin-top: 10px;
  780. }
  781. .info-item+.info-item {
  782. margin-top: 10px;
  783. }
  784. .info-item {
  785. .info-title {
  786. display: flex;
  787. align-items: center;
  788. gap: 4px;
  789. color: #2199F8;
  790. font-weight: 500;
  791. .title-block {
  792. width: 6px;
  793. height: 6px;
  794. background: #2199F8;
  795. border-radius: 1px;
  796. transform: rotate(45deg);
  797. }
  798. }
  799. .info-value {
  800. padding-top: 4px;
  801. color: #ACACAC;
  802. line-height: 21px;
  803. white-space: pre-line;
  804. }
  805. .blod-text {
  806. font-weight: 500;
  807. }
  808. }
  809. .map-box {
  810. position: relative;
  811. .map-title {
  812. z-index: 12;
  813. position: absolute;
  814. border-radius: 5px 0 0 5px;
  815. top: 0;
  816. left: 0;
  817. height: 25px;
  818. padding: 0 18px;
  819. line-height: 25px;
  820. font-size: 12px;
  821. font-weight: 500;
  822. color: #fff;
  823. background: rgba(0, 0, 0, 0.45);
  824. backdrop-filter: blur(4px);
  825. }
  826. .map-container {
  827. /* 略增高,配合 fit 后更易看清整块地 */
  828. height: 200px;
  829. width: 100%;
  830. clip-path: inset(0px round 5px);
  831. }
  832. }
  833. .area-list {
  834. margin-top: 10px;
  835. .area-item+.area-item {
  836. margin-top: 6px;
  837. }
  838. .area-item {
  839. display: flex;
  840. align-items: center;
  841. justify-content: space-between;
  842. padding: 10px;
  843. border: 1px solid rgba(0, 0, 0, 0.1);
  844. border-radius: 6px;
  845. .area-l {
  846. color: #000000;
  847. // font-weight: 500;
  848. .area-tag {
  849. background: rgba(98, 98, 98, 0.1);
  850. color: #626262;
  851. height: 22px;
  852. line-height: 22px;
  853. padding: 0 5px;
  854. border-radius: 2px;
  855. font-size: 12px;
  856. margin-left: 5px;
  857. display: inline-block;
  858. }
  859. }
  860. .area-r {
  861. background: #2199F8;
  862. color: #fff;
  863. font-size: 12px;
  864. font-weight: 500;
  865. cursor: pointer;
  866. height: 28px;
  867. line-height: 28px;
  868. padding: 0 10px;
  869. border-radius: 20px;
  870. }
  871. }
  872. }
  873. .ecological-plant-card {
  874. margin-top: 10px;
  875. // padding: 10px;
  876. // background: rgba(244, 244, 244, 0.8);
  877. border-radius: 8px;
  878. text-align: left;
  879. }
  880. .ecological-plant-text {
  881. color: #666666;
  882. font-size: 14px;
  883. line-height: 1.45;
  884. }
  885. .ecological-plant-line--sub {
  886. margin-top: 6px;
  887. }
  888. .ecological-plant-gallery {
  889. display: flex;
  890. gap: 12px;
  891. }
  892. .ecological-plant-thumb {
  893. width: 80px;
  894. height: 80px;
  895. object-fit: cover;
  896. border-radius: 8px;
  897. display: block;
  898. }
  899. .stage-card {
  900. .stage-header {
  901. padding-bottom: 12px;
  902. display: flex;
  903. align-items: center;
  904. gap: 5px;
  905. .stage-title {
  906. font-size: 16px;
  907. color: #000;
  908. }
  909. .title-tag {
  910. width: fit-content;
  911. font-size: 12px;
  912. height: 26px;
  913. line-height: 26px;
  914. color: #2199F8;
  915. background: rgba(33, 153, 248, 0.1);
  916. border-radius: 20px;
  917. padding: 0 8px;
  918. }
  919. .tag-0 {
  920. color: #FF953D;
  921. background: rgba(255, 149, 61, 0.1);
  922. }
  923. }
  924. .stage-info {
  925. padding: 8px 0 2px;
  926. border-top: 1px solid #f5f5f5;
  927. }
  928. }
  929. .form-item {
  930. display: flex;
  931. font-size: 14px;
  932. color: #767676;
  933. margin-top: 4px;
  934. .area-btn {
  935. border: 1px solid rgba(0, 0, 0, 0.1);
  936. padding: 0 10px;
  937. color: #767676;
  938. }
  939. .area-btn-right {
  940. padding-right: 4px;
  941. display: inline-flex;
  942. align-items: center;
  943. gap: 4px;
  944. }
  945. .item-name {
  946. padding-right: 26px;
  947. color: rgba(0, 0, 0, 0.2);
  948. }
  949. .item-text {
  950. flex: 1;
  951. line-height: 21px;
  952. &.light-text {
  953. color: #2199F8;
  954. }
  955. }
  956. .area-text {
  957. display: inline-flex;
  958. align-items: center;
  959. gap: 10px;
  960. }
  961. }
  962. .light-text {
  963. color: #2199F8;
  964. }
  965. .stage-tabs {
  966. display: inline-flex;
  967. gap: 8px;
  968. margin-top: 8px;
  969. .tab-pill {
  970. padding: 0 8px;
  971. font-size: 14px;
  972. text-align: center;
  973. border-radius: 2px;
  974. height: 28px;
  975. line-height: 28px;
  976. color: #767676;
  977. background: rgba(171, 171, 171, 0.1);
  978. &.active {
  979. background: rgba(33, 153, 248, 0.1);
  980. color: #2199F8;
  981. }
  982. }
  983. }
  984. .prescription-wrap {
  985. margin-top: 10px;
  986. overflow: hidden;
  987. .prescription-title {
  988. padding: 8px 10px;
  989. font-size: 13px;
  990. font-weight: 500;
  991. border-bottom: 1px solid rgba(225, 225, 225, 0.6);
  992. }
  993. }
  994. .prescription-table {
  995. font-size: 12px;
  996. text-align: center;
  997. border-radius: 6px;
  998. border: 1px solid rgba(225, 225, 225, 0.6);
  999. .table-header {
  1000. display: flex;
  1001. background: rgba(171, 171, 171, 0.1);
  1002. padding: 9px 6px;
  1003. color: #767676;
  1004. }
  1005. .table-row {
  1006. display: flex;
  1007. padding: 12px 6px;
  1008. border-bottom: 1px solid rgba(0, 0, 0, 0.08);
  1009. color: rgba(0, 0, 0, 0.76);
  1010. &:last-child {
  1011. border-bottom: none;
  1012. }
  1013. }
  1014. .col {
  1015. display: flex;
  1016. align-items: center;
  1017. justify-content: center;
  1018. padding: 0 2px;
  1019. }
  1020. .col-type {
  1021. width: 56px;
  1022. }
  1023. .col-name {
  1024. flex: 1;
  1025. }
  1026. .col-ratio {
  1027. width: 64px;
  1028. }
  1029. .col-dose {
  1030. width: 64px;
  1031. }
  1032. }
  1033. .prescription-remark {
  1034. margin-top: 8px;
  1035. padding: 7px 10px;
  1036. border-radius: 6px;
  1037. color: #767676;
  1038. background: rgba(171, 171, 171, 0.1);
  1039. line-height: 21px;
  1040. }
  1041. .photo-img-wrap {
  1042. display: flex;
  1043. flex-wrap: wrap;
  1044. gap: 10px;
  1045. ::v-deep {
  1046. .PhotoConsumer {
  1047. width: 31%;
  1048. height: 92px;
  1049. }
  1050. }
  1051. .photo-img {
  1052. width: 100%;
  1053. height: 100%;
  1054. position: relative;
  1055. box-sizing: border-box;
  1056. border: 2px solid transparent;
  1057. border-radius: 8px;
  1058. overflow: hidden;
  1059. .fail-icon {
  1060. position: absolute;
  1061. top: 0;
  1062. left: 0;
  1063. width: 100%;
  1064. height: 100%;
  1065. background: rgba(0, 0, 0, 0.6);
  1066. border-radius: 8px;
  1067. display: flex;
  1068. align-items: center;
  1069. justify-content: center;
  1070. flex-direction: column;
  1071. gap: 4px;
  1072. .fail-icon-text {
  1073. color: #FF943D;
  1074. font-size: 12px;
  1075. }
  1076. }
  1077. img {
  1078. width: 100%;
  1079. height: 100%;
  1080. border-radius: 8px;
  1081. object-fit: cover;
  1082. }
  1083. }
  1084. }
  1085. .execute-action {
  1086. display: flex;
  1087. align-items: center;
  1088. justify-content: center;
  1089. // justify-content: space-between;
  1090. gap: 16px;
  1091. .action-item {
  1092. padding: 0 26px;
  1093. height: 40px;
  1094. line-height: 40px;
  1095. border-radius: 26px;
  1096. box-sizing: border-box;
  1097. font-size: 14px;
  1098. &.second {
  1099. background: #ffffff;
  1100. border: 0.5px solid rgba(153, 153, 153, 0.5);
  1101. color: #999999;
  1102. }
  1103. &.primary {
  1104. background: linear-gradient(180deg, #8ACBFF 0%, #2199F8 100%);
  1105. color: #ffffff;
  1106. }
  1107. }
  1108. &.no-share {
  1109. justify-content: center;
  1110. }
  1111. }
  1112. .warning-info {
  1113. position: fixed;
  1114. bottom: 100px;
  1115. left: 10px;
  1116. width: calc(100% - 20px);
  1117. background: linear-gradient(180deg, #FFDFC5 -13%, #FFFFFF 39%);
  1118. border-radius: 8px;
  1119. padding: 14px 10px;
  1120. box-sizing: border-box;
  1121. display: flex;
  1122. align-items: center;
  1123. justify-content: space-between;
  1124. box-shadow: 0px 4px 4px 0px #0000001A;
  1125. .warning-l {
  1126. .warning-title {
  1127. display: inline-flex;
  1128. align-items: center;
  1129. gap: 6px;
  1130. font-size: 16px;
  1131. color: #FF953D;
  1132. }
  1133. .warning-text {
  1134. color: rgba(0, 0, 0, 0.4);
  1135. font-size: 12px;
  1136. // margin-top: 4px;
  1137. }
  1138. }
  1139. .warning-r {
  1140. background: #FF953D;
  1141. color: #fff;
  1142. font-size: 12px;
  1143. cursor: pointer;
  1144. padding: 0 7px;
  1145. height: 32px;
  1146. line-height: 32px;
  1147. border-radius: 20px;
  1148. }
  1149. }
  1150. .fixed-btn-wrap {
  1151. display: flex;
  1152. justify-content: center;
  1153. // justify-content: space-between;
  1154. &.center-btn {
  1155. justify-content: center;
  1156. }
  1157. position: fixed;
  1158. bottom: 0px;
  1159. left: 0;
  1160. right: 0;
  1161. background: #fff;
  1162. padding: 10px 12px 36px 12px;
  1163. box-sizing: border-box;
  1164. // box-shadow: 0 -2px 8px rgba(15, 35, 52, 0.06);
  1165. box-shadow: 2px 2px 4.5px 0px #00000066;
  1166. .fixed-btn {
  1167. min-width: 110px;
  1168. height: 40px;
  1169. line-height: 40px;
  1170. text-align: center;
  1171. border-radius: 20px;
  1172. background: linear-gradient(180deg, #70bffe, #2199f8);
  1173. color: #ffffff;
  1174. font-size: 14px;
  1175. }
  1176. }
  1177. .map-popup {
  1178. width: 92%;
  1179. // max-width: 420px;
  1180. border-radius: 8px;
  1181. padding: 12px;
  1182. box-sizing: border-box;
  1183. }
  1184. </style>