weatherInfo.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. <template>
  2. <div class="weather-info" :class="{ expanded: isExpanded, 'is-garden': isGarden ,'bg-white': isWhite}">
  3. <div class="header flex-center">
  4. <div class="header-left">
  5. <div class="address-select flex-center" v-if="isGarden">
  6. <el-dropdown class="select-garden" trigger="click" popper-class="select-garden-popper">
  7. <div class="el-dropdown-link flex-center">
  8. <span class="ellipsis-l1">{{ farmName }}</span>
  9. <div class="default-text" v-show="isDefaultFarm">默认</div>
  10. <el-icon class="el-icon--right"><arrow-down /></el-icon>
  11. </div>
  12. <template #dropdown>
  13. <el-dropdown-menu>
  14. <el-dropdown-item
  15. @click="handleCommand(item)"
  16. v-for="item in farmList"
  17. :key="item.id"
  18. :class="{ 'selected-active-garden': farmId === item.id }"
  19. >
  20. <span>{{ item.name }}</span>
  21. <span v-if="item.defaultOption" class="dropdown-default-text">默认</span>
  22. </el-dropdown-item>
  23. </el-dropdown-menu>
  24. </template>
  25. </el-dropdown>
  26. <div class="add-garden" @click="handleAddGarden">新增农场</div>
  27. </div>
  28. <div class="temperature flex-center">
  29. <div class="temperature-number">{{ currentWeather.temp || '--' }}</div>
  30. <div class="temperature-text">
  31. <span>{{ locationName || '--' }}</span>
  32. <div class="temperature-text-time">
  33. <span>{{ currentWeather.text }}-</span>
  34. <span>{{ currentDateText }}</span>
  35. <span v-show="!isExpanded" class="temperature-text-more" @click="toggleExpand">
  36. 展开更多天气
  37. </span>
  38. </div>
  39. </div>
  40. </div>
  41. </div>
  42. <div class="weather-icon" v-if="currentWeather.iconDay">
  43. <i :class="'qi-'+currentWeather.iconDay + '-fill'"></i>
  44. </div>
  45. <!-- <div class="weather-icon" v-else>
  46. <img :src="`https://birdseye-img.sysuimars.com/weather/${currentWeather.iconDay}.svg`" alt="" />
  47. </div> -->
  48. </div>
  49. <div class="weather-chart-container">
  50. <div class="weather-chart-title">
  51. <span>未来七天天气</span>
  52. <div class="weather-chart-title-more" @click="toggleExpand">收起</div>
  53. </div>
  54. <weather-chart class="weather-chart" :weather-data="weatherData"></weather-chart>
  55. </div>
  56. </div>
  57. </template>
  58. <script setup>
  59. import { ref, onActivated, computed, watch, onMounted } from "vue";
  60. import weatherChart from "./weatherChart.vue";
  61. import { useRouter } from "vue-router";
  62. import { useStore } from "vuex";
  63. const store = useStore();
  64. const props = defineProps({
  65. isGarden: {
  66. type: Boolean,
  67. default: false
  68. },
  69. gardenId: {
  70. type: [Number, String],
  71. default: null
  72. }
  73. });
  74. // 定义emit事件
  75. const emit = defineEmits(['weatherExpanded','changeGarden']);
  76. const router = useRouter();
  77. const handleCommand = ({id, name}) => {
  78. farmName.value = name;
  79. farmId.value = id;
  80. // 更新默认农场标识
  81. const selectedFarm = farmList.value.find(farm => farm.id === id);
  82. isDefaultFarm.value = selectedFarm ? selectedFarm.defaultOption || false : false;
  83. // 保存用户选择的农场到 localStorage
  84. localStorage.setItem('selectedFarmId', id);
  85. localStorage.setItem('selectedFarmName', name);
  86. emit('changeGarden',{id, name});
  87. };
  88. const isExpanded = ref(false);
  89. const isWhite = ref(false);
  90. const toggleExpand = () => {
  91. if(props.isGarden){
  92. isWhite.value = !isWhite.value;
  93. }
  94. isExpanded.value = !isExpanded.value;
  95. emit('weatherExpanded',isExpanded.value);
  96. };
  97. const farmId = ref(null);
  98. const farmName = ref("");
  99. const farmList = ref([]);
  100. const isDefaultFarm = ref(false); // 添加默认农场标识
  101. // 根据传入的gardenId设置农场(先刷新列表再设置)
  102. async function setFarmByGardenId(gardenIdValue) {
  103. if (!gardenIdValue) {
  104. return false;
  105. }
  106. // 先刷新农场列表,确保数据是最新的
  107. return new Promise((resolve) => {
  108. VE_API.farm.userFarmSelectOption().then(({data}) => {
  109. farmList.value = data || [];
  110. if (data && data.length > 0) {
  111. const targetFarm = data.find(farm => farm.id == gardenIdValue);
  112. if (targetFarm) {
  113. farmName.value = targetFarm.name;
  114. farmId.value = Number(gardenIdValue);
  115. isDefaultFarm.value = targetFarm.defaultOption || false;
  116. // 保存到 localStorage
  117. localStorage.setItem('selectedFarmId', farmId.value);
  118. localStorage.setItem('selectedFarmName', farmName.value);
  119. emit('changeGarden', { id: farmId.value, name: farmName.value });
  120. resolve(true);
  121. } else {
  122. resolve(false);
  123. }
  124. } else {
  125. resolve(false);
  126. }
  127. }).catch(() => {
  128. resolve(false);
  129. });
  130. });
  131. }
  132. // 获取农场列表
  133. function getFarmList() {
  134. // 如果传入了 gardenId,优先使用 setFarmByGardenId(它会刷新列表并设置)
  135. if (props.gardenId) {
  136. setFarmByGardenId(props.gardenId).then((setSuccess) => {
  137. // 如果设置失败,使用已获取的列表数据执行默认逻辑(避免重复请求)
  138. if (!setSuccess && farmList.value && farmList.value.length > 0) {
  139. selectFarmFromList(farmList.value);
  140. } else if (!setSuccess) {
  141. // 如果列表为空,再次获取列表
  142. getFarmListWithoutGardenId();
  143. }
  144. });
  145. return;
  146. }
  147. // 如果没有传入 gardenId,执行正常逻辑
  148. getFarmListWithoutGardenId();
  149. }
  150. // 从列表中选择农场(使用已有列表数据)
  151. function selectFarmFromList(data) {
  152. // 使用 localStorage 中保存的农场选择
  153. const savedFarmId = localStorage.getItem('selectedFarmId');
  154. const savedFarmName = localStorage.getItem('selectedFarmName');
  155. if (savedFarmId && savedFarmName) {
  156. // 检查保存的农场是否还在当前列表中
  157. const savedFarm = data.find(farm => farm.id == savedFarmId);
  158. if (savedFarm) {
  159. farmName.value = savedFarmName;
  160. farmId.value = Number(savedFarmId);
  161. isDefaultFarm.value = savedFarm.defaultOption || false;
  162. } else {
  163. // 如果保存的农场不在列表中,按优先级选择
  164. selectDefaultFarm(data);
  165. }
  166. } else {
  167. // 如果没有保存的选择,按优先级选择
  168. selectDefaultFarm(data);
  169. }
  170. emit('changeGarden',{id: farmId.value, name: farmName.value});
  171. }
  172. // 获取农场列表(不处理传入的gardenId)
  173. function getFarmListWithoutGardenId() {
  174. VE_API.farm.userFarmSelectOption().then(({data}) => {
  175. farmList.value = data || []
  176. if (data && data.length > 0) {
  177. selectFarmFromList(data);
  178. }
  179. })
  180. }
  181. // 监听父组件传入的gardenId变化
  182. watch(() => props.gardenId, (newGardenId) => {
  183. if (newGardenId) {
  184. // 直接调用 setFarmByGardenId,它会刷新列表并设置
  185. setFarmByGardenId(newGardenId);
  186. }
  187. }, { immediate: false });
  188. // 选择默认农场的逻辑
  189. function selectDefaultFarm(data) {
  190. // 首先查找 defaultOption 为 true 的农场
  191. const defaultFarm = data.find(farm => farm.defaultOption === true);
  192. if (defaultFarm) {
  193. // 如果有默认农场,选择它
  194. farmName.value = defaultFarm.name;
  195. farmId.value = defaultFarm.id;
  196. isDefaultFarm.value = true;
  197. } else {
  198. // 如果没有默认农场,选择第一个
  199. farmName.value = data[0].name;
  200. farmId.value = data[0].id;
  201. isDefaultFarm.value = data[0].defaultOption || false;
  202. }
  203. // 保存到 localStorage
  204. localStorage.setItem('selectedFarmId', farmId.value);
  205. localStorage.setItem('selectedFarmName', farmName.value);
  206. }
  207. onMounted(() => {
  208. getLocationName();
  209. getWeatherData();
  210. })
  211. onActivated(() => {
  212. getFarmList();
  213. getWeatherData();
  214. });
  215. // 暴露刷新方法供父组件调用
  216. defineExpose({
  217. refreshFarmList: getFarmList,
  218. toggleExpand
  219. });
  220. const locationName = ref("");
  221. const weatherData = ref(null);
  222. const currentWeather = ref({ temp: "--", text: "--", iconDay: "" });
  223. const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
  224. function getLocationName() {
  225. const location = store.state.home.miniUserLocation;
  226. // 将 location 从"经度,纬度"格式转换为"纬度,经度"格式
  227. let formattedLocation = location;
  228. if (typeof location === 'string' && location.includes(',')) {
  229. const [lng, lat] = location.split(',');
  230. formattedLocation = `${lat},${lng}`;
  231. }
  232. const params = {
  233. key: MAP_KEY,
  234. location: formattedLocation,
  235. };
  236. VE_API.old_mini_map.location(params).then(({ result }) => {
  237. // locationVal.value = result.formatted_addresses.recommend;
  238. locationName.value = result.address_component
  239. ? result.address_component.city + result.address_component.district
  240. : result.address + "";
  241. });
  242. }
  243. const handleAddGarden = () => {
  244. router.push(`/create_farm?isReload=true&from=monitor`)
  245. }
  246. // 获取天气数据
  247. function getWeatherData() {
  248. const point = store.state.home.miniUserLocationPoint;
  249. if (!point) {
  250. return;
  251. }
  252. VE_API.old_mini_map.get7d({ point }).then(({ data }) => {
  253. if (data && data.daily && data.daily.length > 0) {
  254. weatherData.value = data;
  255. // 设置当前天气(第一天的数据)
  256. const today = data.daily[0];
  257. currentWeather.value = {
  258. temp: today.tempMax || today.tempMin || 26,
  259. text: today.textDay || "晴天",
  260. iconDay: today.iconDay
  261. };
  262. }
  263. }).catch(() => {
  264. // 获取天气数据失败,使用默认值
  265. });
  266. }
  267. // 获取当前日期和星期
  268. const currentDateText = computed(() => {
  269. const now = new Date();
  270. const month = String(now.getMonth() + 1).padStart(2, '0');
  271. const day = String(now.getDate()).padStart(2, '0');
  272. return `${month}/${day}`;
  273. });
  274. </script>
  275. <style lang="scss" scoped>
  276. .weather-info {
  277. width: 100%;
  278. height: 58px;
  279. border-radius: 14px;
  280. background-color: #ffffff;
  281. padding: 0 12px;
  282. box-sizing: border-box;
  283. transition: height 0.3s ease-in-out;
  284. overflow: hidden;
  285. &.expanded {
  286. height: 312px;
  287. }
  288. &.is-garden {
  289. border-radius: 0;
  290. background-image: none;
  291. }
  292. &.bg-white{
  293. border-radius: 14px;
  294. background-image: linear-gradient(90deg, #e2f1fe 0%, #ffffff 80%);
  295. }
  296. .flex-center {
  297. display: flex;
  298. align-items: center;
  299. }
  300. .header {
  301. display: flex;
  302. justify-content: space-between;
  303. .header-left {
  304. .address-select {
  305. .select-garden {
  306. width: fit-content;
  307. max-width: 170px;
  308. margin-right: 8px;
  309. .el-dropdown-link {
  310. font-size: 15px;
  311. font-weight: 500;
  312. color: #000000;
  313. span {
  314. width: fit-content;
  315. max-width: 95%;
  316. margin-right: 4px;
  317. }
  318. .default-text {
  319. font-size: 12px;
  320. color: #fff;
  321. padding: 2px 4px;
  322. width: 44px;
  323. text-align: center;
  324. box-sizing: border-box;
  325. font-weight: 400;
  326. background-color: #2199f8;
  327. border-radius: 25px;
  328. }
  329. }
  330. }
  331. .add-garden {
  332. font-size: 12px;
  333. color: #2199f8;
  334. padding: 3px 10px;
  335. border: 1px solid rgba(33, 153, 248, 0.5);
  336. border-radius: 25px;
  337. }
  338. }
  339. .temperature {
  340. .temperature-number {
  341. font-size: 40px;
  342. position: relative;
  343. margin-right: 14px;
  344. &::after {
  345. content: "°";
  346. font-size: 18px;
  347. position: absolute;
  348. right: -6px;
  349. top: 2px;
  350. }
  351. }
  352. .temperature-text {
  353. font-weight: 500;
  354. .temperature-text-time {
  355. font-size: 12px;
  356. font-weight: 400;
  357. }
  358. .temperature-text-more {
  359. font-size: 12px;
  360. color: #2199f8;
  361. margin-left: 10px;
  362. font-weight: 500;
  363. cursor: pointer;
  364. }
  365. }
  366. }
  367. }
  368. .header-right {
  369. width: 84px;
  370. height: 73px;
  371. }
  372. .weather-icon {
  373. i {
  374. font-size: 47px;
  375. color: #a7cffb;
  376. }
  377. }
  378. }
  379. .weather-chart-container {
  380. width: 100%;
  381. height: 100%;
  382. overflow: hidden;
  383. margin-top: 8px;
  384. .weather-chart-title {
  385. display: flex;
  386. justify-content: space-between;
  387. align-items: center;
  388. font-size: 12px;
  389. font-weight: 500;
  390. .weather-chart-title-more {
  391. color: #828282;
  392. cursor: pointer;
  393. padding: 3px 10px;
  394. border-radius: 25px;
  395. font-weight: 400;
  396. border: 1px solid rgba(130, 130, 130, 0.5);
  397. }
  398. }
  399. .weather-chart {
  400. margin-top: 5px;
  401. width: 100%;
  402. height: 180px;
  403. }
  404. }
  405. }
  406. </style>
  407. <style lang="scss">
  408. .select-garden-popper {
  409. max-height: calc(100vh - 200px);
  410. overflow-y: auto;
  411. &.el-dropdown__popper {
  412. .el-dropdown__list {
  413. max-width: 250px;
  414. }
  415. .el-dropdown-menu__item{
  416. background-color: transparent !important;
  417. color: #606266 !important;
  418. }
  419. .selected-active-garden {
  420. color: #2199f8 !important;
  421. background-color: rgba(33, 153, 248, 0.1) !important;
  422. font-weight: 500;
  423. }
  424. }
  425. .dropdown-default-text {
  426. font-size: 11px;
  427. color: #2199f8;
  428. margin-left: 8px;
  429. padding: 0 5px;
  430. background-color: rgba(33, 153, 248, 0.1);
  431. border-radius: 10px;
  432. font-weight: 400;
  433. }
  434. }
  435. </style>