plan.vue 15 KB

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