index.vue 40 KB

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