treeAlbumPopup.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <up-popup :show="showPopup" mode="center" round="20" @close="handleClose">
  3. <view class="album-popup">
  4. <view class="album-title">
  5. <view class="name">
  6. 果树相册
  7. <view class="sub-name">无人机实时监测生长情况</view>
  8. </view>
  9. <image class="icon" :src="`${config.BASIC_IMG}img/treePage/drone-icon-popup.png`"></image>
  10. </view>
  11. <view class="album-cont">
  12. <scroll-view class="time-line-scroll" scroll-x :scroll-into-view="scrollIntoViewId" scroll-with-animation>
  13. <view class="time-line">
  14. <view class="time-item" v-for="(item,index) in dateList" :key="index" :id="`d-${item.dateStr}`" @click="handleDateClick(item.dateStr)">
  15. <text v-if="item.showYear" class="year-flag">{{ item.year }}</text>
  16. <text :style="{color:(selectedDateStr===item.dateStr)?'#2199F8':'#777777'}">{{item.display}}</text>
  17. <view class="dot" :style="{background:(selectedDateStr===item.dateStr)?'#2199F8':'#777777'}"></view>
  18. <text class="today" v-if="item.isToday">今</text>
  19. </view>
  20. </view>
  21. </scroll-view>
  22. <view class="swiper-wrap">
  23. <swiper class="swiper">
  24. <swiper-item v-for="(item,index) in photoList" :key="index">
  25. <image class="img" :src="getImageUrl(item.filename)" mode="aspectFill"></image>
  26. <!-- <view class="text-wrap">
  27. <view class="date">{{item.uploadDate}}</view>
  28. <view class="code">{{item.treeCode}}</view>
  29. </view> -->
  30. </swiper-item>
  31. </swiper>
  32. <view class="arrow left" @click="handlePrevDay">
  33. <up-icon name="arrow-left" bold color="#F0D09C" size="22"></up-icon>
  34. </view>
  35. <view class="arrow right" @click="handleNextDay">
  36. <up-icon name="arrow-right" bold color="#F0D09C" size="22"></up-icon>
  37. </view>
  38. </view>
  39. </view>
  40. </view>
  41. </up-popup>
  42. </template>
  43. <script setup>
  44. import config from "@/api/config.js"
  45. import TREE from '@/api/tree.js'
  46. import {
  47. ref,
  48. watch,
  49. nextTick
  50. } from "vue";
  51. const resize = "?x-oss-process=image/resize,w_1000";
  52. const props = defineProps({
  53. show: {
  54. type: Boolean,
  55. defalut: false,
  56. },
  57. farmBuyId: {
  58. type: [String, Number],
  59. default: ''
  60. },
  61. sampleId: {
  62. type: [String, Number],
  63. default: 110939
  64. }
  65. });
  66. const showPopup = ref(false);
  67. const handleClose = ()=>{
  68. showPopup.value = false
  69. }
  70. const dateList = ref([])
  71. const scrollIntoViewId = ref('')
  72. const selectedDateStr = ref('')
  73. const formatToDisplay = (dateStr)=>{
  74. // dateStr: YYYY-MM-DD
  75. const [y,m,d] = dateStr.split('-')
  76. return `${m}/${d}`
  77. }
  78. const getTodayStr = ()=>{
  79. const d = new Date()
  80. const mm = `${d.getMonth()+1}`.padStart(2,'0')
  81. const dd = `${d.getDate()}`.padStart(2,'0')
  82. return `${d.getFullYear()}-${mm}-${dd}`
  83. }
  84. const fetchHasImageDates = async ()=>{
  85. if(!props.farmBuyId) return
  86. try{
  87. const {data} = await TREE.findHasImagesDate({farmBuyId: props.farmBuyId})
  88. const today = getTodayStr()
  89. const arr = Array.isArray(data) ? data : []
  90. const mapped = arr.map(ds=>{
  91. const dateStr = (typeof ds === 'string') ? ds.split(' ')[0] : ''
  92. const isToday = dateStr === today
  93. return {
  94. dateStr,
  95. display: dateStr ? formatToDisplay(dateStr) : '',
  96. isToday,
  97. year: dateStr ? dateStr.slice(0,4) : ''
  98. }
  99. }).filter(i=>i.dateStr)
  100. mapped.sort((a,b)=> a.dateStr.localeCompare(b.dateStr))
  101. for(let i=0;i<mapped.length;i++){
  102. mapped[i].showYear = i===0 || mapped[i].year !== mapped[i-1].year
  103. }
  104. dateList.value = mapped
  105. if(dateList.value.length){
  106. const last = dateList.value[dateList.value.length-1]
  107. selectedDateStr.value = last.dateStr
  108. // 强制等渲染后再滚动到最右端
  109. scrollIntoViewId.value = ''
  110. await nextTick()
  111. scrollIntoViewId.value = `d-${last.dateStr}`
  112. }else{
  113. selectedDateStr.value = ''
  114. scrollIntoViewId.value = ''
  115. }
  116. }catch(e){
  117. // 静默失败
  118. }
  119. }
  120. const fetchTreeImages = async (dateStr)=>{
  121. try{
  122. const params = {
  123. page: 1,
  124. limit: 1,
  125. treeId: Number(props.sampleId) || 110939,
  126. date: dateStr,
  127. }
  128. const {data} = await TREE.treeImageList(params)
  129. photoList.value = data || []
  130. }catch(err){
  131. console.log('treeImageList error:', err)
  132. }
  133. }
  134. const getImageUrl = (filename) => {
  135. if (filename.startsWith("https")) {
  136. return filename; // 直接使用完整 URL
  137. } else {
  138. return config.BASE_IMG_URL + filename + resize; // 拼接基础 URL
  139. }
  140. };
  141. const handleDateClick = (dateStr)=>{
  142. selectedDateStr.value = dateStr
  143. scrollIntoViewId.value = `d-${dateStr}`
  144. fetchTreeImages(dateStr)
  145. }
  146. const handlePrevDay = () => {
  147. if(!selectedDateStr.value) return
  148. const currentDate = new Date(selectedDateStr.value)
  149. const prevDate = new Date(currentDate)
  150. prevDate.setDate(currentDate.getDate() - 1)
  151. const prevDateStr = prevDate.toISOString().split('T')[0]
  152. selectedDateStr.value = prevDateStr
  153. scrollIntoViewId.value = `d-${prevDateStr}`
  154. fetchTreeImages(prevDateStr)
  155. }
  156. const handleNextDay = () => {
  157. if(!selectedDateStr.value) return
  158. const currentDate = new Date(selectedDateStr.value)
  159. const nextDate = new Date(currentDate)
  160. nextDate.setDate(currentDate.getDate() + 1)
  161. const today = new Date()
  162. const todayStr = today.toISOString().split('T')[0]
  163. // 不能超过当前日期
  164. if(nextDate.toISOString().split('T')[0] > todayStr) {
  165. uni.showToast({
  166. title: '不能超过当前日期',
  167. icon: 'none',
  168. duration: 2000
  169. })
  170. return
  171. }
  172. const nextDateStr = nextDate.toISOString().split('T')[0]
  173. selectedDateStr.value = nextDateStr
  174. scrollIntoViewId.value = `d-${nextDateStr}`
  175. fetchTreeImages(nextDateStr)
  176. }
  177. const photoList = ref([]);
  178. watch(
  179. () => props.show,
  180. async (val) => {
  181. showPopup.value = val;
  182. if(val){
  183. await fetchHasImageDates()
  184. const today = getTodayStr()
  185. fetchTreeImages(today)
  186. }
  187. }
  188. );
  189. watch(
  190. () => props.farmBuyId,
  191. (val) => {
  192. if(showPopup.value && val){
  193. fetchHasImageDates()
  194. }
  195. }
  196. )
  197. </script>
  198. <style lang="scss" scoped>
  199. .album-popup {
  200. width: 92vw;
  201. .album-title {
  202. padding: 30rpx 0 46rpx 30rpx;
  203. position: relative;
  204. background-image: linear-gradient(120deg, #79C4FF 13%, #FFFFFF 66%);
  205. border-radius: 40rpx 40rpx 0 0;
  206. line-height: 44rpx;
  207. .name {
  208. color: #004275;
  209. font-size: 48rpx;
  210. font-family: 'PangMenZhengDao';
  211. .sub-name {
  212. font-size: 24rpx;
  213. }
  214. }
  215. .icon {
  216. position: absolute;
  217. top: -100rpx;
  218. right: -26rpx;
  219. width: 364rpx;
  220. height: 300rpx;
  221. }
  222. }
  223. .album-cont {
  224. position: relative;
  225. z-index: 2;
  226. margin-top: -34rpx;
  227. padding: 32rpx 0 20rpx;
  228. background: #fff;
  229. border-radius: 40rpx;
  230. .time-line-scroll{
  231. margin-bottom: 16rpx;
  232. width: 100%;
  233. white-space: nowrap;
  234. }
  235. .time-line{
  236. display: flex;
  237. padding: 0 20rpx;
  238. }
  239. .time-item{
  240. min-width: 100rpx;
  241. font-size: 24rpx;
  242. color: #777777;
  243. display: flex;
  244. flex-direction: column;
  245. align-items: center;
  246. position: relative;
  247. padding: 0 12rpx;
  248. &::before{
  249. content: '';
  250. position: absolute;
  251. top: 44%;
  252. left: 0;
  253. right: 0;
  254. height: 2rpx;
  255. background: rgba(136, 136, 136, 0.1);
  256. }
  257. .year-flag{
  258. font-size: 20rpx;
  259. color: #999;
  260. margin-bottom: 6rpx;
  261. }
  262. .dot{
  263. width: 14rpx;
  264. height: 14rpx;
  265. background: #777777;
  266. border-radius: 50%;
  267. }
  268. .today{
  269. color: #2199F8;
  270. margin-top: 8rpx;
  271. }
  272. }
  273. .swiper-wrap{
  274. position: relative;
  275. padding: 0 20rpx;
  276. .swiper {
  277. width: 100%;
  278. height: 496rpx;
  279. .img{
  280. width: 100%;
  281. height: 100%;
  282. border-radius: 16rpx;
  283. }
  284. .text-wrap{
  285. position: absolute;
  286. bottom: 20rpx;
  287. left: 20rpx;
  288. font-size: 20rpx;
  289. color: #fff;
  290. .date{
  291. font-size: 40rpx;
  292. font-family: 'SMILEYSANS';
  293. }
  294. }
  295. }
  296. .arrow{
  297. position: absolute;
  298. top: calc(50% - 40rpx);
  299. width: 80rpx;
  300. height: 80rpx;
  301. display: flex;
  302. align-items: center;
  303. justify-content: center;
  304. border-radius: 50%;
  305. background: rgba(0, 0, 0, .6);
  306. }
  307. .left{
  308. left: 32rpx;
  309. }
  310. .right{
  311. right: 32rpx;
  312. }
  313. }
  314. }
  315. }
  316. </style>