diff --git a/components/tab-bar/postList.vue b/components/tab-bar/postList.vue index 5799399..edbc7a2 100644 --- a/components/tab-bar/postList.vue +++ b/components/tab-bar/postList.vue @@ -60,6 +60,7 @@ > + {{formatBadgeCount(fishUnread)}} {{item.name}} {{item.action == 'publishSell' ? '点我发布' : item.desc}} @@ -114,7 +115,7 @@ {{item.xiangyao}} @@ -133,6 +134,17 @@ + + + + + @@ -141,7 +153,8 @@ listFishCategories, pageFishGoods, pageFishWants, - toggleFishLike + toggleFishLike, + getFishUnreadSummary } from '@/common/fishMarketApi.js' const POST_LIST_IMAGE_CACHE_KEY = 'post_list_image_cache_v1' @@ -169,7 +182,7 @@ desc: '大家想要的', category: '求购', imageKey: 'wanted', - action: 'category' + action: 'wantSquare' }, { name: '拼团购', desc: '暂未开放', @@ -195,14 +208,18 @@ imageKey: 'graduation', action: 'mine' }], - baseCategories: ['推荐', '附近', '最新', '求购'], + baseCategories: ['推荐', '最新'], categoryList: [], goodsList: [], pageNum: 1, pageSize: 10, totalPages: 1, loading: false, - sortType: 'hot' + sortType: 'hot', + likeEffectSeq: 0, + likeEffectTimers: {}, + pageLikeEffects: [], + fishUnread: 0 } }, computed: { @@ -232,6 +249,14 @@ }, created() { this.applyCachedPostListImages() + uni.$on('fishUnreadChange', this.handleFishUnreadChange) + }, + beforeDestroy() { + Object.keys(this.likeEffectTimers || {}).forEach(key => { + clearTimeout(this.likeEffectTimers[key]) + }) + this.likeEffectTimers = {} + uni.$off('fishUnreadChange', this.handleFishUnreadChange) }, methods: { getPostListImage(key) { @@ -375,8 +400,32 @@ } } this.loadCategories() + this.loadFishUnread() this.reloadGoods() }, + formatBadgeCount(count) { + const value = Number(count) || 0 + return value > 99 ? '99+' : value + }, + loadFishUnread() { + const token = uni.getStorageSync('hiver_token') + if (!token) { + this.fishUnread = 0 + return + } + getFishUnreadSummary().then(res => { + this.fishUnread = Number(res && res.fishUnread) || 0 + }).catch(() => { + this.fishUnread = 0 + }) + }, + handleFishUnreadChange(summary) { + if (summary && summary.fishUnread !== undefined) { + this.fishUnread = Number(summary.fishUnread) || 0 + return + } + this.loadFishUnread() + }, handleSearch() { uni.hideKeyboard() this.reloadGoods() @@ -410,6 +459,10 @@ this.goFishSessions() return } + if (item.action == 'wantSquare') { + this.goWantSquare() + return + } const index = this.categories.indexOf(item.category) if (index > -1) { this.categoryIndex = index @@ -555,12 +608,36 @@ uni.showToast({ title: '商品ID不能为空', icon: 'none' }) return } + const shouldAnimate = !item.liked + if (shouldAnimate) { + this.playLikeEffect(item) + } toggleFishLike({ goodsId: item.id, likeType: 'goods' }).then(res => { if (!res) return item.liked = !!res.liked item.xiangyao = Number(res.count) || 0 }) }, + playLikeEffect(item) { + const burstId = Date.now() + '-' + (++this.likeEffectSeq) + const tracks = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'] + const effects = tracks.map((name, index) => ({ + id: burstId + '-' + index, + className: 'page-like-heart--' + name, + style: `left:${6 + ((index * 59) % 86)}%; animation-delay:${index * 95}ms;` + })) + this.pageLikeEffects = effects + const timerKey = 'page' + if (this.likeEffectTimers[timerKey]) { + clearTimeout(this.likeEffectTimers[timerKey]) + } + this.likeEffectTimers[timerKey] = setTimeout(() => { + if (this.pageLikeEffects.length && String(this.pageLikeEffects[0].id).indexOf(burstId) === 0) { + this.pageLikeEffects = [] + } + delete this.likeEffectTimers[timerKey] + }, 3050) + }, goDetail(item) { if (!item) return if (item.type == 'want') { @@ -585,6 +662,11 @@ uni.navigateTo({ url: '/package2/IdleTrad/sessionList' }) + }, + goWantSquare() { + uni.navigateTo({ + url: '/package2/IdleTrad/wantList' + }) } } } @@ -816,7 +898,7 @@ } .shortcut-item--publish { - margin-top: -8rpx; + margin-top: 4rpx; } .shortcut-item--publish::before { @@ -836,6 +918,7 @@ width: 84rpx; height: 84rpx; margin: 0 auto 10rpx; + position: relative; } .shortcut-item--publish .shortcut-icon { @@ -852,6 +935,24 @@ display: block; } + .shortcut-badge { + position: absolute; + right: -8rpx; + top: -8rpx; + min-width: 30rpx; + height: 30rpx; + padding: 0 8rpx; + border-radius: 18rpx; + background: #ff4d5f; + color: #fff; + font-size: 18rpx; + font-weight: 900; + line-height: 30rpx; + text-align: center; + box-shadow: 0 6rpx 14rpx rgba(255, 77, 95, .26); + box-sizing: border-box; + } + .shortcut-name { color: #29342f; font-size: 21rpx; @@ -1084,6 +1185,7 @@ } .want-count { + position: relative; display: flex; align-items: center; gap: 3rpx; @@ -1091,6 +1193,142 @@ font-size: 17rpx; } + .page-like-effect { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + pointer-events: none; + z-index: 99999; + } + + .page-like-heart { + position: absolute; + bottom: -230rpx; + width: 120rpx; + height: 120rpx; + opacity: 0; + transform-origin: center; + animation-duration: 2600ms; + animation-timing-function: cubic-bezier(.14, .66, .22, 1); + animation-fill-mode: forwards; + } + + .page-like-heart-shape { + position: absolute; + left: 50%; + top: 50%; + width: 72%; + height: 72%; + background: linear-gradient(135deg, rgba(255, 176, 207, .72), rgba(255, 88, 148, .36)); + border-radius: 18rpx 18rpx 10rpx 18rpx; + box-shadow: + 0 18rpx 42rpx rgba(255, 104, 164, .28), + inset 14rpx 18rpx 28rpx rgba(255, 255, 255, .32); + filter: blur(.2rpx); + transform: translate(-50%, -50%) rotate(45deg); + } + + .page-like-heart-shape::before, + .page-like-heart-shape::after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + border-radius: 50%; + background: inherit; + box-shadow: inherit; + } + + .page-like-heart-shape::before { + left: -50%; + top: 0; + } + + .page-like-heart-shape::after { + left: 0; + top: -50%; + } + + .page-like-heart--one { width: 154rpx; height: 154rpx; animation-name: pageHeartFloatOne; } + .page-like-heart--two { width: 112rpx; height: 112rpx; animation-name: pageHeartFloatTwo; } + .page-like-heart--three { width: 188rpx; height: 188rpx; animation-name: pageHeartFloatThree; } + .page-like-heart--four { width: 92rpx; height: 92rpx; animation-name: pageHeartFloatFour; } + .page-like-heart--five { width: 136rpx; height: 136rpx; animation-name: pageHeartFloatFive; } + .page-like-heart--six { width: 82rpx; height: 82rpx; animation-name: pageHeartFloatSix; } + .page-like-heart--seven { width: 166rpx; height: 166rpx; animation-name: pageHeartFloatSeven; } + .page-like-heart--eight { width: 104rpx; height: 104rpx; animation-name: pageHeartFloatEight; } + + .page-like-heart--three .page-like-heart-shape, + .page-like-heart--seven .page-like-heart-shape { + background: linear-gradient(135deg, rgba(255, 198, 220, .66), rgba(255, 103, 158, .28)); + filter: blur(1rpx); + } + + .page-like-heart--four .page-like-heart-shape, + .page-like-heart--six .page-like-heart-shape, + .page-like-heart--eight .page-like-heart-shape { + background: linear-gradient(135deg, rgba(255, 211, 226, .74), rgba(255, 126, 174, .32)); + } + + @keyframes pageHeartFloatOne { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.68) rotate(-8deg); } + 14% { opacity: .82; transform: translate3d(-18rpx, -92rpx, 0) scale(1) rotate(5deg); } + 62% { opacity: .52; transform: translate3d(-72rpx, -370rpx, 0) scale(.94) rotate(-12deg); } + 100% { opacity: 0; transform: translate3d(-112rpx, -650rpx, 0) scale(.72) rotate(-22deg); } + } + + @keyframes pageHeartFloatTwo { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.62) rotate(6deg); } + 16% { opacity: .7; transform: translate3d(28rpx, -112rpx, 0) scale(.92) rotate(-6deg); } + 64% { opacity: .44; transform: translate3d(96rpx, -350rpx, 0) scale(.82) rotate(12deg); } + 100% { opacity: 0; transform: translate3d(146rpx, -600rpx, 0) scale(.62) rotate(22deg); } + } + + @keyframes pageHeartFloatThree { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.64) rotate(-4deg); } + 12% { opacity: .64; transform: translate3d(8rpx, -86rpx, 0) scale(.94) rotate(7deg); } + 60% { opacity: .38; transform: translate3d(-36rpx, -420rpx, 0) scale(.78) rotate(-12deg); } + 100% { opacity: 0; transform: translate3d(-68rpx, -710rpx, 0) scale(.58) rotate(-24deg); } + } + + @keyframes pageHeartFloatFour { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.58) rotate(12deg); } + 18% { opacity: .68; transform: translate3d(-26rpx, -124rpx, 0) scale(.86) rotate(-6deg); } + 66% { opacity: .4; transform: translate3d(-112rpx, -320rpx, 0) scale(.72) rotate(14deg); } + 100% { opacity: 0; transform: translate3d(-166rpx, -560rpx, 0) scale(.54) rotate(26deg); } + } + + @keyframes pageHeartFloatFive { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.64) rotate(-10deg); } + 14% { opacity: .72; transform: translate3d(34rpx, -96rpx, 0) scale(.96) rotate(6deg); } + 62% { opacity: .44; transform: translate3d(68rpx, -380rpx, 0) scale(.78) rotate(-10deg); } + 100% { opacity: 0; transform: translate3d(104rpx, -650rpx, 0) scale(.58) rotate(-20deg); } + } + + @keyframes pageHeartFloatSix { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.52) rotate(8deg); } + 20% { opacity: .64; transform: translate3d(6rpx, -132rpx, 0) scale(.8) rotate(-8deg); } + 68% { opacity: .36; transform: translate3d(44rpx, -290rpx, 0) scale(.64) rotate(12deg); } + 100% { opacity: 0; transform: translate3d(70rpx, -500rpx, 0) scale(.48) rotate(22deg); } + } + + @keyframes pageHeartFloatSeven { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.62) rotate(-8deg); } + 13% { opacity: .62; transform: translate3d(-28rpx, -82rpx, 0) scale(.88) rotate(6deg); } + 58% { opacity: .38; transform: translate3d(-86rpx, -390rpx, 0) scale(.72) rotate(-12deg); } + 100% { opacity: 0; transform: translate3d(-132rpx, -680rpx, 0) scale(.52) rotate(-22deg); } + } + + @keyframes pageHeartFloatEight { + 0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.56) rotate(10deg); } + 18% { opacity: .66; transform: translate3d(38rpx, -118rpx, 0) scale(.84) rotate(-8deg); } + 64% { opacity: .4; transform: translate3d(122rpx, -338rpx, 0) scale(.68) rotate(14deg); } + 100% { opacity: 0; transform: translate3d(178rpx, -580rpx, 0) scale(.5) rotate(26deg); } + } + .empty-state { padding: 100rpx 0; color: #77827d; diff --git a/components/tab-bar/tab-bar.vue b/components/tab-bar/tab-bar.vue index 0372803..8159510 100644 --- a/components/tab-bar/tab-bar.vue +++ b/components/tab-bar/tab-bar.vue @@ -7,6 +7,9 @@ {{formatBadgeCount(indexWorkerCount)}} + + {{formatBadgeCount(indexFishCount)}} + {{formatBadgeCount(indexMyCount)}} @@ -60,6 +63,7 @@ }], indexZhipaiCount:0, indexWorkerCount:0, + indexFishCount:0, indexMyCount:0, countRefreshId:0 } @@ -90,10 +94,12 @@ }, mounted() { uni.$on('deliveryStatusCountsChange', this.setDeliveryStatusCounts) + uni.$on('fishUnreadChange', this.setFishUnreadCounts) this.setDeliveryStatusCounts(uni.getStorageSync('deliveryStatusCounts') || {}) }, beforeDestroy() { uni.$off('deliveryStatusCountsChange', this.setDeliveryStatusCounts) + uni.$off('fishUnreadChange', this.setFishUnreadCounts) }, methods: { hasLogin() { @@ -115,6 +121,14 @@ this.indexWorkerCount = (Number(counts.daiqu) || 0) + (Number(counts.daisong) || 0) this.$forceUpdate() }, + setFishUnreadCounts(summary) { + if (summary && summary.fishUnread !== undefined) { + this.indexFishCount = Number(summary.fishUnread) || 0 + this.$forceUpdate() + return + } + this.getWorkerCounts() + }, //点击tab切换 clickBar(index) { if (index !== 0 && !this.hasLogin()) { @@ -145,7 +159,11 @@ const refreshId = ++this.countRefreshId this.indexZhipaiCount = 0 this.indexWorkerCount = 0 + this.indexFishCount = 0 this.indexMyCount = 0 + if (!this.hasLogin()) { + return Promise.resolve() + } let deliveryStatusCounts = { daiqu: 0, daisong: 0 @@ -161,6 +179,12 @@ } let shopCounts = 0 let tasks = [] + tasks.push(this.tui.request("/app/fishmarket/unread/summary", "GET", {}, false, true).then((res) => { + if (res && res.success !== false) { + const result = res.result || res + this.indexFishCount = Number(result.fishUnread) || 0 + } + }).catch(() => {})) if(worker){ const area = uni.getStorageSync('area') ? JSON.parse(uni.getStorageSync('area')) : {} tasks.push(this.tui.request("/mall/delivery/countOrderByStatus", "POST", {workerId:worker.workerId,regionId:area.id}, false, true).then((res) => {