You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

841 lines
21 KiB

<template>
<view class="planet" :style="{'--statusbar': statusBarHeight + 'px'}">
<view class="planet-bg">
<view class="bg-aura bg-aura-a"></view>
<view class="bg-aura bg-aura-b"></view>
<view class="bg-aura bg-aura-c"></view>
<view class="bg-space-dust bg-space-dust-a"></view>
<view class="bg-space-dust bg-space-dust-b"></view>
<view class="bg-shooting bg-shooting-a"></view>
<view class="bg-shooting bg-shooting-b"></view>
<view class="bg-orbit bg-orbit-a"></view>
<view class="bg-orbit bg-orbit-b"></view>
<view class="bg-star" v-for="(s, i) in stars" :key="i" :style="s"></view>
<view class="bg-planet bg-planet-a"></view>
<view class="bg-planet bg-planet-b"></view>
</view>
<view class="nav" :style="{paddingTop: statusBarHeight + 'px'}">
<view class="nav-back" @tap="goBack">
<text class="nav-back-icon"></text>
</view>
<view class="nav-title">白嫖星球</view>
</view>
<scroll-view scroll-y class="planet-scroll" :style="{paddingTop: (statusBarHeight + 44) + 'px'}">
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton">
<view class="sk-block sk-header"></view>
<view class="sk-block sk-me"></view>
<view class="sk-block sk-row"></view>
<view class="sk-block sk-list"></view>
</view>
<block v-else>
<planet-header
:data="home"
@ticketlog="goTicketLog"
@draw="goDrawResult">
</planet-header>
<planet-daily-loop
:data="home.dailyLoop"
:pool-amount="home.poolAmount"
@collect="onDailyCollect"
@revenge="onDailyRevenge"
@join="onJoinPool">
</planet-daily-loop>
<planet-news :list="home.newsList"></planet-news>
<planet-me
:data="home"
@sign="onSign"
@openbox="openBox">
</planet-me>
<planet-box
:available="home.boxAvailable"
:opening="boxOpening"
@open="openBox">
</planet-box>
<planet-operate
:data="home.operate"
@plant="onPlantTree"
@harvest="onHarvestTree"
@store="onStoreWarehouse"
@take="onTakeWarehouse"
@upgradeWarehouse="onUpgradeWarehouse"
@upgradeTower="onUpgradeTower"
@search="onRandomSearch"
@bid="onBidLandmark">
</planet-operate>
<planet-tasks
:tasks="home.tasks"
@claim="onClaimTask">
</planet-tasks>
<planet-rank
:list="home.rankList"
:remain-hunt="home.remainHunt"
@hunt="onHunt">
</planet-rank>
<buff-shop
:list="home.buffShop"
:my-ticket="home.myTicketCount"
@buy="onBuyBuff">
</buff-shop>
<view class="bottom-space"></view>
</block>
</scroll-view>
<!-- 追捕浮层 -->
<hunt-modal
:show="huntModal.show"
:phase="huntModal.phase"
:result="huntModal.result"
:target="huntModal.target"
@close="closeHunt">
</hunt-modal>
<view v-if="boxResult.show" class="box-result-mask" @tap="boxResult.show=false">
<view class="box-result" @tap.stop>
<view class="box-result-burst"></view>
<view class="box-result-icon">BLIND BOX</view>
<view class="box-result-title">{{boxResult.data.rewardName}}</view>
<view class="box-result-desc">{{boxResult.data.message}}</view>
<view class="box-result-btn" @tap="boxResult.show=false">收下了</view>
</view>
</view>
</view>
</template>
<script>
import planetHeader from '@/package1/components/planet/planet-header.vue'
import planetMe from '@/package1/components/planet/planet-me.vue'
import planetTasks from '@/package1/components/planet/planet-tasks.vue'
import planetBox from '@/package1/components/planet/planet-box.vue'
import planetRank from '@/package1/components/planet/planet-rank.vue'
import huntModal from '@/package1/components/planet/hunt-modal.vue'
import buffShop from '@/package1/components/planet/buff-shop.vue'
import planetNews from '@/package1/components/planet/planet-news.vue'
import planetOperate from '@/package1/components/planet/planet-operate.vue'
import planetDailyLoop from '@/package1/components/planet/planet-daily-loop.vue'
export default {
components: {
planetHeader,
planetMe,
planetTasks,
planetBox,
planetRank,
huntModal,
buffShop,
planetNews,
planetOperate,
planetDailyLoop
},
data() {
return {
statusBarHeight: 20,
loading: true,
userId: '',
regionId: '',
nickname: '',
avatar: '',
college: '',
home: {
poolAmount: 0,
joinCount: 0,
myTicketCount: 0,
myRankNo: 0,
remainHunt: 3,
tasks: [],
rankList: [],
buffShop: [],
newsList: [],
myBuffs: [],
operate: {},
dailyLoop: {}
},
boxOpening: false,
boxResult: {
show: false,
data: {}
},
huntModal: {
show: false,
phase: 'searching',
result: null,
target: {}
}
}
},
computed: {
stars() {
const arr = []
for (let n = 1; n <= 46; n++) {
const top = (n * 29) % 100
const left = (n * 47) % 100
const delay = (n % 12) / 2
const size = (n % 4) + 1
arr.push(`top:${top}%;left:${left}%;width:${size}px;height:${size}px;animation-delay:${delay}s;`)
}
return arr
}
},
onLoad() {
const sys = uni.getSystemInfoSync()
this.statusBarHeight = sys.statusBarHeight || 20
this.userId = uni.getStorageSync('id') || ''
this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || ''
this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || ''
try {
const area = uni.getStorageSync('area')
if (area) {
this.regionId = JSON.parse(area).id || ''
}
} catch (e) {}
this.loadHome()
},
methods: {
loadHome(silent) {
if (!this.userId) {
this.tui.toast('请先登录')
return
}
if (!this.regionId) {
this.loading = false
this.tui.toast('未获取到校区信息,白嫖星球仅限本区域内进行')
return
}
if (!silent) this.loading = true
this.tui.request('/app/planet/home', 'POST', {
userId: this.userId,
regionId: this.regionId,
nickname: this.nickname,
avatar: this.avatar,
college: this.college
}, false, false, true).then((res) => {
this.loading = false
if (res.code == 200 && res.result) {
this.home = res.result
} else if (res.message) {
this.tui.toast(res.message)
}
}).catch(() => {
this.loading = false
})
},
onSign() {
this.tui.request('/app/planet/sign', 'POST', {
userId: this.userId,
regionId: this.regionId
}).then((res) => {
this.tui.toast(res.message, 1500, res.code == 200)
if (res.code == 200) this.loadHome(true)
})
},
openBox() {
if (!this.home.boxAvailable || this.boxOpening) return
this.boxOpening = true
this.tui.request('/app/planet/box/open', 'POST', {
userId: this.userId,
regionId: this.regionId
}).then((res) => {
setTimeout(() => {
this.boxOpening = false
if (res.code == 200 && res.result) {
this.boxResult.data = res.result
this.boxResult.show = true
this.loadHome(true)
} else {
this.tui.toast(res.message)
}
}, 800)
}).catch(() => {
this.boxOpening = false
})
},
onClaimTask(task) {
if (task.code === 'sign') {
this.onSign()
return
}
this.tui.request('/app/planet/task/claim', 'POST', {
userId: this.userId,
regionId: this.regionId,
taskCode: task.code
}).then((res) => {
this.tui.toast(res.message, 1500, res.code == 200)
if (res.code == 200) this.loadHome(true)
})
},
onHunt(item) {
if (item.self) {
this.tui.toast('不能追捕自己')
return
}
if (item.shielded) {
this.tui.toast('目标已开启防护罩')
return
}
if (this.home.remainHunt <= 0) {
this.tui.toast('今日追捕次数已用完')
return
}
this.huntModal.show = true
this.huntModal.phase = 'searching'
this.huntModal.result = null
this.huntModal.target = item
// 动画阶段推进
setTimeout(() => { this.huntModal.phase = 'locking' }, 900)
setTimeout(() => { this.huntModal.phase = 'chasing' }, 1800)
this.tui.request('/app/planet/hunt', 'POST', {
userId: this.userId,
regionId: this.regionId,
toUserId: item.userId
}, false, false, true).then((res) => {
setTimeout(() => {
if (res.code == 200 && res.result) {
this.huntModal.result = res.result
this.huntModal.phase = 'result'
this.loadHome(true)
} else {
this.huntModal.show = false
this.tui.toast(res.message)
}
}, 2600)
}).catch(() => {
this.huntModal.show = false
})
},
closeHunt() {
this.huntModal.show = false
this.huntModal.result = null
},
onBuyBuff(buff) {
if (buff.active) {
this.tui.toast('该增益正在生效中')
return
}
if (this.home.myTicketCount < buff.costTickets) {
this.tui.toast('星球券不足')
return
}
this.tui.modal('确认购买', `消耗 ${buff.costTickets} 张星球券购买「${buff.name}」?`, true, (ok) => {
if (!ok) return
this.tui.request('/app/planet/buff/buy', 'POST', {
userId: this.userId,
regionId: this.regionId,
buffId: buff.id
}).then((res) => {
this.tui.toast(res.code == 200 ? '购买成功' : res.message, 1500, res.code == 200)
if (res.code == 200) this.loadHome(true)
})
})
},
promptTickets(title, callback) {
uni.showModal({
title,
editable: true,
placeholderText: '输入星球券数量',
success: (res) => {
if (!res.confirm) return
const tickets = parseInt(res.content || '0', 10)
if (!tickets || tickets <= 0) {
this.tui.toast('请输入正确数量')
return
}
callback(tickets)
}
})
},
operateRequest(url, data, successText) {
this.tui.request(url, 'POST', Object.assign({
userId: this.userId,
regionId: this.regionId
}, data || {})).then((res) => {
this.tui.toast(res.code == 200 ? successText : res.message, 1500, res.code == 200)
if (res.code == 200) this.loadHome(true)
})
},
onPlantTree(config) {
this.promptTickets(`投入 ${config.cycleHours}h 券树`, (tickets) => {
this.operateRequest('/app/planet/tree/plant', { configId: config.id, tickets }, '种植成功')
})
},
onHarvestTree(order) {
if (order.status === 2) {
this.tui.toast('这棵券树已经收获')
return
}
this.operateRequest('/app/planet/tree/harvest', { orderId: order.id }, '收获成功')
},
onStoreWarehouse() {
this.promptTickets('存入松鼠仓库', (tickets) => {
this.operateRequest('/app/planet/warehouse/store', { tickets }, '存入成功')
})
},
onTakeWarehouse() {
this.promptTickets('从仓库取出', (tickets) => {
this.operateRequest('/app/planet/warehouse/take', { tickets }, '取出成功')
})
},
onUpgradeWarehouse() {
this.tui.modal('升级仓库', '确认消耗星球券升级松鼠仓库?', true, (ok) => {
if (ok) this.operateRequest('/app/planet/warehouse/upgrade', {}, '升级成功')
})
},
onUpgradeTower() {
this.tui.modal('升级防御塔', '确认消耗星球券提升拦截能力?', true, (ok) => {
if (ok) this.operateRequest('/app/planet/tower/upgrade', {}, '升级成功')
})
},
onRandomSearch() {
this.startSearch('/app/planet/search/random', {}, { nickname: '随机同校区玩家' })
},
onDailyCollect() {
if (!this.home.dailyLoop || !this.home.dailyLoop.signedToday) {
this.onSign()
return
}
if (this.home.dailyLoop.boxAvailable) {
this.openBox()
return
}
this.tui.toast('今天的顺手券已经收完')
},
onDailyRevenge() {
const loop = this.home.dailyLoop || {}
if (loop.hasRevengeTarget && loop.revengeTarget && loop.revengeTarget.fromUserId) {
this.startSearch('/app/planet/search/target', { toUserId: loop.revengeTarget.fromUserId }, loop.revengeTarget)
} else {
this.onRandomSearch()
}
},
startSearch(url, data, target) {
this.huntModal.show = true
this.huntModal.phase = 'searching'
this.huntModal.result = null
this.huntModal.target = target || { nickname: '同校区玩家' }
setTimeout(() => { this.huntModal.phase = 'locking' }, 900)
setTimeout(() => { this.huntModal.phase = 'chasing' }, 1800)
this.tui.request(url, 'POST', Object.assign({
userId: this.userId,
regionId: this.regionId
}, data || {}), false, false, true).then((res) => {
setTimeout(() => {
if (res.code == 200 && res.result) {
this.huntModal.result = {
result: res.result.intercepted ? 'shield' : 'success',
message: res.result.message,
gainTickets: res.result.gainTickets,
totalGain: res.result.gainTickets,
remainHunt: res.result.remainSearchCount
}
this.huntModal.phase = 'result'
this.loadHome(true)
} else {
this.huntModal.show = false
this.tui.toast(res.message)
}
}, 2200)
}).catch(() => {
this.huntModal.show = false
})
},
onJoinPool() {
const loop = this.home.dailyLoop || {}
const n = loop.suggestedAddTickets || 1
this.tui.modal('投入现金奖池', `建议投入 ${n} 张星球券,预计概率提升到 ${loop.suggestedProbability || 0}%。投入后星球券会被回收,确认上车?`, true, (ok) => {
if (!ok) return
this.operateRequest('/app/planet/pool/join', { tickets: n }, '投入成功')
}, '#22B889', '立即上车')
},
onBidLandmark(item) {
this.promptTickets(`争夺${item.name}`, (tickets) => {
this.operateRequest('/app/planet/landmark/bid', { landmarkId: item.id, tickets }, '投入成功')
})
},
goDrawResult() {
uni.navigateTo({
url: '/package1/planet/drawResult'
})
},
goTicketLog() {
uni.navigateTo({
url: '/package1/planet/ticketLog'
})
},
goBack() {
uni.navigateBack({
delta: 1,
fail() {
uni.switchTab({ url: '/pages/index/index' })
}
})
}
}
}
</script>
<style lang="scss" scoped>
.planet {
min-height: 100vh;
background:
radial-gradient(circle at 50% 260rpx, rgba(255,255,255,0.72), rgba(255,255,255,0) 360rpx),
linear-gradient(155deg, #F3FFF4 0%, #EAF8FF 42%, #F7EEFF 76%, #FFF8DE 100%);
position: relative;
overflow: hidden;
color: #12342F;
}
.planet-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.bg-aura {
position: absolute;
border-radius: 50%;
filter: blur(12rpx);
opacity: 0.72;
animation: auraFloat 8s ease-in-out infinite;
}
.bg-aura-a {
width: 520rpx;
height: 520rpx;
right: -160rpx;
top: 74rpx;
background: radial-gradient(circle, rgba(79,183,255,0.34), rgba(79,183,255,0));
}
.bg-aura-b {
width: 460rpx;
height: 460rpx;
left: -180rpx;
top: 360rpx;
background: radial-gradient(circle, rgba(53,214,166,0.35), rgba(53,214,166,0));
animation-delay: -2.5s;
}
.bg-aura-c {
width: 420rpx;
height: 420rpx;
right: -110rpx;
top: 860rpx;
background: radial-gradient(circle, rgba(143,124,255,0.22), rgba(143,124,255,0));
animation-delay: -4s;
}
.bg-space-dust {
position: absolute;
border-radius: 50%;
background:
radial-gradient(circle, rgba(255,255,255,0.9) 0 2rpx, transparent 3rpx),
radial-gradient(circle, rgba(53,214,166,0.5) 0 2rpx, transparent 3rpx),
radial-gradient(circle, rgba(143,124,255,0.38) 0 2rpx, transparent 3rpx);
background-size: 86rpx 92rpx, 124rpx 116rpx, 156rpx 148rpx;
opacity: 0.38;
animation: dustDrift 16s linear infinite;
}
.bg-space-dust-a {
left: -120rpx;
top: 120rpx;
width: 980rpx;
height: 860rpx;
}
.bg-space-dust-b {
left: -180rpx;
top: 900rpx;
width: 1000rpx;
height: 760rpx;
animation-delay: -6s;
opacity: 0.24;
}
@keyframes dustDrift {
0% { transform: translate3d(0, 0, 0); }
100% { transform: translate3d(-86rpx, 92rpx, 0); }
}
.bg-shooting {
position: absolute;
width: 180rpx;
height: 3rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,0.9), rgba(79,183,255,0.25));
transform: rotate(-30deg);
animation: shooting 5.6s linear infinite;
}
.bg-shooting-a {
right: -160rpx;
top: 240rpx;
}
.bg-shooting-b {
right: -160rpx;
top: 780rpx;
width: 130rpx;
animation-delay: -2.8s;
opacity: 0.72;
}
@keyframes shooting {
0% { transform: translate3d(180rpx, -80rpx, 0) rotate(-30deg); opacity: 0; }
10% { opacity: 1; }
60% { opacity: 1; }
100% { transform: translate3d(-760rpx, 300rpx, 0) rotate(-30deg); opacity: 0; }
}
@keyframes auraFloat {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(18rpx, -34rpx, 0) scale(1.06); }
}
.bg-orbit {
position: absolute;
border: 2rpx solid rgba(255,255,255,0.72);
border-radius: 50%;
transform: rotate(-18deg);
}
.bg-orbit-a {
width: 920rpx;
height: 300rpx;
left: -60rpx;
top: 250rpx;
opacity: 0.34;
}
.bg-orbit-b {
width: 720rpx;
height: 210rpx;
right: -190rpx;
top: 680rpx;
opacity: 0.26;
}
.bg-star {
position: absolute;
background: rgba(255,255,255,0.92);
border-radius: 50%;
box-shadow: 0 0 12rpx rgba(79,183,255,0.45);
opacity: 0.55;
animation: twinkle 3.6s ease-in-out infinite;
}
@keyframes twinkle {
0%, 100% { opacity: 0.28; transform: scale(0.8); }
50% { opacity: 0.85; transform: scale(1.28); }
}
.bg-planet {
position: absolute;
border-radius: 50%;
opacity: 0.8;
box-shadow: inset -18rpx -22rpx 42rpx rgba(18,52,47,0.08), 0 22rpx 60rpx rgba(53,214,166,0.18);
}
.bg-planet-a {
width: 120rpx;
height: 120rpx;
right: 34rpx;
top: 106rpx;
background: radial-gradient(circle at 32% 28%, #FFFFFF 0%, #BAF7CF 32%, #7DE2FF 100%);
animation: float 8.5s ease-in-out infinite;
}
.bg-planet-b {
width: 92rpx;
height: 92rpx;
left: 24rpx;
top: 760rpx;
background: radial-gradient(circle at 30% 30%, #FFFFFF 0%, #FFE7A8 36%, #FFB7D1 100%);
animation: float 10s ease-in-out infinite reverse;
}
@keyframes float {
0%, 100% { transform: translateY(0) translateX(0); }
50% { transform: translateY(-24rpx) translateX(12rpx); }
}
.nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 44px;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
box-sizing: content-box;
background: linear-gradient(180deg, rgba(243,255,244,0.88), rgba(243,255,244,0));
}
.nav-back {
position: absolute;
left: 20rpx;
bottom: 0;
height: 44px;
width: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-back-icon {
color: #12342F;
font-size: 48rpx;
font-weight: 300;
}
.nav-title {
color: #12342F;
font-size: 34rpx;
font-weight: 800;
letter-spacing: 1rpx;
align-self: flex-end;
padding-bottom: 6rpx;
}
.planet-scroll {
position: relative;
z-index: 5;
height: 100vh;
box-sizing: border-box;
padding-left: 22rpx;
padding-right: 22rpx;
}
.bottom-space {
height: 60rpx;
}
/* 骨架屏 */
.skeleton {
padding-top: 22rpx;
}
.sk-block {
border-radius: 36rpx;
background: linear-gradient(90deg, rgba(255,255,255,0.45) 25%, rgba(255,255,255,0.9) 37%, rgba(255,255,255,0.45) 63%);
background-size: 400% 100%;
animation: shimmer 1.4s ease infinite;
margin-bottom: 24rpx;
box-shadow: 0 18rpx 42rpx rgba(53,214,166,0.1);
}
.sk-header { height: 520rpx; border-radius: 48rpx; }
.sk-me { height: 150rpx; }
.sk-row { height: 220rpx; }
.sk-list { height: 330rpx; }
@keyframes shimmer {
0% { background-position: 100% 50%; }
100% { background-position: 0 50%; }
}
/* 宝箱结果 */
.box-result-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(18,52,47,0.28);
backdrop-filter: blur(8px);
z-index: 60;
display: flex;
align-items: center;
justify-content: center;
}
.box-result {
position: relative;
width: 540rpx;
padding: 62rpx 40rpx 42rpx;
border-radius: 48rpx;
background: linear-gradient(155deg, rgba(255,255,255,0.92), rgba(244,255,249,0.78));
border: 2rpx solid rgba(255,255,255,0.9);
box-shadow: 0 34rpx 80rpx rgba(53,214,166,0.24);
text-align: center;
overflow: hidden;
}
.box-result-burst {
position: absolute;
top: -60rpx;
left: 50%;
transform: translateX(-50%);
width: 400rpx;
height: 400rpx;
background: radial-gradient(circle, rgba(255,184,77,0.36), transparent 62%);
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: translateX(-50%) scale(0.9); opacity: 0.6; }
50% { transform: translateX(-50%) scale(1.1); opacity: 1; }
}
.box-result-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 190rpx;
height: 190rpx;
border-radius: 46rpx;
background: linear-gradient(145deg, #FFFFFF, #DDF8FF 48%, #E8DFFF);
color: #22B889;
font-size: 24rpx;
font-weight: 900;
letter-spacing: 1rpx;
position: relative;
animation: bounce 1s ease infinite;
box-shadow: inset -16rpx -20rpx 34rpx rgba(143,124,255,0.16), 0 22rpx 48rpx rgba(79,183,255,0.18);
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12rpx); }
}
.box-result-title {
margin-top: 20rpx;
color: #12342F;
font-size: 36rpx;
font-weight: 800;
}
.box-result-desc {
margin-top: 12rpx;
color: #42635E;
font-size: 26rpx;
}
.box-result-btn {
margin-top: 36rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(135deg, #35D6A6, #4FB7FF);
color: #FFFFFF;
font-size: 30rpx;
font-weight: 700;
box-shadow: 0 18rpx 36rpx rgba(53,214,166,0.25);
}
</style>