plan.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <template>
  2. <div class="plan-page">
  3. <custom-header :name="pageType === 'plant' ? '种植方案' : '农事规划'"></custom-header>
  4. <div class="plan-content">
  5. <div class="plan-content-header" v-if="pageType === 'plant'">
  6. <el-select class="select-item" v-model="specieValue" placeholder="选择品类" @change="getListMySchemes">
  7. <el-option
  8. v-for="item in options"
  9. :key="item.id"
  10. :label="item.name"
  11. :value="item.defaultContainerId"
  12. />
  13. </el-select>
  14. <tab-list type="light" v-model="active" :tabs="tabs" @change="handleTabChange" />
  15. </div>
  16. <farm-work-plan-timeline
  17. class="timeline-container"
  18. :class="{
  19. 'timeline-container-plant': pageType == 'plant',
  20. 'timeline-container-no-permission': !hasPlanPermission,
  21. }"
  22. :pageType="pageType"
  23. :containerId="specieValue"
  24. @row-click="handleRowClick"
  25. @edit="handleEdit"
  26. :disableClick="!hasPlanPermission"
  27. />
  28. </div>
  29. <div class="custom-bottom-fixed-btns" v-has-permission="'农事规划'">
  30. <div class="bottom-btn-group">
  31. <div class="bottom-btn secondary-btn" @click="handlePhenologySetting">物候期设置</div>
  32. <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'" @click="openCopyPlanPopup">
  33. {{ defaultContainerId === specieValue ? "复制方案" : "方案设置" }}
  34. </div>
  35. </div>
  36. <div class="bottom-btn primary-btn" @click="addNewTask">新增农事</div>
  37. </div>
  38. </div>
  39. <!-- 农事信息弹窗 -->
  40. <detail-dialog ref="detailDialogRef" @triggerFarmWork="triggerFarmWork"></detail-dialog>
  41. <!-- 互动设置弹窗 -->
  42. <interact-popup
  43. ref="interactPopupRef"
  44. @handleSaveSuccess="getFarmWorkPlanForPhenology"
  45. @handleDeleteInteract="handleDeleteInteract"
  46. ></interact-popup>
  47. <!-- 复制方案弹窗 -->
  48. <Popup v-model:show="showCopyPlan" class="copy-plan-popup" round closeable :close-on-click-overlay="false">
  49. <div class="copy-plan-content">
  50. <div class="label">{{ defaultContainerId === specieValue ? "复制为" : "方案名称" }}</div>
  51. <el-input v-model="copyPlanName" size="large" placeholder="请输入方案名称" class="copy-plan-input" />
  52. </div>
  53. <div class="copy-plan-footer">
  54. <div class="btn btn-cancel" @click="handleCancelCopyPlan">{{ defaultContainerId === specieValue ? "取消复制" : "删除方案" }}</div>
  55. <div class="btn btn-confirm" @click="handleConfirmCopyPlan">
  56. {{ active === 1 ? "确定复制" : "确定设置" }}
  57. </div>
  58. </div>
  59. </Popup>
  60. <!-- 物候期设置弹窗 -->
  61. <Popup
  62. v-model:show="showPhenologySetting"
  63. class="copy-plan-popup phenology-popup"
  64. round
  65. closeable
  66. :close-on-click-overlay="false"
  67. >
  68. <div class="phenology-header">物候期时间设置</div>
  69. <div class="phenology-list">
  70. <div class="phenology-item" v-for="(item, index) in mergedReproductiveList" :key="item.id || index">
  71. <div class="item-label">
  72. <span class="label-text">{{ item.name }}</span>
  73. <span>起始时间</span>
  74. </div>
  75. <div class="item-value">
  76. <el-date-picker
  77. style="width: 100%"
  78. size="large"
  79. value-format="YYYY-MM-DD"
  80. v-model="item.startDate"
  81. type="date"
  82. placeholder="选择日期"
  83. />
  84. </div>
  85. </div>
  86. </div>
  87. <div class="phenology-footer" @click="handleConfirmPhenologySetting">确认设置</div>
  88. </Popup>
  89. </template>
  90. <script setup>
  91. import { ref, onMounted, computed } from "vue";
  92. import { Popup } from "vant";
  93. import customHeader from "@/components/customHeader.vue";
  94. import tabList from "@/components/pageComponents/TabList.vue";
  95. import FarmWorkPlanTimeline from "@/components/pageComponents/FarmWorkPlanTimeline.vue";
  96. import { useRouter, useRoute } from "vue-router";
  97. import detailDialog from "@/components/detailDialog.vue";
  98. import eventBus from "@/api/eventBus";
  99. import interactPopup from "@/components/popup/interactPopup.vue";
  100. import { ElMessage } from "element-plus";
  101. const router = useRouter();
  102. const route = useRoute();
  103. // 检查是否有"农事规划"权限
  104. const hasPlanPermission = computed(() => {
  105. try {
  106. const userInfoStr = localStorage.getItem("localUserInfo");
  107. if (!userInfoStr) return false;
  108. const userInfo = JSON.parse(userInfoStr);
  109. const permissions = userInfo.agriculturalPermissions || [];
  110. return permissions.includes("农事规划");
  111. } catch (error) {
  112. console.error("解析用户信息失败:", error);
  113. return false;
  114. }
  115. });
  116. const pageType = ref("");
  117. onMounted(() => {
  118. pageType.value = route.query.pageType || "";
  119. if (pageType.value === "plant") {
  120. getSpecieList();
  121. }
  122. });
  123. // 获取品类列表
  124. const specieValue = ref(null);
  125. const defaultContainerId = ref(null);
  126. const options = ref([]);
  127. const userInfo = JSON.parse(localStorage.getItem("localUserInfo"));
  128. const getSpecieList = () => {
  129. VE_API.farm.fetchSpecieList({ agriculturalId: userInfo.agriculturalId }).then(({ data }) => {
  130. options.value = data || [];
  131. specieValue.value = data[0].defaultContainerId;
  132. defaultContainerId.value = data[0].defaultContainerId;
  133. getListMySchemes();
  134. });
  135. };
  136. // 获取方案列表
  137. const active = ref(null);
  138. const tabs = ref([]);
  139. const getListMySchemes = () => {
  140. VE_API.home.listMySchemes({ containerId: specieValue.value }).then(({ data }) => {
  141. tabs.value = data || [];
  142. active.value = data[0].id;
  143. defaultContainerId.value = data[0].containerId;
  144. getFarmWorkPlanForPhenology();
  145. });
  146. };
  147. const handleTabChange = (id, item) => {
  148. active.value = id;
  149. console.log(id, item);
  150. };
  151. const mergedReproductiveList = ref([]);
  152. const containerSpaceTimeId = ref(null);
  153. const getPhenologyList = async () => {
  154. const res = await VE_API.monitor.listPhenology({
  155. containerSpaceTimeId: containerSpaceTimeId.value,
  156. });
  157. if (res.code === 0) {
  158. mergedReproductiveList.value = res.data || [];
  159. }
  160. };
  161. // 获取农事规划数据以获取 containerSpaceTimeId
  162. const getFarmWorkPlanForPhenology = async () => {
  163. try {
  164. const { data, code } = await VE_API.monitor.farmWorkPlan({
  165. containerId: specieValue.value,
  166. });
  167. if (code === 0 && data?.phenologyList?.[0]?.containerSpaceTimeId) {
  168. containerSpaceTimeId.value = data.phenologyList[0].containerSpaceTimeId;
  169. await getPhenologyList(containerSpaceTimeId.value);
  170. }
  171. } catch (error) {
  172. console.error("获取农事规划数据失败:", error);
  173. }
  174. };
  175. // 复制方案弹窗相关
  176. const showCopyPlan = ref(false);
  177. const showPhenologySetting = ref(false);
  178. const copyPlanName = ref("");
  179. const openCopyPlanPopup = () => {
  180. copyPlanName.value = "";
  181. showCopyPlan.value = true;
  182. };
  183. // 物候期设置弹窗
  184. const handlePhenologySetting = () => {
  185. showPhenologySetting.value = true;
  186. };
  187. /**
  188. * 确认物候期设置
  189. */
  190. const handleConfirmPhenologySetting = async () => {
  191. const params = {
  192. farmId: route.query.farmId,
  193. items: mergedReproductiveList.value.map((item) => ({
  194. phenologyId: item.id,
  195. startDate: item.startDate,
  196. })),
  197. };
  198. const res = await VE_API.monitor.batchSaveFarmPhenologyTime(params);
  199. if (res.code === 0) {
  200. ElMessage.success("设置成功");
  201. showPhenologySetting.value = false;
  202. getFarmWorkPlanForPhenology();
  203. }
  204. };
  205. // 取消复制方案
  206. const handleCancelCopyPlan = () => {
  207. showCopyPlan.value = false;
  208. };
  209. // 确定复制方案
  210. const handleConfirmCopyPlan = () => {
  211. if (!copyPlanName.value.trim()) {
  212. ElMessage.warning("请输入方案名称");
  213. return;
  214. }
  215. if (defaultContainerId.value === specieValue.value) {
  216. VE_API.monitor.copyScheme({
  217. containerId: specieValue.value,
  218. sourceSchemeId: active.value,
  219. }).then(({ code }) => {
  220. if (code === 0) {
  221. showCopyPlan.value = false;
  222. ElMessage.success("复制成功");
  223. getListMySchemes();
  224. }
  225. });
  226. }
  227. };
  228. // 新增农事
  229. const addNewTask = () => {
  230. ElMessage.warning("该功能正在升级中,敬请期待");
  231. };
  232. const triggerFarmWork = () => {
  233. eventBus.emit("activeUpload:show", {
  234. gardenIdVal: route.query.farmId,
  235. problemTitleVal: "请选择您出现" + curFarmObj.value.farmWorkName + "的时间",
  236. arrangeIdVal: curFarmObj.value.id,
  237. needExecutorVal: true,
  238. });
  239. };
  240. const curFarmObj = ref({});
  241. const handleRowClick = (item) => {
  242. curFarmObj.value = item;
  243. router.push({
  244. path: "/modify",
  245. query: {
  246. id: item.id,
  247. farmId: route.query.farmId,
  248. farmWorkId: item.farmWorkId,
  249. containerSpaceTimeId: item.containerSpaceTimeId,
  250. agriculturalStoreId: route.query.agriculturalStoreId,
  251. },
  252. });
  253. };
  254. const interactPopupRef = ref(null);
  255. const handleEdit = (item) => {
  256. console.log(item);
  257. if (interactPopupRef.value) {
  258. interactPopupRef.value.showPopup(item);
  259. }
  260. };
  261. const handleDeleteInteract = (params) => {
  262. getFarmWorkPlanForPhenology();
  263. };
  264. </script>
  265. <style scoped lang="scss">
  266. .plan-page {
  267. width: 100%;
  268. height: 100vh;
  269. background: #f5f7fb;
  270. .plan-content {
  271. padding: 12px 0;
  272. .plan-content-header {
  273. display: flex;
  274. align-items: center;
  275. gap: 12px;
  276. margin-bottom: 10px;
  277. margin-left: 12px;
  278. .select-item {
  279. width: 82px;
  280. ::v-deep {
  281. .el-select__wrapper {
  282. box-shadow: none;
  283. border-radius: 25px;
  284. border: 0.5px solid rgba(153, 153, 153, 0.5);
  285. }
  286. }
  287. }
  288. }
  289. .timeline-container {
  290. height: calc(100vh - 40px - 73px);
  291. padding: 0 12px;
  292. &.timeline-container-plant {
  293. height: calc(100vh - 40px - 73px - 38px);
  294. }
  295. // 没有权限时,底部按钮不显示,高度增加 73px
  296. &.timeline-container-no-permission {
  297. height: calc(100vh - 40px - 58px);
  298. }
  299. }
  300. }
  301. // 控制区域样式
  302. .custom-bottom-fixed-btns {
  303. .bottom-btn-group {
  304. display: flex;
  305. gap: 12px;
  306. }
  307. }
  308. }
  309. .copy-plan-popup {
  310. width: 100%;
  311. padding: 50px 12px 20px 12px;
  312. &::before {
  313. content: "";
  314. position: absolute;
  315. top: 0;
  316. left: 0;
  317. width: 100%;
  318. height: 136px;
  319. background: url("@/assets/img/monitor/popup-header-bg.png") no-repeat center center / 100% 100%;
  320. }
  321. .copy-plan-content {
  322. display: flex;
  323. align-items: center;
  324. gap: 12px;
  325. .label {
  326. font-size: 16px;
  327. font-weight: 500;
  328. }
  329. .copy-plan-input {
  330. width: calc(100% - 80px);
  331. }
  332. }
  333. .copy-plan-footer {
  334. display: flex;
  335. gap: 12px;
  336. margin-top: 20px;
  337. .btn {
  338. flex: 1;
  339. color: #666666;
  340. border: 1px solid #999999;
  341. border-radius: 25px;
  342. padding: 10px 0;
  343. font-size: 16px;
  344. text-align: center;
  345. &.btn-confirm {
  346. color: #fff;
  347. border: 1px solid #2199f8;
  348. background: #2199f8;
  349. }
  350. }
  351. }
  352. }
  353. .phenology-popup {
  354. padding: 28px 20px 20px;
  355. .phenology-header {
  356. font-size: 24px;
  357. text-align: center;
  358. margin-bottom: 20px;
  359. font-family: "PangMenZhengDao";
  360. }
  361. .phenology-list {
  362. width: 100%;
  363. .phenology-item {
  364. width: 100%;
  365. display: flex;
  366. align-items: center;
  367. justify-content: space-between;
  368. .item-label {
  369. display: flex;
  370. align-items: center;
  371. gap: 4px;
  372. font-size: 15px;
  373. color: rgba(0, 0, 0, 0.4);
  374. .label-text {
  375. color: #000;
  376. font-size: 16px;
  377. font-weight: 500;
  378. }
  379. }
  380. .item-value {
  381. width: calc(100% - 156px);
  382. }
  383. }
  384. .phenology-item + .phenology-item {
  385. margin-top: 10px;
  386. }
  387. }
  388. .phenology-footer {
  389. width: 100%;
  390. text-align: center;
  391. font-size: 16px;
  392. margin-top: 20px;
  393. color: #fff;
  394. background: #2199f8;
  395. border-radius: 25px;
  396. padding: 10px 0;
  397. }
  398. }
  399. </style>