5 changed files with 1194 additions and 40 deletions
@ -0,0 +1,819 @@ |
|||
<template> |
|||
<view class="campus-page"> |
|||
<view class="header" :style="{ paddingTop: statusBarHeight + 'px' }"> |
|||
<view class="brand-line"> |
|||
<text class="brand">校园热榜</text> |
|||
<text class="eating">热议中</text> |
|||
</view> |
|||
<view class="subtitle">每天都有新鲜事,全校都在聊</view> |
|||
<view class="search-line"> |
|||
<view class="search-box"> |
|||
<uni-icons type="search" size="19" color="#8a9692"></uni-icons> |
|||
<input class="search-input" v-model.trim="keyword" placeholder="搜索校园热议、关键词" placeholder-class="placeholder" confirm-type="search" @confirm="search" /> |
|||
<view class="search-button" @tap="search">搜索</view> |
|||
</view> |
|||
<view class="release-fab" @tap="goPublish"> |
|||
<view class="fab-spark fab-spark--one"></view> |
|||
<view class="fab-spark fab-spark--two"></view> |
|||
<view class="fab-camera"> |
|||
<view class="fab-camera-lens"></view> |
|||
</view> |
|||
<text style="margin-top: 6rpx;">发布</text> |
|||
</view> |
|||
</view> |
|||
<view class="category-row"> |
|||
<view |
|||
class="category-card" |
|||
:class="{ active: activeCategory === item.tone }" |
|||
v-for="item in channels" |
|||
:key="item.tone" |
|||
@tap="selectCategory(item.tone)" |
|||
> |
|||
<view class="hot-category-icon"> |
|||
<image class="hot-category-img" :src="item.icon" mode="aspectFit"></image> |
|||
</view> |
|||
<view class="category-badge" v-if="item.tone === 'mine' && myCircleUnread > 0">{{formatBadge(myCircleUnread)}}</view> |
|||
<view class="hot-category-name">{{item.name}}</view> |
|||
<view class="hot-category-desc">{{item.desc}}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<scroll-view class="page-scroll" scroll-y :style="{ paddingTop: headerHeight + 'px' }" :lower-threshold="120" @scrolltolower="loadMore"> |
|||
<view class="sub-category-scroll" v-if="activeCategory !== 'mine'"> |
|||
<view |
|||
class="sub-category" |
|||
:class="{ active: !activeCategoryId && postSortType !== 'latest' }" |
|||
@tap="selectSecondCategory('')" |
|||
>全部</view> |
|||
<view |
|||
class="sub-category" |
|||
:class="{ active: postSortType === 'latest' }" |
|||
@tap="selectLatest" |
|||
>最新</view> |
|||
<view |
|||
class="sub-category" |
|||
:class="{ active: postSortType !== 'latest' && activeCategoryId === item.id }" |
|||
v-for="item in secondCategoryOptions" |
|||
:key="item.id" |
|||
@tap="selectSecondCategory(item.id)" |
|||
>{{item.name}}</view> |
|||
</view> |
|||
|
|||
<view class="filter-panel" v-if="activeCategory !== 'wall' && activeCategory !== 'mine'"> |
|||
<view class="filter-head"> |
|||
<text>随机找搭子</text> |
|||
<view class="match-btn" :class="{ matching }" @tap="startMatch">{{matching ? '匹配中...' : '发起匹配'}}</view> |
|||
</view> |
|||
<view class="match-animation" v-if="matching"> |
|||
<view></view><view></view><view></view> |
|||
</view> |
|||
<scroll-view scroll-x class="tag-scroll" v-if="tagOptions.length"> |
|||
<view |
|||
class="tag" |
|||
:class="{ active: selectedTag === item.name }" |
|||
v-for="item in tagOptions" |
|||
:key="item.key" |
|||
@tap="toggleTag(item.name)" |
|||
>{{item.name}}</view> |
|||
</scroll-view> |
|||
</view> |
|||
|
|||
<view class="section-head"> |
|||
<view> |
|||
<view class="section-title">{{sectionTitle}}</view> |
|||
<view class="section-sub">{{sectionSubTitle}}</view> |
|||
</view> |
|||
<view class="live-dot"><text></text>{{postSortType === 'latest' ? '最新优先' : (activeCategory === 'wall' ? '实时更新' : '热度优先')}}</view> |
|||
</view> |
|||
|
|||
<view class="post-list"> |
|||
<view |
|||
class="post-card" |
|||
:class="{ 'post-card--image': getImages(item).length }" |
|||
v-for="item in posts" |
|||
:key="item.id" |
|||
@tap="goDetail(item)" |
|||
> |
|||
<view class="post-top"> |
|||
<image class="avatar" :src="displayAvatar(item)" mode="aspectFill"></image> |
|||
<view class="author-main"> |
|||
<view class="author">{{displayName(item)}}</view> |
|||
<view class="time">{{item.createTime || ''}} · {{item.categoryName || '本校'}}</view> |
|||
</view> |
|||
<view class="partner-action" v-if="canUsePartnerChat(item)" @tap.stop="goChat(item)">搭一下</view> |
|||
</view> |
|||
<view class="title">{{item.title}}</view> |
|||
<view class="content">{{item.contentText || stripHtml(item.content)}}</view> |
|||
<view v-if="getImages(item).length" class="image-grid" :class="'image-grid--' + Math.min(getImages(item).length, 3)"> |
|||
<image |
|||
v-for="(pic, index) in getImages(item).slice(0, 3)" |
|||
:key="pic" |
|||
:src="pic" |
|||
:mode="getImages(item).length === 1 ? 'aspectFit' : 'aspectFill'" |
|||
:style="getPostImageStyle(pic, getImages(item).length)" |
|||
@load="onPostImageLoad(pic, $event)" |
|||
@tap.stop="previewImages(item, index)" |
|||
></image> |
|||
</view> |
|||
<view class="tag-row" v-if="getTags(item).length"> |
|||
<text v-for="tag in getTags(item)" :key="tag">{{tag}}</text> |
|||
</view> |
|||
<view class="partner-info" v-if="item.postType === 'partner'"> |
|||
<text>{{item.location || '地点待定'}}</text> |
|||
<text>{{item.activityTime || '时间可商量'}}</text> |
|||
<text>{{item.joinedCount || 0}}/{{item.targetCount || '不限'}} 人联系</text> |
|||
</view> |
|||
<view class="contact-row" v-if="item.contactWechat || item.contactPhone"> |
|||
<text v-if="item.contactWechat">微信 {{item.contactWechat}}</text> |
|||
<text v-if="item.contactPhone">手机 {{item.contactPhone}}</text> |
|||
</view> |
|||
<view class="post-footer"> |
|||
<view class="meta">{{item.commentCount || 0}} 评论 · 热度 {{item.hotScore || 0}}</view> |
|||
<view class="reaction-row"> |
|||
<view |
|||
class="reaction" |
|||
:class="{ 'reaction--active': reactionEffects[item.id + ':' + emoji] }" |
|||
v-for="emoji in reactions" |
|||
:key="emoji" |
|||
@tap.stop="react(item, emoji)" |
|||
> |
|||
<view class="reaction-emoji">{{emoji}}</view> |
|||
<text class="reaction-count">{{getReactionCount(item, emoji)}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="empty" v-if="!loading && !posts.length">暂无内容,来发布第一条吧</view> |
|||
<view class="load-state">{{loading ? '加载中...' : (hasMore ? '上滑加载更多' : '已经到底了')}}</view> |
|||
<view class="bottom-space"></view> |
|||
</scroll-view> |
|||
|
|||
<view class="page-reaction-effect" v-if="reactionBurstEffects.length"> |
|||
<view |
|||
class="page-reaction-float" |
|||
:class="effect.className" |
|||
:style="effect.style" |
|||
v-for="effect in reactionBurstEffects" |
|||
:key="effect.id" |
|||
>{{effect.emoji}}</view> |
|||
</view> |
|||
<view class="match-popup-mask" v-if="showMatchPanel" @tap="closeMatchPanel"> |
|||
<view class="match-popup" @tap.stop> |
|||
<view class="match-popup-head"> |
|||
<view> |
|||
<view class="match-popup-title">模糊匹配找搭子</view> |
|||
<view class="match-popup-sub">按标题关键词和二级分类,从当前搭子列表里挑一个最合适的。</view> |
|||
</view> |
|||
<view class="match-close" @tap="closeMatchPanel">×</view> |
|||
</view> |
|||
<view class="match-field-title">想找什么搭子</view> |
|||
<input class="match-input" v-model.trim="matchKeyword" maxlength="30" placeholder="例如:王者、夜跑、饭搭、自习" /> |
|||
<view class="match-field-title">选择二级分类</view> |
|||
<view class="match-tag-wrap"> |
|||
<view class="match-tag" :class="{ active: !matchCategoryId }" @tap="setMatchCategory('')">不限</view> |
|||
<view |
|||
class="match-tag" |
|||
:class="{ active: matchCategoryId === item.id }" |
|||
v-for="item in secondCategoryOptions" |
|||
:key="item.key" |
|||
@tap="setMatchCategory(item.id)" |
|||
>{{item.name}}</view> |
|||
</view> |
|||
<view class="match-actions"> |
|||
<view class="match-cancel" @tap="closeMatchPanel">取消</view> |
|||
<view class="match-start" @tap="confirmMatch">开始匹配</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="creating-mask" v-if="matching"> |
|||
<view class="creating-card"> |
|||
<view class="creating-orbit"> |
|||
<view class="creating-dot dot-a"></view> |
|||
<view class="creating-dot dot-b"></view> |
|||
<view class="creating-dot dot-c"></view> |
|||
<view class="creating-core">搭</view> |
|||
</view> |
|||
<view class="creating-title">正在漂流匹配</view> |
|||
<view class="creating-desc">根据关键词和标签寻找最合适的搭子...</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import { |
|||
listCampusCategories, |
|||
pageCampusPosts, |
|||
pageCampusPartnerPosts, |
|||
matchCampusPartnerPost, |
|||
pageMyCampusPosts, |
|||
toggleCampusLike, |
|||
createCampusChatSession, |
|||
getCampusUnreadSummary |
|||
} from '@/common/campusApi.js' |
|||
import { getIeProfile } from '@/common/ieApi.js' |
|||
import { ensureIeVerifiedBeforeAction } from '@/common/ieRealNameAuth.js' |
|||
|
|||
const CHANNELS = [ |
|||
{ tone: 'wall', name: '校园墙', desc: '新鲜事', icon: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/f3b050e7f6d74bb9adca54de0bf3fc17.png' }, |
|||
{ tone: 'game', name: '游戏搭子', desc: '开黑组队', icon: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/f8d490dfec0a4d17b7c18d710502f6f2.png' }, |
|||
{ tone: 'daily', name: '日常搭子', desc: '吃饭自习', icon: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/e810837993204689bda2fc68d1d29448.png' }, |
|||
{ tone: 'sport', name: '运动搭子', desc: '约球夜跑', icon: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/01444d0812c544fe9cde7cbbb919b473.png' }, |
|||
{ tone: 'mine', name: '我的圈子', desc: '消息动态', icon: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/430e4ae880574845b7d028bd96abde02.jpg' } |
|||
] |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
statusBarHeight: 24, |
|||
channels: CHANNELS, |
|||
activeCategory: 'wall', |
|||
activeCategoryId: '', |
|||
keyword: '', |
|||
selectedTag: '', |
|||
postSortType: 'hot', |
|||
posts: [], |
|||
pageNum: 1, |
|||
pageSize: 10, |
|||
totalPages: 1, |
|||
loading: false, |
|||
inited: false, |
|||
matching: false, |
|||
showMatchPanel: false, |
|||
matchKeyword: '', |
|||
matchTag: '', |
|||
matchCategoryId: '', |
|||
myCircleUnread: 0, |
|||
categoryMap: {}, |
|||
tagMap: {}, |
|||
reactions: ['😂', '😭', '🤢', '👍'], |
|||
reactionLocks: {}, |
|||
reactionEffects: {}, |
|||
reactionBurstSeq: 0, |
|||
reactionBurstTimer: null, |
|||
reactionBurstEffects: [], |
|||
imageSizeMap: {}, |
|||
defaultAvatar: '/static/images/img/songshu.png' |
|||
} |
|||
}, |
|||
computed: { |
|||
headerHeight() { |
|||
return this.statusBarHeight + (uni.upx2px ? uni.upx2px(424) : 212) |
|||
}, |
|||
hasMore() { |
|||
return this.pageNum < this.totalPages |
|||
}, |
|||
tagOptions() { |
|||
return (this.tagMap[this.activeCategory] || []).map((item, index) => ({ |
|||
...item, |
|||
key: item.id || item.code || item.name || String(index) |
|||
})) |
|||
}, |
|||
secondCategoryOptions() { |
|||
return (this.categoryMap[this.activeCategory] || []).map((item, index) => ({ |
|||
...item, |
|||
key: item.id || item.code || item.name || String(index) |
|||
})) |
|||
}, |
|||
sectionTitle() { |
|||
if (this.activeCategory === 'mine') return '我的圈子' |
|||
const item = this.channels.find(row => row.tone === this.activeCategory) |
|||
return item ? item.name : '校园圈' |
|||
}, |
|||
sectionSubTitle() { |
|||
if (this.postSortType === 'latest') return '按发布时间倒序排列' |
|||
if (this.activeCategory === 'wall') return '按点赞和评论综合热度排序' |
|||
if (this.activeCategory === 'mine') return '我的发布、评论和聊天动态' |
|||
return '支持关键词和热门标签模糊匹配' |
|||
} |
|||
}, |
|||
mounted() { |
|||
if (uni.getMenuButtonBoundingClientRect) { |
|||
this.statusBarHeight = uni.getMenuButtonBoundingClientRect().top || this.statusBarHeight |
|||
} |
|||
uni.$on('campusUnreadChange', this.applyUnread) |
|||
uni.$on('campusPostPublished', this.refreshAfterCampusPublish) |
|||
}, |
|||
beforeDestroy() { |
|||
uni.$off('campusUnreadChange', this.applyUnread) |
|||
uni.$off('campusPostPublished', this.refreshAfterCampusPublish) |
|||
if (this.reactionBurstTimer) clearTimeout(this.reactionBurstTimer) |
|||
}, |
|||
methods: { |
|||
init() { |
|||
if (this.inited && this.loading) return |
|||
this.inited = true |
|||
this.loadUnread() |
|||
this.loadCategories().finally(() => { |
|||
this.reload() |
|||
}) |
|||
}, |
|||
refreshAfterCampusPublish() { |
|||
if (!this.inited) return |
|||
this.loadUnread() |
|||
this.reload() |
|||
}, |
|||
getArea() { |
|||
const area = uni.getStorageSync('area') |
|||
if (!area) return {} |
|||
if (typeof area === 'string') { |
|||
try { return JSON.parse(area) } catch (e) { return {} } |
|||
} |
|||
return area |
|||
}, |
|||
loadCategories() { |
|||
const area = this.getArea() |
|||
return Promise.all([ |
|||
listCampusCategories({ regionId: area.id || '', categoryType: 'category' }), |
|||
listCampusCategories({ regionId: area.id || '', categoryType: 'tag' }) |
|||
]).then(([categories, tags]) => { |
|||
const byParent = {} |
|||
;(Array.isArray(tags) ? tags : []).forEach(item => { |
|||
const key = item.parentCode |
|||
if (!byParent[key]) byParent[key] = [] |
|||
byParent[key].push(item) |
|||
}) |
|||
this.tagMap = byParent |
|||
const categoryList = Array.isArray(categories) ? categories : [] |
|||
const categoryByParent = {} |
|||
categoryList.forEach(item => { |
|||
const key = item.parentCode |
|||
if (!categoryByParent[key]) categoryByParent[key] = [] |
|||
categoryByParent[key].push(item) |
|||
}) |
|||
this.categoryMap = categoryByParent |
|||
this.channels = CHANNELS |
|||
}) |
|||
}, |
|||
loadUnread() { |
|||
getCampusUnreadSummary().then(summary => this.applyUnread(summary)).catch(() => {}) |
|||
}, |
|||
applyUnread(summary = {}) { |
|||
this.myCircleUnread = Number(summary.myCircleUnread || summary.campusUnread) || 0 |
|||
}, |
|||
selectCategory(tone) { |
|||
if (this.activeCategory === tone) return |
|||
this.activeCategory = tone |
|||
this.activeCategoryId = '' |
|||
this.selectedTag = '' |
|||
this.postSortType = 'hot' |
|||
this.reload() |
|||
}, |
|||
selectSecondCategory(id) { |
|||
if (this.activeCategoryId === id && this.postSortType !== 'latest') return |
|||
this.activeCategoryId = id |
|||
this.postSortType = 'hot' |
|||
this.reload() |
|||
}, |
|||
selectLatest() { |
|||
if (this.postSortType === 'latest') return |
|||
this.activeCategoryId = '' |
|||
this.postSortType = 'latest' |
|||
this.reload() |
|||
}, |
|||
search() { |
|||
this.reload() |
|||
}, |
|||
toggleTag(tag) { |
|||
this.selectedTag = this.selectedTag === tag ? '' : tag |
|||
this.reload() |
|||
}, |
|||
startMatch() { |
|||
if (this.matching) return |
|||
this.matchKeyword = this.keyword || '' |
|||
this.matchTag = this.selectedTag || '' |
|||
this.matchCategoryId = this.activeCategoryId || '' |
|||
this.showMatchPanel = true |
|||
}, |
|||
closeMatchPanel() { |
|||
if (this.matching) return |
|||
this.showMatchPanel = false |
|||
}, |
|||
setMatchTag(tag) { |
|||
this.matchTag = tag |
|||
}, |
|||
setMatchCategory(id) { |
|||
this.matchCategoryId = id || '' |
|||
}, |
|||
confirmMatch() { |
|||
if (this.matching) return |
|||
this.showMatchPanel = false |
|||
this.matching = true |
|||
const area = this.getArea() |
|||
const query = { |
|||
regionId: area.id || '', |
|||
partnerType: this.activeCategory, |
|||
categoryId: this.matchCategoryId, |
|||
keyword: this.matchKeyword, |
|||
tag: this.matchTag |
|||
} |
|||
const matchRequest = matchCampusPartnerPost(query).catch(() => null) |
|||
setTimeout(() => { |
|||
matchRequest.then(matched => { |
|||
this.matching = false |
|||
if (!matched) { |
|||
uni.showToast({ title: '暂时没有匹配到合适搭子', icon: 'none' }) |
|||
return |
|||
} |
|||
this.goDetail(matched) |
|||
}) |
|||
}, 1500) |
|||
}, |
|||
reload() { |
|||
this.pageNum = 1 |
|||
this.totalPages = 1 |
|||
this.posts = [] |
|||
this.loadPosts() |
|||
}, |
|||
loadMore() { |
|||
if (this.loading || !this.hasMore) return |
|||
this.pageNum += 1 |
|||
this.loadPosts() |
|||
}, |
|||
loadPosts() { |
|||
if (this.loading) return |
|||
this.loading = true |
|||
const area = this.getArea() |
|||
const query = { |
|||
pageNum: this.pageNum, |
|||
pageSize: this.pageSize, |
|||
regionId: area.id || '', |
|||
keyword: this.keyword, |
|||
categoryId: this.activeCategoryId, |
|||
tag: this.selectedTag, |
|||
sortType: this.postSortType |
|||
} |
|||
let request |
|||
if (this.activeCategory === 'mine') { |
|||
request = pageMyCampusPosts(query) |
|||
} else if (this.activeCategory === 'wall') { |
|||
request = pageCampusPosts({ ...query, postType: 'wall' }) |
|||
} else { |
|||
request = pageCampusPartnerPosts({ ...query, partnerType: this.activeCategory }) |
|||
} |
|||
request.then(page => { |
|||
const records = page && (page.records || page.content) ? (page.records || page.content) : [] |
|||
this.totalPages = Number(page && (page.pages || page.totalPages)) || 1 |
|||
this.posts = this.pageNum === 1 ? records : this.posts.concat(records) |
|||
}).finally(() => { |
|||
this.loading = false |
|||
}) |
|||
}, |
|||
getImages(item) { |
|||
if (!item) return [] |
|||
if (Array.isArray(item.imageList)) return item.imageList |
|||
if (Array.isArray(item.images)) return item.images |
|||
if (!item.images) return [] |
|||
try { return JSON.parse(item.images) || [] } catch (e) { return [] } |
|||
}, |
|||
onPostImageLoad(pic, e) { |
|||
if (!pic || !e || !e.detail) return |
|||
this.$set(this.imageSizeMap, this.getImageKey(pic), { |
|||
width: Number(e.detail.width) || 0, |
|||
height: Number(e.detail.height) || 0 |
|||
}) |
|||
}, |
|||
getImageKey(pic) { |
|||
const value = String(pic || '') |
|||
let hash = 0 |
|||
for (let i = 0; i < value.length; i++) { |
|||
hash = ((hash << 5) - hash) + value.charCodeAt(i) |
|||
hash |= 0 |
|||
} |
|||
return 'img_' + Math.abs(hash) |
|||
}, |
|||
getPostImageStyle(pic, count) { |
|||
if (count > 1) return '' |
|||
const maxWidth = 560 |
|||
const maxHeight = 360 |
|||
const size = this.imageSizeMap[this.getImageKey(pic)] |
|||
if (!size || !size.width || !size.height) { |
|||
return 'width:' + maxWidth + 'rpx;height:320rpx;max-width:' + maxWidth + 'rpx;max-height:' + maxHeight + 'rpx;' |
|||
} |
|||
const ratio = size.width / size.height |
|||
let width = maxWidth |
|||
let height = Math.round(width / ratio) |
|||
if (height > maxHeight) { |
|||
height = maxHeight |
|||
width = Math.round(height * ratio) |
|||
} |
|||
return 'width:' + width + 'rpx;height:' + height + 'rpx;max-width:' + maxWidth + 'rpx;max-height:' + maxHeight + 'rpx;' |
|||
}, |
|||
getTags(item) { |
|||
if (!item) return [] |
|||
if (Array.isArray(item.tagList)) return item.tagList |
|||
if (Array.isArray(item.tags)) return item.tags |
|||
if (!item.tags) return [] |
|||
try { return JSON.parse(item.tags) || [] } catch (e) { return [] } |
|||
}, |
|||
displayName(item) { |
|||
return item && item.userName ? item.userName : '半径同学' |
|||
}, |
|||
displayAvatar(item) { |
|||
return item && item.userAvatar ? item.userAvatar : this.defaultAvatar |
|||
}, |
|||
canUsePartnerChat(item) { |
|||
return !!(item && item.postType === 'partner' && item.partnerType !== 'game') |
|||
}, |
|||
getReactionCount(item, emoji) { |
|||
if (!item || !item.reactionCounts) return 0 |
|||
return Number(item.reactionCounts[emoji] || 0) |
|||
}, |
|||
stripHtml(html = '') { |
|||
return String(html).replace(/<[^>]+>/g, '') |
|||
}, |
|||
previewImages(item, index) { |
|||
const urls = this.getImages(item) |
|||
if (!urls.length) return |
|||
uni.previewImage({ urls, current: urls[index] || urls[0] }) |
|||
}, |
|||
goDetail(item) { |
|||
uni.navigateTo({ url: '/package1/post/postDetail?id=' + item.id + '&source=campus' }) |
|||
}, |
|||
goPublish() { |
|||
uni.navigateTo({ url: '/package1/campus/campusRelease?type=' + (this.activeCategory === 'wall' || this.activeCategory === 'mine' ? 'wall' : this.activeCategory) }) |
|||
}, |
|||
async goChat(item) { |
|||
if (!this.canUsePartnerChat(item)) return |
|||
const ok = await this.ensureVerified('联系校园圈搭子') |
|||
if (!ok) return |
|||
createCampusChatSession(item.id).then(session => { |
|||
if (!session || !session.sessionId) return |
|||
uni.navigateTo({ url: '/package1/campus/campusChat?sessionId=' + session.sessionId }) |
|||
}) |
|||
}, |
|||
async react(item, emoji) { |
|||
const key = item.id + ':' + emoji |
|||
const now = Date.now() |
|||
if (this.reactionLocks[key] && now - this.reactionLocks[key] < 1200) return |
|||
this.reactionLocks[key] = now |
|||
this.$set(this.reactionEffects, key, true) |
|||
this.playReactionBurst(emoji) |
|||
setTimeout(() => { |
|||
this.$delete(this.reactionEffects, key) |
|||
}, 680) |
|||
toggleCampusLike({ targetType: 'reaction', targetId: item.id, reaction: emoji }).then(res => { |
|||
const counts = item.reactionCounts || {} |
|||
if (res && res.reactionCount !== undefined && res.reactionCount !== null) { |
|||
this.$set(counts, emoji, Number(res.reactionCount) || 0) |
|||
this.$set(item, 'reactionCounts', counts) |
|||
} else if (res) { |
|||
const nextCount = Math.max(0, Number(counts[emoji] || 0) + (res.liked === false ? -1 : 1)) |
|||
this.$set(counts, emoji, nextCount) |
|||
this.$set(item, 'reactionCounts', counts) |
|||
} |
|||
if (res && res.liked !== false) { |
|||
item.likeCount = Number(item.likeCount || 0) + 1 |
|||
} |
|||
}) |
|||
}, |
|||
playReactionBurst(emoji) { |
|||
const burstId = Date.now() + '-' + (++this.reactionBurstSeq) |
|||
const tracks = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve'] |
|||
const emojis = [emoji, emoji, emoji, emoji, ...this.reactions] |
|||
this.reactionBurstEffects = tracks.map((name, index) => ({ |
|||
id: burstId + '-' + index, |
|||
emoji: emojis[index % emojis.length], |
|||
className: 'page-reaction-float--' + name, |
|||
style: `left:${4 + ((index * 47) % 92)}%; animation-delay:${index * 72}ms;` |
|||
})) |
|||
if (this.reactionBurstTimer) clearTimeout(this.reactionBurstTimer) |
|||
this.reactionBurstTimer = setTimeout(() => { |
|||
if (this.reactionBurstEffects.length && String(this.reactionBurstEffects[0].id).indexOf(burstId) === 0) { |
|||
this.reactionBurstEffects = [] |
|||
} |
|||
this.reactionBurstTimer = null |
|||
}, 3100) |
|||
}, |
|||
async ensureVerified(actionText) { |
|||
const profile = await getIeProfile().catch(() => null) |
|||
return ensureIeVerifiedBeforeAction({ |
|||
profile: profile || {}, |
|||
actionText, |
|||
authDialog: { badge: '校园圈', title: '实名认证', desc: actionText + '前需完成实名认证' }, |
|||
reload: getIeProfile |
|||
}) |
|||
}, |
|||
formatBadge(count) { |
|||
const value = Number(count) || 0 |
|||
return value > 99 ? '99+' : value |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.campus-page { height: 100vh; overflow: hidden; background: radial-gradient(circle at 90rpx 30rpx, rgba(198, 247, 231, .65), transparent 250rpx), linear-gradient(180deg, #f1fffb 0, #fff 720rpx, #f8fbf9 100%); position: relative; color: #161d1a; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; } |
|||
.page-scroll { width: 100%; height: 100%; box-sizing: border-box; } |
|||
.header { position: fixed; left: 0; right: 0; top: 0; z-index: 900; padding: 20rpx 28rpx 18rpx; background: linear-gradient(180deg, rgba(237, 255, 249, .99), rgba(249, 255, 252, .98)); box-shadow: 0 8rpx 24rpx rgba(33, 111, 87, .06); box-sizing: border-box; } |
|||
.brand-line { height: 58rpx; display: flex; align-items: center; } |
|||
.brand { color: #12362e; font-size: 39rpx; font-weight: 900; letter-spacing: -1rpx; } |
|||
.eating { margin-left: 13rpx; padding: 7rpx 17rpx; border-radius: 24rpx; background: #31c78a; color: #fff; font-size: 19rpx; font-weight: 800; } |
|||
.subtitle { margin-top: 2rpx; color: #69726f; font-size: 19rpx; letter-spacing: 1rpx; } |
|||
.search-line { display: flex; align-items: center; gap: 16rpx; margin-top: 20rpx; } |
|||
.search-box { position: relative; flex: 1; min-width: 0; height: 66rpx; display: flex; align-items: center; padding-left: 20rpx; border: 2rpx solid #57d4ac; border-radius: 36rpx; background: rgba(255,255,255,.92); box-shadow: 0 9rpx 22rpx rgba(35, 145, 111, .08); } |
|||
.search-input { flex: 1; height: 62rpx; margin-left: 9rpx; color: #26302c; font-size: 22rpx; line-height: 62rpx; } |
|||
.placeholder { color: #a0a6a4; } |
|||
.search-button { width: 94rpx; height: 56rpx; margin-right: 5rpx; border-radius: 30rpx; background: linear-gradient(135deg, #2bd398, #0db779); box-shadow: 0 7rpx 15rpx rgba(20, 182, 123, .24); color: #fff; font-size: 21rpx; font-weight: 800; line-height: 56rpx; text-align: center; } |
|||
.category-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14rpx; margin-top: 18rpx; padding: 10rpx 24rpx 20rpx; } |
|||
.category-card { position: relative; height: 170rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 24rpx; background: linear-gradient(145deg, #ffffff, #edf5ff); text-align: center; box-shadow: 0 10rpx 24rpx rgba(35, 74, 61, 0.075); border: 2rpx solid transparent; box-sizing: border-box; } |
|||
.category-card.active { border-color: rgba(34, 191, 134, 0.55); box-shadow: 0 14rpx 30rpx rgba(34, 191, 134, 0.14); transform: translateY(-3rpx); } |
|||
.category-badge { position: absolute; right: 8rpx; top: 8rpx; min-width: 32rpx; height: 32rpx; line-height: 32rpx; padding: 0 8rpx; border-radius: 999rpx; background: #ff5b5b; color: #fff; font-size: 19rpx; box-sizing: border-box; } |
|||
.hot-category-icon { width: 84rpx; height: 84rpx; display: flex; align-items: center; justify-content: center; margin-bottom: 4rpx; border-radius: 26rpx; background: rgba(255, 255, 255, 0.72); box-shadow: inset 0 2rpx 0 rgba(255, 255, 255, 0.92), 0 9rpx 18rpx rgba(33, 111, 87, 0.08); } |
|||
.hot-category-img { width: 88rpx; height: 88rpx; border-radius: 22rpx; overflow: hidden; } |
|||
.hot-category-name { color: #173f36; font-size: 21rpx; font-weight: 900; line-height: 30rpx; } |
|||
.hot-category-desc { margin-top: 5rpx; color: #7d8e88; font-size: 17rpx; font-weight: 700; line-height: 23rpx; } |
|||
.sub-category-scroll { min-height: 62rpx; display: flex; align-items: center; gap: 18rpx; overflow-x: auto; padding: 12rpx 24rpx 4rpx; box-sizing: border-box; } |
|||
.sub-category { display: inline-block; margin-right: 18rpx; padding: 12rpx 4rpx; color: #6c7d78; font-size: 25rpx; border-bottom: 5rpx solid transparent; } |
|||
.sub-category.active { color: #18b892; font-weight: 800; border-bottom-color: #18d0a4; } |
|||
.filter-panel { margin: 18rpx 24rpx 0; padding: 20rpx; background: #fff; border-radius: 28rpx; box-shadow: 0 10rpx 30rpx rgba(41, 80, 73, .08); } |
|||
.filter-head { display: flex; justify-content: space-between; align-items: center; font-weight: 700; color: #183a32; } |
|||
.match-btn { padding: 10rpx 18rpx; border-radius: 999rpx; background: #232f3d; color: #fff; font-size: 22rpx; } |
|||
.match-btn.matching { background: #34c7a2; } |
|||
.match-animation { display: flex; gap: 12rpx; justify-content: center; padding: 18rpx 0; } |
|||
.match-animation view { width: 18rpx; height: 18rpx; border-radius: 50%; background: #34c7a2; animation: pulse 1s infinite alternate; } |
|||
.match-animation view:nth-child(2) { animation-delay: .18s; } |
|||
.match-animation view:nth-child(3) { animation-delay: .36s; } |
|||
.match-popup-mask { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; background: rgba(14, 28, 25, .38); padding: 40rpx 28rpx 180rpx; box-sizing: border-box; } |
|||
.match-popup { width: 100%; max-height: 78vh; padding: 30rpx; border-radius: 34rpx; background: linear-gradient(180deg, #ffffff, #f3fffb); box-shadow: 0 22rpx 58rpx rgba(22, 69, 58, .18); box-sizing: border-box; overflow-y: auto; } |
|||
.match-popup-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 20rpx; } |
|||
.match-popup-title { color: #173a32; font-size: 32rpx; font-weight: 900; } |
|||
.match-popup-sub { margin-top: 8rpx; color: #788984; font-size: 23rpx; line-height: 1.45; } |
|||
.match-close { flex: 0 0 54rpx; width: 54rpx; height: 54rpx; line-height: 50rpx; text-align: center; border-radius: 50%; background: #eef6f3; color: #657772; font-size: 34rpx; } |
|||
.match-field-title { margin-top: 24rpx; color: #213d37; font-size: 25rpx; font-weight: 800; } |
|||
.match-input { margin-top: 12rpx; width: 100%; height: 76rpx; box-sizing: border-box; padding: 0 22rpx; border-radius: 22rpx; background: #f5fbf9; color: #263d38; font-size: 26rpx; border: 1rpx solid #e2efeb; } |
|||
.match-tag-wrap { margin-top: 14rpx; display: flex; flex-wrap: wrap; gap: 12rpx; max-height: 190rpx; overflow-y: auto; } |
|||
.match-tag { padding: 12rpx 20rpx; border-radius: 999rpx; background: #eef6f3; color: #526761; font-size: 23rpx; font-weight: 700; } |
|||
.match-tag.active { background: linear-gradient(135deg, #34d8ad, #25c6c5); color: #fff; box-shadow: 0 8rpx 18rpx rgba(38, 196, 169, .18); } |
|||
.match-actions { display: flex; gap: 18rpx; margin-top: 30rpx; } |
|||
.match-cancel, .match-start { flex: 1; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 999rpx; font-size: 27rpx; font-weight: 900; } |
|||
.match-cancel { background: #eef3f1; color: #526761; } |
|||
.match-start { background: linear-gradient(135deg, #34d8ad, #25c6c5); color: #fff; box-shadow: 0 12rpx 28rpx rgba(38, 196, 169, .26); } |
|||
.creating-mask { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; background: rgba(12, 24, 22, .48); backdrop-filter: blur(6rpx); } |
|||
.creating-card { width: 500rpx; padding: 46rpx 34rpx; border-radius: 36rpx; background: radial-gradient(circle at 50% 24%, rgba(64, 225, 176, .22), transparent 150rpx), #fff; text-align: center; box-shadow: 0 28rpx 70rpx rgba(12, 50, 42, .18); } |
|||
.creating-orbit { position: relative; width: 190rpx; height: 190rpx; margin: 0 auto 24rpx; border-radius: 50%; border: 2rpx solid rgba(44, 202, 161, .26); animation: matchOrbitRotate 1.5s linear infinite; } |
|||
.creating-core { position: absolute; left: 50%; top: 50%; width: 92rpx; height: 92rpx; margin-left: -46rpx; margin-top: -46rpx; border-radius: 50%; background: linear-gradient(135deg, #34d8ad, #25c6c5); color: #fff; font-size: 34rpx; font-weight: 900; line-height: 92rpx; box-shadow: 0 12rpx 28rpx rgba(38, 196, 169, .28); animation: matchCorePulse .75s ease-in-out infinite alternate; } |
|||
.creating-dot { position: absolute; border-radius: 50%; background: #41d9b2; box-shadow: 0 0 18rpx rgba(65, 217, 178, .48); } |
|||
.dot-a { left: 18rpx; top: 38rpx; width: 22rpx; height: 22rpx; } |
|||
.dot-b { right: 14rpx; top: 84rpx; width: 16rpx; height: 16rpx; background: #74e6ff; } |
|||
.dot-c { left: 86rpx; bottom: 10rpx; width: 18rpx; height: 18rpx; background: #ffd36f; } |
|||
.creating-title { color: #173a32; font-size: 31rpx; font-weight: 900; } |
|||
.creating-desc { margin-top: 10rpx; color: #71837d; font-size: 24rpx; } |
|||
.tag-scroll { white-space: nowrap; margin-top: 16rpx; } |
|||
.tag { display: inline-block; padding: 10rpx 20rpx; border-radius: 999rpx; margin-right: 12rpx; background: #f0f6f4; color: #51645f; font-size: 23rpx; } |
|||
.tag.active { background: #34c7a2; color: #fff; } |
|||
.section-head { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 28rpx 14rpx; } |
|||
.section-title { font-size: 34rpx; font-weight: 800; color: #1d352f; } |
|||
.section-sub { margin-top: 6rpx; font-size: 23rpx; color: #83918d; } |
|||
.live-dot { display: flex; align-items: center; gap: 8rpx; font-size: 22rpx; color: #5a716c; } |
|||
.live-dot text { width: 12rpx; height: 12rpx; border-radius: 50%; background: #34c7a2; } |
|||
.post-list { padding: 0 24rpx; } |
|||
.post-card { margin-bottom: 20rpx; padding: 24rpx; background: #fff; border-radius: 30rpx; box-shadow: 0 12rpx 34rpx rgba(38, 76, 69, .09); } |
|||
.post-card--image { background: linear-gradient(180deg, #fff 0%, #fbfffd 100%); } |
|||
.post-top { display: flex; align-items: center; } |
|||
.avatar { width: 62rpx; height: 62rpx; border-radius: 50%; background: #dff8f0; color: #257c69; text-align: center; line-height: 62rpx; font-weight: 700; } |
|||
.author-main { flex: 1; margin-left: 14rpx; min-width: 0; } |
|||
.author { font-size: 26rpx; font-weight: 700; color: #263d38; } |
|||
.time { margin-top: 4rpx; font-size: 21rpx; color: #93a09c; } |
|||
.partner-action { padding: 10rpx 18rpx; border-radius: 999rpx; background: #34c7a2; color: #fff; font-size: 23rpx; } |
|||
.title { margin-top: 20rpx; font-size: 31rpx; font-weight: 800; color: #162d28; } |
|||
.content { margin-top: 12rpx; font-size: 26rpx; color: #51645f; line-height: 1.6; } |
|||
.image-grid { display: grid; gap: 8rpx; margin-top: 18rpx; } |
|||
.image-grid--1 { display: flex; align-items: center; justify-content: flex-start; max-width: 560rpx; max-height: 360rpx; overflow: hidden; border-radius: 20rpx; background: transparent; } |
|||
.image-grid--2 { grid-template-columns: repeat(2, 1fr); } |
|||
.image-grid--3 { grid-template-columns: repeat(3, 1fr); } |
|||
.image-grid image { width: 100%; height: 190rpx; max-width: 100%; border-radius: 20rpx; background: #eef5f3; } |
|||
.image-grid--1 image { max-width: 560rpx; max-height: 360rpx; background: transparent; } |
|||
.tag-row { margin-top: 16rpx; display: flex; flex-wrap: wrap; gap: 10rpx; } |
|||
.tag-row text { padding: 8rpx 16rpx; border-radius: 999rpx; background: #eefaf6; color: #2f9a81; font-size: 22rpx; } |
|||
.partner-info { margin-top: 14rpx; display: flex; flex-wrap: wrap; gap: 12rpx; color: #71807c; font-size: 22rpx; } |
|||
.contact-row { margin-top: 14rpx; display: flex; flex-wrap: wrap; gap: 10rpx; } |
|||
.contact-row text { padding: 8rpx 14rpx; border-radius: 999rpx; background: #f0fbf7; color: #2b8d76; font-size: 22rpx; } |
|||
.post-footer { margin-top: 18rpx; display: flex; justify-content: space-between; align-items: flex-end; gap: 12rpx; } |
|||
.meta { color: #889692; font-size: 22rpx; } |
|||
.reaction-row { display: flex; gap: 14rpx; overflow: visible; } |
|||
.reaction { position: relative; z-index: 1; width: 52rpx; min-height: 62rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border-radius: 18rpx; background: transparent; transform-origin: center center; transition: background .16s ease, box-shadow .16s ease; overflow: visible; } |
|||
.reaction-emoji { font-size: 31rpx; line-height: 34rpx; } |
|||
.reaction-count { margin-top: 2rpx; color: #667671; font-size: 18rpx; line-height: 20rpx; font-weight: 700; } |
|||
.reaction--active { z-index: 5; background: #fff9dc; box-shadow: 0 12rpx 28rpx rgba(255, 186, 61, .30), 0 0 0 8rpx rgba(255, 207, 88, .16); animation: reactionBoom .68s cubic-bezier(.18, .88, .24, 1.22); } |
|||
.reaction--active::after { content: ''; position: absolute; left: 50%; top: 50%; width: 56rpx; height: 56rpx; margin-left: -28rpx; margin-top: -28rpx; border-radius: 50%; border: 4rpx solid rgba(255, 199, 63, .42); animation: reactionRing .68s ease-out; pointer-events: none; } |
|||
.empty, .load-state { text-align: center; color: #8a9692; font-size: 24rpx; padding: 30rpx 0; } |
|||
.bottom-space { height: 160rpx; } |
|||
.page-reaction-effect { position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; pointer-events: none; z-index: 99999; } |
|||
.page-reaction-float { position: absolute; bottom: -160rpx; width: 86rpx; height: 86rpx; line-height: 86rpx; text-align: center; opacity: 0; transform-origin: center; font-size: 48rpx; filter: drop-shadow(0 18rpx 28rpx rgba(35, 74, 61, .18)); animation-duration: 2600ms; animation-timing-function: cubic-bezier(.14, .66, .22, 1); animation-fill-mode: forwards; } |
|||
.page-reaction-float--one { font-size: 66rpx; animation-name: reactionFloatOne; } |
|||
.page-reaction-float--two { font-size: 50rpx; animation-name: reactionFloatTwo; } |
|||
.page-reaction-float--three { font-size: 74rpx; animation-name: reactionFloatThree; } |
|||
.page-reaction-float--four { font-size: 44rpx; animation-name: reactionFloatFour; } |
|||
.page-reaction-float--five { font-size: 58rpx; animation-name: reactionFloatFive; } |
|||
.page-reaction-float--six { font-size: 42rpx; animation-name: reactionFloatSix; } |
|||
.page-reaction-float--seven { font-size: 68rpx; animation-name: reactionFloatSeven; } |
|||
.page-reaction-float--eight { font-size: 48rpx; animation-name: reactionFloatEight; } |
|||
.page-reaction-float--nine { font-size: 56rpx; animation-name: reactionFloatNine; } |
|||
.page-reaction-float--ten { font-size: 46rpx; animation-name: reactionFloatTen; } |
|||
.page-reaction-float--eleven { font-size: 62rpx; animation-name: reactionFloatEleven; } |
|||
.page-reaction-float--twelve { font-size: 40rpx; animation-name: reactionFloatTwelve; } |
|||
.release-fab { position: relative; flex: 0 0 82rpx; width: 82rpx; height: 82rpx; border-radius: 50%; background: radial-gradient(circle at 35% 28%, #8cf7a9 0%, #49dc7b 42%, #24ba5b 100%); color: #fff; box-shadow: 0 0 0 8rpx rgba(119, 232, 146, .18), 0 0 22rpx 8rpx rgba(80, 225, 118, .26), 0 10rpx 18rpx rgba(18, 151, 76, .20); display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 3; animation: fabFloat 3s ease-in-out infinite; overflow: visible; border: 3rpx solid rgba(239, 255, 241, .78); } |
|||
.release-fab::before { content: ''; position: absolute; inset: -13rpx; border-radius: 50%; background: radial-gradient(circle, rgba(148, 255, 167, .34) 0%, rgba(115, 238, 144, .15) 46%, rgba(115, 238, 144, 0) 72%); animation: fabGlow 2s ease-in-out infinite; z-index: -1; } |
|||
.release-fab::after { content: ''; position: absolute; inset: 8rpx; border-radius: 50%; border: 2rpx solid rgba(255,255,255,.42); box-shadow: inset 0 6rpx 12rpx rgba(255,255,255,.18); pointer-events: none; } |
|||
.fab-camera { position: relative; width: 30rpx; height: 22rpx; border-radius: 7rpx; background: #167c3f; box-shadow: inset 0 -3rpx 6rpx rgba(0,0,0,.10); } |
|||
.fab-camera::before { content: ''; position: absolute; left: 9rpx; top: -6rpx; width: 13rpx; height: 8rpx; border-radius: 6rpx 6rpx 0 0; background: #167c3f; } |
|||
.fab-camera::after { content: ''; position: absolute; right: 4rpx; top: 5rpx; width: 5rpx; height: 5rpx; border-radius: 50%; background: rgba(255,255,255,.75); } |
|||
.fab-camera-lens { position: absolute; left: 9rpx; top: 6rpx; width: 12rpx; height: 12rpx; border-radius: 50%; background: #78ee99; box-shadow: 0 0 0 3rpx rgba(255,255,255,.24); } |
|||
.release-fab text { position: relative; margin-top: 3rpx; font-size: 18rpx; line-height: 22rpx; font-weight: 800; text-shadow: 0 2rpx 6rpx rgba(0, 101, 49, .22); } |
|||
.fab-spark { position: absolute; background: #96f4a6; transform: rotate(45deg); animation: sparkBlink 1.8s ease-in-out infinite; } |
|||
.fab-spark--one { right: -6rpx; top: -12rpx; width: 18rpx; height: 18rpx; } |
|||
.fab-spark--two { right: 22rpx; top: -30rpx; width: 10rpx; height: 10rpx; animation-delay: .45s; } |
|||
@keyframes pulse { from { opacity: .35; transform: scale(.8); } to { opacity: 1; transform: scale(1.15); } } |
|||
@keyframes matchOrbitRotate { to { transform: rotate(360deg); } } |
|||
@keyframes matchCorePulse { from { transform: scale(.92); } to { transform: scale(1.08); } } |
|||
@keyframes fabGlow { 0%, 100% { opacity: .55; transform: scale(.94); } 50% { opacity: 1; transform: scale(1.08); } } |
|||
@keyframes fabFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6rpx); } } |
|||
@keyframes sparkBlink { 0%, 100% { opacity: .45; transform: rotate(45deg) scale(.85); } 50% { opacity: 1; transform: rotate(45deg) scale(1.16); } } |
|||
@keyframes reactionBoom { |
|||
0% { transform: translateY(0) scale(1) rotate(0); } |
|||
22% { transform: translateY(-18rpx) scale(2.25) rotate(-8deg); } |
|||
48% { transform: translateY(-8rpx) scale(1.55) rotate(6deg); } |
|||
68% { transform: translateY(-12rpx) scale(1.82) rotate(-3deg); } |
|||
100% { transform: translateY(0) scale(1) rotate(0); } |
|||
} |
|||
@keyframes reactionRing { |
|||
0% { opacity: .95; transform: scale(.7); } |
|||
70% { opacity: .35; transform: scale(1.75); } |
|||
100% { opacity: 0; transform: scale(2.1); } |
|||
} |
|||
@keyframes reactionFloatOne { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.68) rotate(-8deg); } |
|||
14% { opacity: .92; transform: translate3d(-18rpx, -92rpx, 0) scale(1) rotate(5deg); } |
|||
62% { opacity: .56; transform: translate3d(-72rpx, -370rpx, 0) scale(.94) rotate(-12deg); } |
|||
100% { opacity: 0; transform: translate3d(-112rpx, -650rpx, 0) scale(.72) rotate(-22deg); } |
|||
} |
|||
@keyframes reactionFloatTwo { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.62) rotate(6deg); } |
|||
16% { opacity: .84; transform: translate3d(28rpx, -112rpx, 0) scale(.92) rotate(-6deg); } |
|||
64% { opacity: .48; transform: translate3d(96rpx, -350rpx, 0) scale(.82) rotate(12deg); } |
|||
100% { opacity: 0; transform: translate3d(146rpx, -600rpx, 0) scale(.62) rotate(22deg); } |
|||
} |
|||
@keyframes reactionFloatThree { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.64) rotate(-4deg); } |
|||
12% { opacity: .78; transform: translate3d(8rpx, -86rpx, 0) scale(.94) rotate(7deg); } |
|||
60% { opacity: .42; transform: translate3d(-36rpx, -420rpx, 0) scale(.78) rotate(-12deg); } |
|||
100% { opacity: 0; transform: translate3d(-68rpx, -710rpx, 0) scale(.58) rotate(-24deg); } |
|||
} |
|||
@keyframes reactionFloatFour { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.58) rotate(12deg); } |
|||
18% { opacity: .8; transform: translate3d(-26rpx, -124rpx, 0) scale(.86) rotate(-6deg); } |
|||
66% { opacity: .44; transform: translate3d(-112rpx, -320rpx, 0) scale(.72) rotate(14deg); } |
|||
100% { opacity: 0; transform: translate3d(-166rpx, -560rpx, 0) scale(.54) rotate(26deg); } |
|||
} |
|||
@keyframes reactionFloatFive { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.64) rotate(-10deg); } |
|||
14% { opacity: .86; transform: translate3d(34rpx, -96rpx, 0) scale(.96) rotate(6deg); } |
|||
62% { opacity: .48; transform: translate3d(68rpx, -380rpx, 0) scale(.78) rotate(-10deg); } |
|||
100% { opacity: 0; transform: translate3d(104rpx, -650rpx, 0) scale(.58) rotate(-20deg); } |
|||
} |
|||
@keyframes reactionFloatSix { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.52) rotate(8deg); } |
|||
20% { opacity: .76; transform: translate3d(6rpx, -132rpx, 0) scale(.8) rotate(-8deg); } |
|||
68% { opacity: .4; transform: translate3d(44rpx, -290rpx, 0) scale(.64) rotate(12deg); } |
|||
100% { opacity: 0; transform: translate3d(70rpx, -500rpx, 0) scale(.48) rotate(22deg); } |
|||
} |
|||
@keyframes reactionFloatSeven { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.62) rotate(-8deg); } |
|||
13% { opacity: .78; transform: translate3d(-28rpx, -82rpx, 0) scale(.88) rotate(6deg); } |
|||
58% { opacity: .42; transform: translate3d(-86rpx, -390rpx, 0) scale(.72) rotate(-12deg); } |
|||
100% { opacity: 0; transform: translate3d(-132rpx, -680rpx, 0) scale(.52) rotate(-22deg); } |
|||
} |
|||
@keyframes reactionFloatEight { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.56) rotate(10deg); } |
|||
18% { opacity: .8; transform: translate3d(38rpx, -118rpx, 0) scale(.84) rotate(-8deg); } |
|||
64% { opacity: .44; transform: translate3d(122rpx, -338rpx, 0) scale(.68) rotate(14deg); } |
|||
100% { opacity: 0; transform: translate3d(178rpx, -580rpx, 0) scale(.5) rotate(26deg); } |
|||
} |
|||
@keyframes reactionFloatNine { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.55) rotate(-14deg); } |
|||
16% { opacity: .82; transform: translate3d(-46rpx, -110rpx, 0) scale(.88) rotate(8deg); } |
|||
62% { opacity: .46; transform: translate3d(-150rpx, -360rpx, 0) scale(.68) rotate(-10deg); } |
|||
100% { opacity: 0; transform: translate3d(-210rpx, -620rpx, 0) scale(.5) rotate(-24deg); } |
|||
} |
|||
@keyframes reactionFloatTen { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.52) rotate(14deg); } |
|||
18% { opacity: .74; transform: translate3d(50rpx, -96rpx, 0) scale(.82) rotate(-8deg); } |
|||
64% { opacity: .38; transform: translate3d(170rpx, -300rpx, 0) scale(.62) rotate(12deg); } |
|||
100% { opacity: 0; transform: translate3d(230rpx, -540rpx, 0) scale(.46) rotate(24deg); } |
|||
} |
|||
@keyframes reactionFloatEleven { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.58) rotate(-6deg); } |
|||
14% { opacity: .82; transform: translate3d(4rpx, -136rpx, 0) scale(.9) rotate(6deg); } |
|||
62% { opacity: .42; transform: translate3d(-24rpx, -450rpx, 0) scale(.7) rotate(-10deg); } |
|||
100% { opacity: 0; transform: translate3d(-48rpx, -760rpx, 0) scale(.48) rotate(-20deg); } |
|||
} |
|||
@keyframes reactionFloatTwelve { |
|||
0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.48) rotate(8deg); } |
|||
18% { opacity: .72; transform: translate3d(22rpx, -150rpx, 0) scale(.72) rotate(-8deg); } |
|||
66% { opacity: .36; transform: translate3d(82rpx, -260rpx, 0) scale(.56) rotate(12deg); } |
|||
100% { opacity: 0; transform: translate3d(126rpx, -470rpx, 0) scale(.42) rotate(22deg); } |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue