AgriculturalDynamics.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. <template>
  2. <div class="agricultural-dynamics">
  3. <!-- 任务列表 -->
  4. <div class="task-list">
  5. <div v-for="(task, index) in taskList" :key="index" class="task-item">
  6. <div class="task-content" @click="handleTaskAction(task)">
  7. <div class="task-header">
  8. <div class="task-status-tag">{{ task.flowStatus === 4 ? '待完成' : '待复核' }}</div>
  9. <div class="task-title">{{ task.farmWorkName }}</div>
  10. </div>
  11. <div class="task-time" v-if="task.flowStatus === 5">
  12. 复核时间
  13. {{ task.reviewDate }}</div>
  14. <div class="task-time" v-if="task.flowStatus === 4 && task.expectedExecuteDate">执行时间 {{ task.expectedExecuteDate }}</div>
  15. <div class="task-time deadline" v-if="task.flowStatus === 4 && !task.expectedExecuteDate">截止时间 {{ task.executeDeadlineDate }}</div>
  16. </div>
  17. <div v-if="task.flowStatus === 5" class="task-action" :class="{ 'call-text': getButtonText(task) }" @click="handleAction(task)">
  18. {{ getButtonText(task) ? '提醒复核' : '上传复核照片' }}
  19. </div>
  20. <div v-else-if="task.flowStatus === 4 && task.expectedExecuteDate" class="task-action" :class="{ 'call-text': getButtonText(task) }" @click="showOfferPopup(task)">
  21. {{ getButtonText(task) ? "提醒执行" : "上传照片" }}
  22. </div>
  23. <div v-else-if="task.flowStatus === 4 && !task.expectedExecuteDate" class="task-action orange" :class="{ 'call-text': getButtonText(task) }" @click="selectExecuteTime(task)">
  24. {{ getButtonText(task) ? "提醒确认执行时间" : "确认执行时间" }}
  25. </div>
  26. </div>
  27. </div>
  28. <div class="title">农情互动</div>
  29. <!-- 内容区域 -->
  30. <div class="agricultural-list">
  31. <div v-if="!unansweredList.length" class="empty-block">暂无数据</div>
  32. <template v-else>
  33. <div class="agricultural-item" v-for="(item, index) in unansweredList" :key="index">
  34. <!-- 头部区域 -->
  35. <div class="header-section">
  36. <div class="header-left">
  37. <div class="farm-name van-ellipsis">{{ item.farmName }}</div>
  38. <div class="tag-group">
  39. <div class="tag tag-blue">{{ item.typeName }}</div>
  40. <div class="tag" :class="{'tag-orange': item.userType === 2}">{{ item.userType === 1 ? '普通客户' : '托管客户' }}</div>
  41. </div>
  42. </div>
  43. <div class="remind-btn" @click="handleRemind(item)">提醒客户</div>
  44. </div>
  45. <!-- 警告通知块 -->
  46. <div
  47. class="warning-block"
  48. v-if="item.latestPhenologyProgressBroadcast"
  49. v-html="item.latestPhenologyProgressBroadcast?.content"
  50. ></div>
  51. <!-- 时间轴组件,只负责时间轴区域 -->
  52. <AgriculturalInteractionCard :item="item" @updateList="updateAllData" />
  53. </div>
  54. </template>
  55. </div>
  56. </div>
  57. <offer-popup ref="offerPopupRef" @uploadSuccess="getTaskList"></offer-popup>
  58. <!-- 确认执行时间 -->
  59. <calendar
  60. teleport="#app"
  61. v-model:show="showCalendar"
  62. @confirm="onConfirmExecuteTime"
  63. :min-date="minDate"
  64. :max-date="maxDate"
  65. />
  66. <!-- 上传复核照片 -->
  67. <upload-execute ref="uploadExecuteRef" :onlyShare="false" @uploadSuccess="handleUploadSuccess" />
  68. </template>
  69. <script setup>
  70. import router from "@/router";
  71. import { ref, onMounted, onActivated } from "vue";
  72. import { Calendar } from "vant";
  73. import wx from "weixin-js-sdk";
  74. import { ElMessage } from "element-plus";
  75. import offerPopup from "@/components/popup/offerPopup.vue";
  76. import AgriculturalInteractionCard from "@/components/pageComponents/AgriculturalInteractionCard.vue";
  77. import { formatDate } from "@/common/commonFun";
  78. import uploadExecute from "@/views/old_mini/task_condition/components/uploadExecute.vue";
  79. // 任务列表数据
  80. const taskList = ref([]);
  81. const handleTaskAction = (item) => {
  82. router.push({
  83. path: "/completed_work",
  84. query: { miniJson: JSON.stringify({ id: item.id }) },
  85. });
  86. };
  87. // 计算复核时间:executeDate + reviewIntervalDays
  88. const calculateReviewDate = (task) => {
  89. if (!task?.executeDate) {
  90. return "--";
  91. }
  92. const executeDate = new Date(task.executeDate);
  93. const reviewIntervalDays = Number(task?.reviewIntervalDays || 0);
  94. // 将执行日期加上间隔天数
  95. executeDate.setDate(executeDate.getDate() + reviewIntervalDays);
  96. // 格式化为 YYYY-MM-DD
  97. const year = executeDate.getFullYear();
  98. const month = String(executeDate.getMonth() + 1).padStart(2, "0");
  99. const day = String(executeDate.getDate()).padStart(2, "0");
  100. return `${year}-${month}-${day}`;
  101. };
  102. onMounted(() => {
  103. const userInfo = JSON.parse(localStorage.getItem("localUserInfo"));
  104. agriculturalRole.value = userInfo.agriculturalRole;
  105. userId.value = userInfo.id;
  106. });
  107. const agriculturalRole = ref(null);
  108. const userId = ref(null);
  109. const offerPopupRef = ref(null);
  110. const showOfferPopup = (item) => {
  111. if(getButtonText(item)) {
  112. const query = {
  113. askInfo: { title: "农事提醒", content: "是否分享该农事提醒给好友" },
  114. shareText: '向您分享了一条农事执行提醒,请您尽快执行',
  115. targetUrl: `completed_work`,
  116. paramsPage: JSON.stringify({id: item.id}),
  117. imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/invite_bg.png',
  118. };
  119. wx.miniProgram.navigateTo({
  120. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  121. });
  122. }else{
  123. offerPopupRef.value.openPopup(item);
  124. }
  125. };
  126. const uploadExecuteRef = ref(null);
  127. function handleAction(item) {
  128. if(getButtonText(item)) {
  129. const query = {
  130. askInfo: { title: "农事提醒", content: "是否分享该农事提醒给好友" },
  131. shareText: '向您分享了一条农事复核提醒,请您尽快复核',
  132. targetUrl: `review_work`,
  133. paramsPage: JSON.stringify({id: item.id}),
  134. imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/invite_bg.png',
  135. };
  136. wx.miniProgram.navigateTo({
  137. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  138. });
  139. }else{
  140. setTimeout(() => {
  141. uploadExecuteRef.value.showPopup(item, "share-sheet");
  142. }, 10);
  143. }
  144. }
  145. function handleUploadSuccess() {
  146. getTaskList();
  147. }
  148. const selectExecuteTime = (item) => {
  149. if (getButtonText(item)) {
  150. const query = {
  151. askInfo: { title: "农事提醒", content: "是否分享该农事提醒给好友" },
  152. shareText: '向您分享了一条农事提醒,请您尽快确认执行时间',
  153. targetUrl: `completed_work`,
  154. paramsPage: JSON.stringify({id: item.id}),
  155. imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/invite_bg.png',
  156. };
  157. wx.miniProgram.navigateTo({
  158. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  159. });
  160. } else {
  161. executeItem.value = item;
  162. maxDate.value = new Date(item.executeDeadlineDate);
  163. showCalendar.value = true;
  164. }
  165. };
  166. // 获取上传按钮的文本(计算属性方式)
  167. const getButtonText = (item) => {
  168. return agriculturalRole.value === 1 || (agriculturalRole.value === 2 && item.executorUserId != userId.value);
  169. };
  170. const showCalendar = ref(false);
  171. const maxDate = ref();
  172. // 最小日期设置为今天,今天可以选择
  173. const minDate = new Date();
  174. const executeItem = ref(null);
  175. const onConfirmExecuteTime = (date) => {
  176. showCalendar.value = false;
  177. VE_API.z_farm_work_record
  178. .updateExpectedExecuteDate({ recordId: executeItem.value.id, expectedExecuteDate: formatDate(date) })
  179. .then((res) => {
  180. if (res.code === 0) {
  181. ElMessage.success("操作成功");
  182. getTaskList();
  183. }
  184. });
  185. };
  186. const handleRemind = (item) => {
  187. router.push(`/remind_customer?farmId=${item.farmId}`);
  188. };
  189. onActivated(() => {
  190. updateAllData();
  191. });
  192. const updateAllData = () => {
  193. getUnansweredFarms();
  194. getTaskList();
  195. };
  196. //农情互动的农场列表接口(分页)
  197. const getTaskList = async () => {
  198. // 同时获取待完成(4)和待复核(5)的任务
  199. const [res4, res5] = await Promise.all([
  200. VE_API.z_farm_work_record.generalPendingFarmWork({ role: 2, flowStatus: 4 }),
  201. VE_API.z_farm_work_record.generalPendingFarmWork({ role: 2, flowStatus: 5 })
  202. ]);
  203. const tasks4 = (res4.data || []).map(task => ({
  204. ...task,
  205. reviewDate: calculateReviewDate(task)
  206. }));
  207. const tasks5 = (res5.data || []).map(task => ({
  208. ...task,
  209. reviewDate: calculateReviewDate(task)
  210. }));
  211. // 合并两种状态的任务,总共取前2个
  212. taskList.value = [...tasks4, ...tasks5].slice(0, 2);
  213. };
  214. const unansweredList = ref([]);
  215. const getUnansweredFarms = async () => {
  216. const params = {
  217. page: 0,
  218. limit: 3,
  219. flowStatus: 5,
  220. };
  221. const res = await VE_API.home.listUnansweredFarms(params);
  222. unansweredList.value = (res.data || []).map((item) => ({
  223. ...item,
  224. timelineList: [],
  225. }));
  226. // 串行请求,一个完成后再请求下一个
  227. if (unansweredList.value.length) {
  228. for (let i = 0; i < unansweredList.value.length; i++) {
  229. await getFutureFarmWorkWarning(unansweredList.value[i]);
  230. }
  231. }
  232. unansweredList.value = unansweredList.value.filter(item => item.timelineList.length > 0);
  233. };
  234. //查询未来农事预警
  235. const getFutureFarmWorkWarning = async (item) => {
  236. const res = await VE_API.home.listFutureFarmWorkWarning({ farmId: item.farmId });
  237. item.timelineList = res.data || [];
  238. };
  239. </script>
  240. <style scoped lang="scss">
  241. .agricultural-dynamics {
  242. padding: 0 10px;
  243. // 任务列表样式
  244. .task-list {
  245. .task-item {
  246. display: flex;
  247. align-items: center;
  248. justify-content: space-between;
  249. background-color: #ffffff;
  250. border-radius: 8px;
  251. padding: 12px;
  252. margin-top: 10px;
  253. position: relative;
  254. overflow: hidden;
  255. &::after {
  256. content: "";
  257. position: absolute;
  258. top: -10px;
  259. right: 0;
  260. width: 72px;
  261. height: 72px;
  262. pointer-events: none;
  263. background: url("@/assets/img/home/task-icon.png") no-repeat center center / 100% 100%;
  264. }
  265. .task-content {
  266. flex: 1;
  267. .task-header {
  268. display: flex;
  269. align-items: center;
  270. margin-bottom: 4px;
  271. gap: 8px;
  272. }
  273. .task-status-tag {
  274. background-color: rgba(33, 153, 248, 0.1);
  275. color: #2199f8;
  276. font-size: 12px;
  277. padding: 1px 6px;
  278. border-radius: 2px;
  279. }
  280. .task-title {
  281. font-size: 16px;
  282. font-weight: 500;
  283. color: #1d2129;
  284. }
  285. .task-time {
  286. font-size: 12px;
  287. color: rgba(78, 89, 105, 0.5);
  288. &.deadline {
  289. color: rgba(255, 85, 85, 0.7);
  290. }
  291. }
  292. }
  293. .task-action {
  294. flex: none;
  295. background-color: rgba(33, 153, 248, 0.1);
  296. color: #2199f8;
  297. border-radius: 25px;
  298. padding: 0px 14px;
  299. font-size: 12px;
  300. height: 28px;
  301. box-sizing: border-box;
  302. line-height: 28px;
  303. &.orange {
  304. color: #ff953d;
  305. background: rgba(255, 149, 61, 0.1);
  306. }
  307. &.call-text {
  308. color: #868686;
  309. background: none;
  310. border: 0.5px solid #D1D1D1;
  311. }
  312. }
  313. }
  314. }
  315. .title {
  316. font-size: 16px;
  317. font-weight: 500;
  318. color: #1d2129;
  319. margin-top: 10px;
  320. }
  321. .agricultural-list {
  322. .empty-block {
  323. display: flex;
  324. align-items: center;
  325. justify-content: center;
  326. height: 120px;
  327. color: #a0a0a0;
  328. font-size: 14px;
  329. background-color: #ffffff;
  330. border-radius: 8px;
  331. margin-top: 8px;
  332. }
  333. .agricultural-item {
  334. background-color: #ffffff;
  335. border-radius: 8px;
  336. padding: 8px 12px;
  337. margin-top: 8px;
  338. // 头部区域样式
  339. .header-section {
  340. display: flex;
  341. align-items: center;
  342. justify-content: space-between;
  343. padding-bottom: 5px;
  344. border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
  345. .header-left {
  346. display: flex;
  347. align-items: center;
  348. gap: 8px;
  349. width: calc(100% - 80px);
  350. .farm-name {
  351. font-size: 16px;
  352. font-weight: 500;
  353. color: #1d2129;
  354. max-width: calc(100% - 130px);
  355. }
  356. .tag-group {
  357. display: flex;
  358. gap: 4px;
  359. .tag {
  360. padding: 2px 8px;
  361. border-radius: 2px;
  362. font-size: 12px;
  363. color: #848282;
  364. background-color: rgba(148, 148, 148, 0.1);
  365. &.tag-blue {
  366. background-color: #e8f3ff;
  367. color: #2199f8;
  368. }
  369. &.tag-orange {
  370. background-color: rgba(255, 149, 61, 0.1);
  371. color: #ff953d;
  372. }
  373. }
  374. }
  375. }
  376. .remind-btn {
  377. background-color: #2199f8;
  378. color: #ffffff;
  379. border-radius: 25px;
  380. padding: 7px 12px;
  381. font-size: 12px;
  382. }
  383. }
  384. // 警告通知块样式
  385. .warning-block {
  386. background-color: rgba(33, 153, 248, 0.1);
  387. border-radius: 5px;
  388. padding: 5px;
  389. margin-top: 8px;
  390. font-size: 12px;
  391. color: #252525;
  392. ::v-deep {
  393. p {
  394. padding: 0;
  395. margin: 0;
  396. }
  397. }
  398. }
  399. }
  400. }
  401. }
  402. </style>
  403. <style lang="scss">
  404. .van-calendar__popup {
  405. z-index: 9999 !important;
  406. }
  407. </style>