treeAlbumPopup.vue 9.8 KB

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