|
|
@ -1,62 +1,85 @@ |
|
|
<template> |
|
|
<template> |
|
|
<view class="adv"> |
|
|
<view class="boost"> |
|
|
<view class="adv-bg"></view> |
|
|
|
|
|
<view class="nav" :style="{paddingTop: statusBarHeight + 'px'}"> |
|
|
<view class="nav" :style="{paddingTop: statusBarHeight + 'px'}"> |
|
|
<view class="nav-back" @tap="goBack"><text>‹</text></view> |
|
|
<view class="nav-back" @tap="goBack"><text>‹</text></view> |
|
|
<view class="nav-title">松鼠屯屯乐</view> |
|
|
<view class="nav-title">学院助推赛</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<view class="hud" :style="{top: (statusBarHeight + 48) + 'px'}"> |
|
|
<scroll-view scroll-y class="page" :style="{paddingTop: (statusBarHeight + 58) + 'px'}"> |
|
|
<view><text>{{score}}</text><text>分数</text></view> |
|
|
<view class="hero"> |
|
|
<view><text>{{timeLeft}}</text><text>秒</text></view> |
|
|
<view class="hero-kicker">COLLEGE ROCKET</view> |
|
|
<view><text>{{Math.round(player.mass)}}</text><text>体型</text></view> |
|
|
<view class="hero-title">今天为 {{collegeName}} 推一把</view> |
|
|
|
|
|
<view class="rocket"> |
|
|
|
|
|
<view class="rocket-body">🚀</view> |
|
|
|
|
|
<view class="rocket-line"> |
|
|
|
|
|
<view class="rocket-progress" :style="{width: rocketPercent + '%'}"></view> |
|
|
|
|
|
</view> |
|
|
|
|
|
</view> |
|
|
|
|
|
<view class="hero-sub">每天1关,通关贡献学院推进值。21:30结算,前三学院成员分别获得3/2/1张星球券。</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<canvas |
|
|
<view class="rank-card"> |
|
|
canvas-id="adventureCanvas" |
|
|
<view class="section-head"> |
|
|
id="adventureCanvas" |
|
|
<text>学院推进榜</text> |
|
|
class="game" |
|
|
<text>我的学院第 {{home.myRankNo || '-'}} 名</text> |
|
|
:disable-scroll="true" |
|
|
</view> |
|
|
@touchstart="onTouch" |
|
|
<view class="college-row" v-for="(item,i) in collegeRanks" :key="i"> |
|
|
@touchmove="onTouch"> |
|
|
<view class="medal">{{i + 1}}</view> |
|
|
</canvas> |
|
|
<view class="college-main"> |
|
|
|
|
|
<text>{{item.college || item.nickname || '未知学院'}}</text> |
|
|
|
|
|
<view class="bar"><view :style="{width: (item.percent || 8) + '%'}"></view></view> |
|
|
|
|
|
</view> |
|
|
|
|
|
<view class="score">{{item.score || 0}}</view> |
|
|
|
|
|
</view> |
|
|
|
|
|
<view class="empty" v-if="!collegeRanks.length">今天还没有学院上榜,快来抢第一棒</view> |
|
|
|
|
|
</view> |
|
|
|
|
|
|
|
|
<view v-if="!playing" class="panel"> |
|
|
<view class="game-card"> |
|
|
<view class="panel-card"> |
|
|
<view class="section-head"> |
|
|
<image class="hero-img" src="/static/images/img/loading.gif" mode="aspectFit"></image> |
|
|
<text>今日关卡</text> |
|
|
<view class="panel-kicker">SQUIRREL TUN TUN</view> |
|
|
<text>{{cleared ? '已通关' : '约2分钟'}}</text> |
|
|
<view class="panel-title">松鼠屯屯乐</view> |
|
|
|
|
|
<view class="panel-sub">在糖果宇宙里屯松子、抢补给、躲大松鼠。撞掉的松子会散落成一片松子雨</view> |
|
|
|
|
|
<view class="event" v-if="home.event">{{home.event.name}}:{{home.event.description}}</view> |
|
|
|
|
|
<view class="my">我的最高分 {{home.myBestScore || 0}} · 今日第 {{home.myRankNo || '-'}} 名</view> |
|
|
|
|
|
<view class="start-btn" @tap="startGame">{{home.freeAvailable ? '免费开始' : '消耗1张星球券开始'}}</view> |
|
|
|
|
|
<view class="rule">21:30结算,前10名发放10-1张星球券</view> |
|
|
|
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
|
|
|
<view class="tips"> |
|
|
|
|
|
<text>规则:点选未被压住的卡牌,底部槽内3张相同自动消除,槽满则失败。</text> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<view v-if="result.show" class="panel"> |
|
|
<view class="board"> |
|
|
<view class="panel-card"> |
|
|
<view |
|
|
<view class="panel-kicker">FINISH</view> |
|
|
class="tile" |
|
|
<view class="panel-title">本局 {{score}} 分</view> |
|
|
v-for="card in visibleCards" |
|
|
<view class="panel-sub">普通{{counts.normal}} · 金色{{counts.golden}} · 彩虹{{counts.rainbow}} · 补给{{counts.prop}}</view> |
|
|
:key="card.id" |
|
|
<view class="my">当前排名:{{result.rankNo || '-'}}</view> |
|
|
:class="{locked: isLocked(card), selected: card.selected}" |
|
|
<view class="start-btn" @tap="closeResult">知道了</view> |
|
|
:style="card.style" |
|
|
|
|
|
@tap="pickCard(card)"> |
|
|
|
|
|
<text>{{card.icon}}</text> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
|
|
|
<view class="rank"> |
|
|
<view class="slot-wrap"> |
|
|
<view class="rank-title">今日前10</view> |
|
|
<view class="slot-title">助推槽 {{slots.length}}/7</view> |
|
|
<scroll-view scroll-y class="rank-list"> |
|
|
<view class="slots"> |
|
|
<view class="rank-row" v-for="(item,i) in ranks" :key="i"> |
|
|
<view class="slot" v-for="i in 7" :key="i"> |
|
|
<text class="rank-no">{{i + 1}}</text> |
|
|
<text v-if="slots[i - 1]">{{slots[i - 1].icon}}</text> |
|
|
<image class="rank-avatar" :src="item.avatar || defaultAvatar" mode="aspectFill"></image> |
|
|
</view> |
|
|
<view class="rank-mid"> |
|
|
</view> |
|
|
<text>{{item.nickname || '松鼠同学'}}</text> |
|
|
</view> |
|
|
<text>{{item.college || '校园玩家'}}</text> |
|
|
|
|
|
|
|
|
<view class="actions"> |
|
|
|
|
|
<view class="sub-btn" @tap="resetLevel">重开本关</view> |
|
|
|
|
|
<view class="main-btn" :class="{disabled: cleared || playing}" @tap="startLevel"> |
|
|
|
|
|
{{cleared ? '今日已助推' : (session ? '继续助推' : '开始助推')}} |
|
|
|
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
<text class="rank-score">{{item.score || 0}}</text> |
|
|
|
|
|
</view> |
|
|
</view> |
|
|
</scroll-view> |
|
|
</scroll-view> |
|
|
|
|
|
|
|
|
|
|
|
<view v-if="modal.show" class="modal"> |
|
|
|
|
|
<view class="modal-card"> |
|
|
|
|
|
<view class="modal-title">{{modal.title}}</view> |
|
|
|
|
|
<view class="modal-sub">{{modal.sub}}</view> |
|
|
|
|
|
<view class="main-btn" @tap="closeModal">知道了</view> |
|
|
|
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</template> |
|
|
</template> |
|
|
@ -72,66 +95,88 @@ |
|
|
avatar: '', |
|
|
avatar: '', |
|
|
college: '', |
|
|
college: '', |
|
|
home: {}, |
|
|
home: {}, |
|
|
ranks: [], |
|
|
|
|
|
ctx: null, |
|
|
|
|
|
w: 375, |
|
|
|
|
|
h: 667, |
|
|
|
|
|
playing: false, |
|
|
|
|
|
session: null, |
|
|
session: null, |
|
|
|
|
|
playing: false, |
|
|
|
|
|
cleared: false, |
|
|
startTs: 0, |
|
|
startTs: 0, |
|
|
timeLeft: 60, |
|
|
cards: [], |
|
|
score: 0, |
|
|
slots: [], |
|
|
world: { w: 2200, h: 2200 }, |
|
|
moveCount: 0, |
|
|
camera: { x: 0, y: 0 }, |
|
|
modal: { show: false, title: '', sub: '' } |
|
|
pointer: null, |
|
|
} |
|
|
player: { x: 1100, y: 1100, r: 34, mass: 34, speed: 4.2, name: '我' }, |
|
|
}, |
|
|
items: [], |
|
|
computed: { |
|
|
ais: [], |
|
|
collegeName() { |
|
|
drops: [], |
|
|
return this.college || '我的学院' |
|
|
props: [], |
|
|
}, |
|
|
effects: { magnet: 0, double: 0, shield: 0, rocket: 0 }, |
|
|
collegeRanks() { |
|
|
counts: { normal: 0, golden: 0, diamond: 0, rainbow: 0, ticketBag: 0, prop: 0, hit: 0 }, |
|
|
const list = this.home.rankList || [] |
|
|
raf: null, |
|
|
let max = 1 |
|
|
lastSpawn: 0, |
|
|
list.forEach(item => { |
|
|
result: { show: false, rankNo: 0 }, |
|
|
if ((item.score || 0) > max) max = item.score || 0 |
|
|
squirrelImg: null, |
|
|
}) |
|
|
defaultAvatar: 'https://jewel-shop.oss-cn-beijing.aliyuncs.com/41cfb56caff4419b94b69d0f2303b602.png' |
|
|
return list.map(item => { |
|
|
|
|
|
item.percent = Math.max(8, Math.round((item.score || 0) * 100 / max)) |
|
|
|
|
|
return item |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
rocketPercent() { |
|
|
|
|
|
let mine = null |
|
|
|
|
|
for (let i = 0; i < this.collegeRanks.length; i++) { |
|
|
|
|
|
const item = this.collegeRanks[i] |
|
|
|
|
|
if ((item.college || item.nickname) === this.college) { |
|
|
|
|
|
mine = item |
|
|
|
|
|
break |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
const top = this.collegeRanks[0] || {} |
|
|
|
|
|
if (!mine || !top.score) return 16 |
|
|
|
|
|
return Math.max(16, Math.min(100, Math.round((mine.score || 0) * 100 / top.score))) |
|
|
|
|
|
}, |
|
|
|
|
|
visibleCards() { |
|
|
|
|
|
return this.cards.filter(item => !item.removed) |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
onLoad() { |
|
|
onLoad() { |
|
|
const sys = uni.getSystemInfoSync() |
|
|
const sys = uni.getSystemInfoSync() |
|
|
this.statusBarHeight = sys.statusBarHeight || 20 |
|
|
this.statusBarHeight = sys.statusBarHeight || 20 |
|
|
this.w = sys.windowWidth |
|
|
|
|
|
this.h = sys.windowHeight |
|
|
|
|
|
this.userId = uni.getStorageSync('id') || '' |
|
|
this.userId = uni.getStorageSync('id') || '' |
|
|
this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || '' |
|
|
this.nickname = uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || '' |
|
|
this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || '' |
|
|
this.avatar = uni.getStorageSync('avatarUrl') || uni.getStorageSync('avatar') || '' |
|
|
|
|
|
this.college = uni.getStorageSync('departmentName') || uni.getStorageSync('departmentTitle') || uni.getStorageSync('college') || '' |
|
|
|
|
|
try { |
|
|
|
|
|
const user = uni.getStorageSync('user') |
|
|
|
|
|
if (!this.college && user) { |
|
|
|
|
|
const u = JSON.parse(user) |
|
|
|
|
|
this.college = u.departmentTitle || u.departmentName || u.departmentNameStr || u.college || '' |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) {} |
|
|
try { |
|
|
try { |
|
|
const area = uni.getStorageSync('area') |
|
|
const area = uni.getStorageSync('area') |
|
|
if (area) this.regionId = JSON.parse(area).id || '' |
|
|
if (area) this.regionId = JSON.parse(area).id || '' |
|
|
} catch (e) {} |
|
|
} catch (e) {} |
|
|
this.ctx = uni.createCanvasContext('adventureCanvas', this) |
|
|
|
|
|
this.squirrelImg = '/static/images/img/loading.gif' |
|
|
|
|
|
this.loadHome() |
|
|
this.loadHome() |
|
|
}, |
|
|
this.buildLevel() |
|
|
onUnload() { |
|
|
|
|
|
this.stopLoop() |
|
|
|
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
loadHome() { |
|
|
loadHome() { |
|
|
this.tui.request('/app/planet/adventure/home', 'POST', { |
|
|
this.tui.request('/app/planet/adventure/home', 'POST', { |
|
|
userId: this.userId, |
|
|
userId: this.userId, |
|
|
regionId: this.regionId |
|
|
regionId: this.regionId, |
|
|
|
|
|
college: this.college |
|
|
}, false, false, true).then(res => { |
|
|
}, false, false, true).then(res => { |
|
|
if (res.code == 200 && res.result) { |
|
|
if (res.code == 200 && res.result) { |
|
|
this.home = res.result |
|
|
this.home = res.result |
|
|
this.ranks = res.result.rankList || [] |
|
|
this.cleared = !res.result.freeAvailable |
|
|
} else if (res.message) { |
|
|
|
|
|
this.tui.toast(res.message) |
|
|
|
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
startGame() { |
|
|
startLevel() { |
|
|
|
|
|
if (this.cleared || this.playing) return |
|
|
|
|
|
if (!this.college) { |
|
|
|
|
|
this.tui.toast('缺少学院信息,暂时无法参赛') |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
this.tui.request('/app/planet/adventure/start', 'POST', { |
|
|
this.tui.request('/app/planet/adventure/start', 'POST', { |
|
|
userId: this.userId, |
|
|
userId: this.userId, |
|
|
regionId: this.regionId, |
|
|
regionId: this.regionId, |
|
|
@ -144,403 +189,124 @@ |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
this.session = res.result |
|
|
this.session = res.result |
|
|
this.resetGame() |
|
|
|
|
|
this.playing = true |
|
|
this.playing = true |
|
|
this.loop() |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
resetGame() { |
|
|
|
|
|
this.score = 0 |
|
|
|
|
|
this.timeLeft = 60 |
|
|
|
|
|
this.items = [] |
|
|
|
|
|
this.ais = [] |
|
|
|
|
|
this.drops = [] |
|
|
|
|
|
this.props = [] |
|
|
|
|
|
this.effects = { magnet: 0, double: 0, shield: 0, rocket: 0 } |
|
|
|
|
|
this.counts = { normal: 0, golden: 0, diamond: 0, rainbow: 0, ticketBag: 0, prop: 0, hit: 0 } |
|
|
|
|
|
this.player = { x: this.world.w / 2, y: this.world.h / 2, r: 38, mass: 38, speed: 4.2, name: this.nickname || '我' } |
|
|
|
|
|
this.pointer = null |
|
|
|
|
|
this.startTs = Date.now() |
|
|
this.startTs = Date.now() |
|
|
this.lastSpawn = 0 |
|
|
this.buildLevel() |
|
|
this.result.show = false |
|
|
|
|
|
for (let i = 0; i < 120; i++) this.spawnNut() |
|
|
|
|
|
for (let i = 0; i < 10; i++) this.spawnAI(i) |
|
|
|
|
|
}, |
|
|
|
|
|
loop() { |
|
|
|
|
|
if (!this.playing) return |
|
|
|
|
|
const now = Date.now() |
|
|
|
|
|
const elapsed = Math.floor((now - this.startTs) / 1000) |
|
|
|
|
|
this.timeLeft = Math.max(0, 60 - elapsed) |
|
|
|
|
|
this.update(now) |
|
|
|
|
|
this.draw() |
|
|
|
|
|
if (this.timeLeft <= 0) { |
|
|
|
|
|
this.finishGame() |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
this.raf = setTimeout(() => this.loop(), 16) |
|
|
|
|
|
}, |
|
|
|
|
|
stopLoop() { |
|
|
|
|
|
if (this.raf) clearTimeout(this.raf) |
|
|
|
|
|
this.raf = null |
|
|
|
|
|
}, |
|
|
|
|
|
update(now) { |
|
|
|
|
|
if (now - this.lastSpawn > 420) { |
|
|
|
|
|
this.spawnNut() |
|
|
|
|
|
if (this.items.length < 130) this.spawnNut() |
|
|
|
|
|
if (Math.random() < 0.18) this.spawnProp() |
|
|
|
|
|
this.lastSpawn = now |
|
|
|
|
|
} |
|
|
|
|
|
this.movePlayer() |
|
|
|
|
|
this.moveAI() |
|
|
|
|
|
this.updateCamera() |
|
|
|
|
|
this.moveDrops() |
|
|
|
|
|
this.checkCollisions(now) |
|
|
|
|
|
Object.keys(this.effects).forEach(k => { |
|
|
|
|
|
if (this.effects[k] > 0 && this.effects[k] < now) this.effects[k] = 0 |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
spawnNut(x, y, forceType) { |
|
|
|
|
|
x = x === undefined ? 40 + Math.random() * (this.world.w - 80) : x |
|
|
|
|
|
y = y === undefined ? 40 + Math.random() * (this.world.h - 80) : y |
|
|
|
|
|
const r = Math.random() |
|
|
|
|
|
let type = forceType || 'normal' |
|
|
|
|
|
if (!forceType) { |
|
|
|
|
|
if (r > 0.96) type = 'rainbow' |
|
|
|
|
|
else if (r > 0.82) type = 'golden' |
|
|
|
|
|
} |
|
|
|
|
|
const map = { |
|
|
|
|
|
normal: { score: 1, r: 13, color: '#B87932' }, |
|
|
|
|
|
golden: { score: 5, r: 16, color: '#FFB84D' }, |
|
|
|
|
|
rainbow: { score: 20, r: 20, color: '#D96DFF' } |
|
|
|
|
|
} |
|
|
|
|
|
this.items.push(Object.assign({ x, y, type }, map[type])) |
|
|
|
|
|
}, |
|
|
|
|
|
spawnProp() { |
|
|
|
|
|
this.props.push({ |
|
|
|
|
|
x: 80 + Math.random() * (this.world.w - 160), |
|
|
|
|
|
y: 80 + Math.random() * (this.world.h - 160), |
|
|
|
|
|
r: 22, |
|
|
|
|
|
type: ['magnet', 'double', 'shield', 'rocket', 'box', 'ticket'][Math.floor(Math.random() * 6)] |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
spawnAI(i) { |
|
|
|
|
|
const names = ['奶茶松鼠', '晚八人', '食堂猎手', '图书馆王', '跑腿侠', '卷王', '摸鱼星人', '拼团仔', '快递站长', '校园锦鲤'] |
|
|
|
|
|
this.ais.push({ |
|
|
|
|
|
x: 120 + Math.random() * (this.world.w - 240), |
|
|
|
|
|
y: 120 + Math.random() * (this.world.h - 240), |
|
|
|
|
|
r: 30 + Math.random() * 28, |
|
|
|
|
|
mass: 30 + Math.random() * 28, |
|
|
|
|
|
speed: 2.2 + Math.random() * 1.6, |
|
|
|
|
|
name: names[i] || 'AI松鼠', |
|
|
|
|
|
tx: 0, |
|
|
|
|
|
ty: 0, |
|
|
|
|
|
color: ['#FFB84D', '#7DE2FF', '#A8F7C1', '#F7A8D8', '#C8B6FF'][i % 5] |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
movePlayer() { |
|
|
|
|
|
if (!this.pointer) return |
|
|
|
|
|
const wx = this.camera.x + this.pointer.x |
|
|
|
|
|
const wy = this.camera.y + this.pointer.y |
|
|
|
|
|
const dx = wx - this.player.x |
|
|
|
|
|
const dy = wy - this.player.y |
|
|
|
|
|
const dist = Math.sqrt(dx * dx + dy * dy) |
|
|
|
|
|
if (dist > 4) { |
|
|
|
|
|
const speed = (this.effects.rocket ? 7.2 : this.player.speed) * Math.max(0.55, 42 / this.player.r) |
|
|
|
|
|
this.player.x += dx / dist * speed |
|
|
|
|
|
this.player.y += dy / dist * speed |
|
|
|
|
|
} |
|
|
|
|
|
this.keepInWorld(this.player) |
|
|
|
|
|
}, |
|
|
|
|
|
moveAI() { |
|
|
|
|
|
this.ais.forEach(ai => { |
|
|
|
|
|
let target = this.nearestFood(ai) |
|
|
|
|
|
if (!target || Math.random() < 0.006) { |
|
|
|
|
|
ai.tx = Math.random() * this.world.w |
|
|
|
|
|
ai.ty = Math.random() * this.world.h |
|
|
|
|
|
target = { x: ai.tx, y: ai.ty } |
|
|
|
|
|
} |
|
|
|
|
|
const dx = target.x - ai.x |
|
|
|
|
|
const dy = target.y - ai.y |
|
|
|
|
|
const dist = Math.sqrt(dx * dx + dy * dy) || 1 |
|
|
|
|
|
ai.x += dx / dist * ai.speed * Math.max(0.55, 42 / ai.r) |
|
|
|
|
|
ai.y += dy / dist * ai.speed * Math.max(0.55, 42 / ai.r) |
|
|
|
|
|
this.keepInWorld(ai) |
|
|
|
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
nearestFood(role) { |
|
|
buildLevel() { |
|
|
let best = null |
|
|
const icons = ['🍔', '🥤', '📚', '🏀', '🎧', '🚲', '🍜', '📦', '☕', '🧋', '🎮', '🌟'] |
|
|
let bestD = 999999 |
|
|
const pool = [] |
|
|
this.items.forEach(it => { |
|
|
icons.forEach(icon => { |
|
|
const d = Math.abs(it.x - role.x) + Math.abs(it.y - role.y) |
|
|
for (let i = 0; i < 6; i++) pool.push(icon) |
|
|
if (d < bestD) { |
|
|
|
|
|
bestD = d |
|
|
|
|
|
best = it |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
}) |
|
|
return best |
|
|
this.shuffle(pool) |
|
|
}, |
|
|
const cards = [] |
|
|
updateCamera() { |
|
|
const layerCounts = [28, 24, 20] |
|
|
this.camera.x = Math.max(0, Math.min(this.world.w - this.w, this.player.x - this.w / 2)) |
|
|
let idx = 0 |
|
|
this.camera.y = Math.max(0, Math.min(this.world.h - this.h, this.player.y - this.h / 2)) |
|
|
layerCounts.forEach((count, layer) => { |
|
|
}, |
|
|
for (let i = 0; i < count; i++) { |
|
|
moveDrops() { |
|
|
cards.push({ |
|
|
for (let i = this.drops.length - 1; i >= 0; i--) { |
|
|
id: 'c' + idx, |
|
|
const d = this.drops[i] |
|
|
icon: pool[idx], |
|
|
d.x += d.vx |
|
|
layer, |
|
|
d.y += d.vy |
|
|
x: 24 + (i % 7) * 88 + (layer * 18) + ((i % 2) * 8), |
|
|
d.vx *= 0.94 |
|
|
y: 34 + Math.floor(i / 7) * 86 + (layer * 54), |
|
|
d.vy *= 0.94 |
|
|
style: '', |
|
|
d.life-- |
|
|
removed: false, |
|
|
if (d.life <= 0) { |
|
|
selected: false |
|
|
this.spawnNut(d.x, d.y, 'normal') |
|
|
|
|
|
this.drops.splice(i, 1) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
keepInWorld(o) { |
|
|
|
|
|
o.x = Math.max(o.r, Math.min(this.world.w - o.r, o.x)) |
|
|
|
|
|
o.y = Math.max(o.r, Math.min(this.world.h - o.r, o.y)) |
|
|
|
|
|
}, |
|
|
|
|
|
checkCollisions(now) { |
|
|
|
|
|
this.hitWorldItems(this.player, this.items, (it) => { |
|
|
|
|
|
const multi = this.effects.double ? 2 : 1 |
|
|
|
|
|
this.score += it.score * multi |
|
|
|
|
|
this.counts[it.type]++ |
|
|
|
|
|
this.grow(this.player, it.score * 0.55) |
|
|
|
|
|
}) |
|
|
}) |
|
|
this.hitWorldItems(this.player, this.props, (it) => { |
|
|
cards[cards.length - 1].style = `left:${cards[cards.length - 1].x}rpx;top:${cards[cards.length - 1].y}rpx;z-index:${cards[cards.length - 1].layer + 1};` |
|
|
this.counts.prop++ |
|
|
idx++ |
|
|
const until = now + (it.type === 'rocket' ? 5000 : 15000) |
|
|
|
|
|
if (it.type === 'box') { |
|
|
|
|
|
this.effects[['magnet', 'double', 'shield', 'rocket'][Math.floor(Math.random() * 4)]] = until |
|
|
|
|
|
} else if (it.type === 'ticket') { |
|
|
|
|
|
this.counts.ticketBag++ |
|
|
|
|
|
this.score += 8 |
|
|
|
|
|
this.grow(this.player, 3) |
|
|
|
|
|
} else { |
|
|
|
|
|
this.effects[it.type] = until |
|
|
|
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
this.ais.forEach(ai => { |
|
|
this.cards = cards |
|
|
this.hitWorldItems(ai, this.items, (it) => this.grow(ai, it.score * 0.5)) |
|
|
this.slots = [] |
|
|
this.resolveRoleHit(ai) |
|
|
this.moveCount = 0 |
|
|
}) |
|
|
|
|
|
this.checkDropPickup() |
|
|
|
|
|
}, |
|
|
}, |
|
|
hitWorldItems(role, list, cb) { |
|
|
pickCard(card) { |
|
|
for (let i = list.length - 1; i >= 0; i--) { |
|
|
if (!this.playing) { |
|
|
const it = list[i] |
|
|
this.tui.toast('先点击开始助推') |
|
|
if (this.effects.magnet && role === this.player && list === this.items) { |
|
|
|
|
|
const mdx = role.x - it.x |
|
|
|
|
|
const mdy = role.y - it.y |
|
|
|
|
|
const md = Math.sqrt(mdx * mdx + mdy * mdy) |
|
|
|
|
|
if (md < 190) { |
|
|
|
|
|
it.x += mdx * 0.1 |
|
|
|
|
|
it.y += mdy * 0.1 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
const dx = it.x - role.x |
|
|
|
|
|
const dy = it.y - role.y |
|
|
|
|
|
if (Math.sqrt(dx * dx + dy * dy) < it.r + role.r * 0.72) { |
|
|
|
|
|
list.splice(i, 1) |
|
|
|
|
|
cb(it) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
resolveRoleHit(ai) { |
|
|
|
|
|
const dx = ai.x - this.player.x |
|
|
|
|
|
const dy = ai.y - this.player.y |
|
|
|
|
|
const dist = Math.sqrt(dx * dx + dy * dy) |
|
|
|
|
|
if (dist > ai.r + this.player.r - 8) return |
|
|
|
|
|
if (this.effects.shield || this.effects.rocket) { |
|
|
|
|
|
this.effects.shield = 0 |
|
|
|
|
|
this.dropNutRain(ai, 8) |
|
|
|
|
|
this.shrink(ai, 8) |
|
|
|
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
if (ai.r > this.player.r * 1.08) { |
|
|
if (card.removed || card.selected || this.isLocked(card)) return |
|
|
const loss = Math.min(18, Math.max(6, Math.floor(this.player.mass * 0.22))) |
|
|
if (this.slots.length >= 7) return |
|
|
this.dropNutRain(this.player, loss) |
|
|
card.selected = true |
|
|
this.shrink(this.player, loss) |
|
|
this.slots.push(card) |
|
|
this.counts.hit++ |
|
|
this.moveCount++ |
|
|
} else if (this.player.r > ai.r * 1.12) { |
|
|
this.tryClear(card.icon) |
|
|
this.dropNutRain(ai, 10) |
|
|
if (this.cards.every(item => item.removed || item.selected)) { |
|
|
this.shrink(ai, 10) |
|
|
this.finishClear() |
|
|
this.score += 10 |
|
|
} else if (this.slots.length >= 7) { |
|
|
this.grow(this.player, 5) |
|
|
this.failLevel() |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
checkDropPickup() { |
|
|
tryClear(icon) { |
|
|
for (let i = this.drops.length - 1; i >= 0; i--) { |
|
|
const same = this.slots.filter(item => item.icon === icon) |
|
|
const d = this.drops[i] |
|
|
if (same.length < 3) return |
|
|
const dx = d.x - this.player.x |
|
|
let removed = 0 |
|
|
const dy = d.y - this.player.y |
|
|
this.slots = this.slots.filter(item => { |
|
|
if (Math.sqrt(dx * dx + dy * dy) < this.player.r + 10) { |
|
|
if (item.icon === icon && removed < 3) { |
|
|
this.drops.splice(i, 1) |
|
|
item.removed = true |
|
|
this.score += 1 |
|
|
removed++ |
|
|
this.counts.normal++ |
|
|
return false |
|
|
this.grow(this.player, 0.5) |
|
|
} |
|
|
} |
|
|
return true |
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
grow(role, v) { |
|
|
|
|
|
role.mass += v |
|
|
|
|
|
role.r = Math.min(96, 24 + Math.sqrt(role.mass) * 3.2) |
|
|
|
|
|
}, |
|
|
|
|
|
shrink(role, v) { |
|
|
|
|
|
role.mass = Math.max(20, role.mass - v) |
|
|
|
|
|
role.r = Math.max(28, 24 + Math.sqrt(role.mass) * 3.2) |
|
|
|
|
|
}, |
|
|
|
|
|
dropNutRain(role, count) { |
|
|
|
|
|
for (let i = 0; i < count; i++) { |
|
|
|
|
|
const a = Math.random() * Math.PI * 2 |
|
|
|
|
|
const s = 2 + Math.random() * 5 |
|
|
|
|
|
this.drops.push({ |
|
|
|
|
|
x: role.x + Math.cos(a) * role.r, |
|
|
|
|
|
y: role.y + Math.sin(a) * role.r, |
|
|
|
|
|
vx: Math.cos(a) * s, |
|
|
|
|
|
vy: Math.sin(a) * s, |
|
|
|
|
|
r: 8, |
|
|
|
|
|
life: 18 + Math.floor(Math.random() * 18) |
|
|
|
|
|
}) |
|
|
}) |
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
draw() { |
|
|
|
|
|
const c = this.ctx |
|
|
|
|
|
c.clearRect(0, 0, this.w, this.h) |
|
|
|
|
|
this.drawMap(c) |
|
|
|
|
|
this.items.forEach(it => this.drawNut(c, it)) |
|
|
|
|
|
this.drops.forEach(it => this.drawNut(c, it, true)) |
|
|
|
|
|
this.props.forEach(it => this.drawProp(c, it)) |
|
|
|
|
|
this.ais.forEach(ai => this.drawSquirrel(c, ai, false)) |
|
|
|
|
|
this.drawSquirrel(c, this.player, true) |
|
|
|
|
|
c.draw() |
|
|
|
|
|
}, |
|
|
|
|
|
drawMap(c) { |
|
|
|
|
|
c.setFillStyle('#EAF8FF') |
|
|
|
|
|
c.fillRect(0, 0, this.w, this.h) |
|
|
|
|
|
const ox = -this.camera.x |
|
|
|
|
|
const oy = -this.camera.y |
|
|
|
|
|
for (let i = 0; i < 80; i++) { |
|
|
|
|
|
const x = (i * 173) % this.world.w + ox |
|
|
|
|
|
const y = (i * 119) % this.world.h + oy |
|
|
|
|
|
if (x > -80 && x < this.w + 80 && y > -80 && y < this.h + 80) { |
|
|
|
|
|
this.cloud(c, x, y, i) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
c.setStrokeStyle('rgba(255,255,255,0.55)') |
|
|
|
|
|
c.setLineWidth(1) |
|
|
|
|
|
for (let gx = -this.camera.x % 120; gx < this.w; gx += 120) { |
|
|
|
|
|
c.beginPath(); c.moveTo(gx, 0); c.lineTo(gx, this.h); c.stroke() |
|
|
|
|
|
} |
|
|
|
|
|
for (let gy = -this.camera.y % 120; gy < this.h; gy += 120) { |
|
|
|
|
|
c.beginPath(); c.moveTo(0, gy); c.lineTo(this.w, gy); c.stroke() |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
}, |
|
|
screen(o) { |
|
|
isLocked(card) { |
|
|
return { x: o.x - this.camera.x, y: o.y - this.camera.y } |
|
|
return this.cards.some(other => { |
|
|
}, |
|
|
if (other.removed || other.selected || other.layer <= card.layer) return false |
|
|
cloud(c, x, y, i) { |
|
|
return Math.abs(other.x - card.x) < 78 && Math.abs(other.y - card.y) < 70 |
|
|
const colors = ['rgba(255,184,77,0.16)', 'rgba(53,214,166,0.14)', 'rgba(143,124,255,0.13)'] |
|
|
}) |
|
|
c.setFillStyle(colors[i % colors.length]) |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.arc(x, y, 26 + (i % 4) * 8, 0, Math.PI * 2) |
|
|
|
|
|
c.fill() |
|
|
|
|
|
}, |
|
|
|
|
|
drawNut(c, it, small) { |
|
|
|
|
|
const p = this.screen(it) |
|
|
|
|
|
const r = small ? 8 : it.r |
|
|
|
|
|
c.save() |
|
|
|
|
|
c.translate(p.x, p.y) |
|
|
|
|
|
c.rotate(0.5) |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.setFillStyle(it.color || '#B87932') |
|
|
|
|
|
c.ellipse(0, 0, r * 0.78, r, 0, 0, Math.PI * 2) |
|
|
|
|
|
c.fill() |
|
|
|
|
|
c.setStrokeStyle('rgba(255,255,255,0.68)') |
|
|
|
|
|
c.setLineWidth(2) |
|
|
|
|
|
for (let y = -r * 0.45; y <= r * 0.45; y += r * 0.32) { |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.moveTo(-r * 0.45, y) |
|
|
|
|
|
c.lineTo(r * 0.45, y + r * 0.16) |
|
|
|
|
|
c.stroke() |
|
|
|
|
|
} |
|
|
|
|
|
c.restore() |
|
|
|
|
|
}, |
|
|
|
|
|
drawProp(c, it) { |
|
|
|
|
|
const p = this.screen(it) |
|
|
|
|
|
const label = { magnet: '磁', double: '2X', shield: '盾', rocket: '冲', box: '箱', ticket: '券' }[it.type] || '礼' |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.setFillStyle(it.type === 'ticket' ? '#FFB84D' : '#35D6A6') |
|
|
|
|
|
c.arc(p.x, p.y, it.r, 0, Math.PI * 2) |
|
|
|
|
|
c.fill() |
|
|
|
|
|
c.setFillStyle('#FFFFFF') |
|
|
|
|
|
c.setFontSize(12) |
|
|
|
|
|
c.fillText(label, p.x - (label.length > 1 ? 8 : 6), p.y + 4) |
|
|
|
|
|
}, |
|
|
|
|
|
drawSquirrel(c, role, self) { |
|
|
|
|
|
const p = this.screen(role) |
|
|
|
|
|
if (p.x < -140 || p.x > this.w + 140 || p.y < -140 || p.y > this.h + 140) return |
|
|
|
|
|
const r = role.r |
|
|
|
|
|
if (self && this.effects.shield) { |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.setFillStyle('rgba(125,226,255,0.25)') |
|
|
|
|
|
c.arc(p.x, p.y, r + 16, 0, Math.PI * 2) |
|
|
|
|
|
c.fill() |
|
|
|
|
|
} |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.setFillStyle(self ? '#FFFFFF' : (role.color || '#FFE5AE')) |
|
|
|
|
|
c.arc(p.x, p.y, r + 8, 0, Math.PI * 2) |
|
|
|
|
|
c.fill() |
|
|
|
|
|
c.drawImage(this.squirrelImg, p.x - r, p.y - r, r * 2, r * 2) |
|
|
|
|
|
if (role.r > 62) { |
|
|
|
|
|
c.setFillStyle('#FFB84D') |
|
|
|
|
|
c.beginPath() |
|
|
|
|
|
c.moveTo(p.x - 18, p.y - r - 10) |
|
|
|
|
|
c.lineTo(p.x - 6, p.y - r - 30) |
|
|
|
|
|
c.lineTo(p.x + 4, p.y - r - 10) |
|
|
|
|
|
c.lineTo(p.x + 18, p.y - r - 30) |
|
|
|
|
|
c.lineTo(p.x + 24, p.y - r - 8) |
|
|
|
|
|
c.fill() |
|
|
|
|
|
} |
|
|
|
|
|
c.setFillStyle('#12342F') |
|
|
|
|
|
c.setFontSize(self ? 13 : 11) |
|
|
|
|
|
const name = role.name || '松鼠' |
|
|
|
|
|
c.fillText(name.slice(0, 5), p.x - Math.min(30, name.length * 6), p.y + r + 20) |
|
|
|
|
|
}, |
|
|
|
|
|
onTouch(e) { |
|
|
|
|
|
if (!this.playing || !e.touches || !e.touches.length) return |
|
|
|
|
|
this.pointer = { x: e.touches[0].x, y: e.touches[0].y } |
|
|
|
|
|
}, |
|
|
}, |
|
|
finishGame() { |
|
|
finishClear() { |
|
|
|
|
|
const duration = Math.max(20, Math.floor((Date.now() - this.startTs) / 1000)) |
|
|
|
|
|
const progress = Math.max(100, 260 - duration + Math.max(0, 80 - this.moveCount)) |
|
|
this.playing = false |
|
|
this.playing = false |
|
|
this.stopLoop() |
|
|
|
|
|
const duration = Math.max(1, Math.floor((Date.now() - this.startTs) / 1000)) |
|
|
|
|
|
this.tui.request('/app/planet/adventure/submit', 'POST', { |
|
|
this.tui.request('/app/planet/adventure/submit', 'POST', { |
|
|
userId: this.userId, |
|
|
userId: this.userId, |
|
|
regionId: this.regionId, |
|
|
regionId: this.regionId, |
|
|
sessionId: this.session.sessionId, |
|
|
sessionId: this.session.sessionId, |
|
|
normalCount: this.counts.normal, |
|
|
normalCount: this.cards.length, |
|
|
goldenCount: this.counts.golden, |
|
|
goldenCount: 1, |
|
|
diamondCount: this.counts.diamond, |
|
|
diamondCount: 0, |
|
|
rainbowCount: this.counts.rainbow, |
|
|
rainbowCount: 0, |
|
|
ticketBagCount: this.counts.ticketBag, |
|
|
ticketBagCount: 0, |
|
|
propUseCount: this.counts.prop, |
|
|
propUseCount: 0, |
|
|
hitCount: this.counts.hit, |
|
|
hitCount: 0, |
|
|
score: this.score, |
|
|
score: progress, |
|
|
durationSeconds: duration |
|
|
durationSeconds: duration |
|
|
}).then(res => { |
|
|
}).then(res => { |
|
|
if (res.code == 200 && res.result) { |
|
|
if (res.code == 200) { |
|
|
this.result.rankNo = res.result.rankNo |
|
|
this.cleared = true |
|
|
|
|
|
this.modal = { show: true, title: '助推成功', sub: `${this.collegeName} 获得 ${progress} 点推进值,晚上21:30争夺学院荣誉。` } |
|
|
|
|
|
this.loadHome() |
|
|
} else { |
|
|
} else { |
|
|
this.tui.toast(res.message) |
|
|
this.tui.toast(res.message) |
|
|
} |
|
|
} |
|
|
this.result.show = true |
|
|
|
|
|
this.loadHome() |
|
|
|
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
closeResult() { |
|
|
failLevel() { |
|
|
this.result.show = false |
|
|
this.playing = false |
|
|
|
|
|
this.modal = { show: true, title: '槽位满了', sub: '本关失败,不消耗次数。换个顺序再推一次。' } |
|
|
|
|
|
}, |
|
|
|
|
|
resetLevel() { |
|
|
|
|
|
if (this.cleared) return |
|
|
|
|
|
this.playing = false |
|
|
|
|
|
this.buildLevel() |
|
|
|
|
|
}, |
|
|
|
|
|
closeModal() { |
|
|
|
|
|
this.modal.show = false |
|
|
|
|
|
}, |
|
|
|
|
|
shuffle(arr) { |
|
|
|
|
|
for (let i = arr.length - 1; i > 0; i--) { |
|
|
|
|
|
const j = Math.floor(Math.random() * (i + 1)) |
|
|
|
|
|
const t = arr[i] |
|
|
|
|
|
arr[i] = arr[j] |
|
|
|
|
|
arr[j] = t |
|
|
|
|
|
} |
|
|
|
|
|
return arr |
|
|
}, |
|
|
}, |
|
|
goBack() { |
|
|
goBack() { |
|
|
uni.navigateBack() |
|
|
uni.navigateBack() |
|
|
@ -550,29 +316,46 @@ |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<style lang="scss" scoped> |
|
|
<style lang="scss" scoped> |
|
|
.adv { min-height: 100vh; background: linear-gradient(155deg, #EAF8FF, #F3FFF4 55%, #FFF5D9); position: relative; overflow: hidden; } |
|
|
.boost { min-height: 100vh; background: linear-gradient(155deg, #F3FFF4 0%, #EAF8FF 46%, #FFF7DE 100%); color: #12342F; } |
|
|
.nav { position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 20; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 8rpx; box-sizing: content-box; } |
|
|
.nav { position: fixed; top: 0; left: 0; right: 0; height: 44px; z-index: 20; display: flex; align-items: flex-end; justify-content: center; padding-bottom: 8rpx; background: rgba(243,255,244,0.86); } |
|
|
.nav-back { position: absolute; left: 20rpx; bottom: 0; width: 70rpx; height: 44px; display: flex; align-items: center; justify-content: center; color: #12342F; font-size: 54rpx; } |
|
|
.nav-back { position: absolute; left: 20rpx; bottom: 0; width: 70rpx; height: 44px; display: flex; align-items: center; justify-content: center; color: #12342F; font-size: 54rpx; } |
|
|
.nav-title { color: #12342F; font-size: 34rpx; font-weight: 900; } |
|
|
.nav-title { color: #12342F; font-size: 34rpx; font-weight: 900; } |
|
|
.game { width: 100vw; height: 100vh; } |
|
|
.page { height: 100vh; box-sizing: border-box; padding-left: 24rpx; padding-right: 24rpx; } |
|
|
.hud { position: fixed; left: 22rpx; right: 22rpx; z-index: 10; display: flex; gap: 14rpx; } |
|
|
.hero, .rank-card, .game-card { margin-bottom: 24rpx; border-radius: 42rpx; background: rgba(255,255,255,0.82); padding: 30rpx; box-shadow: 0 16rpx 42rpx rgba(79,183,255,0.10); } |
|
|
.hud view { flex: 1; height: 78rpx; border-radius: 26rpx; background: rgba(255,255,255,0.76); display: flex; flex-direction: column; align-items: center; justify-content: center; box-shadow: 0 12rpx 30rpx rgba(79,183,255,0.12); } |
|
|
.hero-kicker { color: #FF9C42; font-size: 22rpx; font-weight: 900; letter-spacing: 2rpx; } |
|
|
.hud text:first-child { color: #22B889; font-size: 30rpx; font-weight: 900; } |
|
|
.hero-title { margin-top: 10rpx; font-size: 42rpx; font-weight: 900; } |
|
|
.hud text:last-child { color: #6B817D; font-size: 20rpx; } |
|
|
.hero-sub, .tips { margin-top: 16rpx; color: #6B817D; font-size: 24rpx; line-height: 1.55; } |
|
|
.panel { position: fixed; inset: 0; z-index: 30; background: rgba(18,52,47,0.18); backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; padding: 42rpx; } |
|
|
.rocket { margin-top: 22rpx; display: flex; align-items: center; gap: 16rpx; } |
|
|
.panel-card { width: 100%; padding: 42rpx; border-radius: 44rpx; background: linear-gradient(155deg, rgba(255,255,255,0.96), rgba(241,255,249,0.82)); box-shadow: 0 28rpx 70rpx rgba(53,214,166,0.2); text-align: center; } |
|
|
.rocket-body { font-size: 56rpx; } |
|
|
.panel-kicker { color: #FF9C42; font-size: 22rpx; font-weight: 900; letter-spacing: 2rpx; } |
|
|
.rocket-line { flex: 1; height: 18rpx; border-radius: 999rpx; background: #E6F2EF; overflow: hidden; } |
|
|
.panel-title { margin-top: 12rpx; color: #12342F; font-size: 40rpx; font-weight: 900; } |
|
|
.rocket-progress { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF, #FFB84D); } |
|
|
.panel-sub, .rule { margin-top: 14rpx; color: #6B817D; font-size: 25rpx; line-height: 1.5; } |
|
|
.section-head { display: flex; justify-content: space-between; align-items: center; font-size: 24rpx; color: #6B817D; } |
|
|
.event, .my { margin-top: 18rpx; padding: 18rpx; border-radius: 24rpx; background: rgba(255,255,255,0.72); color: #42635E; font-size: 24rpx; } |
|
|
.section-head text:first-child { color: #12342F; font-size: 31rpx; font-weight: 900; } |
|
|
.start-btn { margin-top: 30rpx; height: 86rpx; line-height: 86rpx; border-radius: 999rpx; background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #fff; font-size: 30rpx; font-weight: 900; box-shadow: 0 18rpx 40rpx rgba(53,214,166,0.22); } |
|
|
.college-row { margin-top: 18rpx; display: flex; align-items: center; gap: 16rpx; } |
|
|
.rank { position: fixed; right: 18rpx; top: 210rpx; width: 230rpx; max-height: 420rpx; z-index: 12; padding: 18rpx; border-radius: 28rpx; background: rgba(255,255,255,0.62); backdrop-filter: blur(6px); } |
|
|
.medal { width: 48rpx; height: 48rpx; line-height: 48rpx; border-radius: 50%; text-align: center; background: #FFF1C7; color: #B87932; font-weight: 900; } |
|
|
.rank-title { color: #12342F; font-size: 24rpx; font-weight: 900; margin-bottom: 10rpx; } |
|
|
.college-main { flex: 1; min-width: 0; } |
|
|
.rank-list { max-height: 350rpx; } |
|
|
.college-main text { font-size: 25rpx; font-weight: 800; } |
|
|
.rank-row { display: flex; align-items: center; margin-top: 10rpx; } |
|
|
.bar { margin-top: 10rpx; height: 12rpx; background: #EEF7F4; border-radius: 999rpx; overflow: hidden; } |
|
|
.rank-no { width: 28rpx; color: #22B889; font-size: 20rpx; font-weight: 900; } |
|
|
.bar view { height: 100%; border-radius: 999rpx; background: linear-gradient(90deg, #35D6A6, #4FB7FF); } |
|
|
.rank-avatar { width: 34rpx; height: 34rpx; border-radius: 50%; margin-right: 8rpx; } |
|
|
.score { width: 92rpx; text-align: right; color: #22B889; font-size: 28rpx; font-weight: 900; } |
|
|
.rank-mid { flex: 1; display: flex; flex-direction: column; min-width: 0; } |
|
|
.empty { margin-top: 18rpx; color: #8AA09C; font-size: 24rpx; } |
|
|
.rank-mid text { font-size: 18rpx; color: #42635E; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } |
|
|
.board { margin-top: 22rpx; height: 620rpx; border-radius: 36rpx; background: linear-gradient(155deg, rgba(234,248,255,0.9), rgba(255,248,222,0.72)); position: relative; overflow: hidden; } |
|
|
.rank-score { color: #12342F; font-size: 20rpx; font-weight: 900; } |
|
|
.tile { position: absolute; width: 76rpx; height: 76rpx; border-radius: 22rpx; background: #fff; display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 22rpx rgba(66,99,94,0.12); border: 2rpx solid rgba(255,255,255,0.9); transition: transform .12s ease, opacity .12s ease; } |
|
|
|
|
|
.tile text { font-size: 42rpx; } |
|
|
|
|
|
.tile.locked { opacity: .42; background: #F2F6F5; } |
|
|
|
|
|
.tile.selected { opacity: 0; transform: scale(.5); pointer-events: none; } |
|
|
|
|
|
.slot-wrap { margin-top: 24rpx; } |
|
|
|
|
|
.slot-title { color: #6B817D; font-size: 24rpx; margin-bottom: 14rpx; } |
|
|
|
|
|
.slots { display: flex; gap: 10rpx; } |
|
|
|
|
|
.slot { flex: 1; height: 74rpx; border-radius: 20rpx; background: rgba(255,255,255,0.8); border: 2rpx dashed rgba(53,214,166,0.28); display: flex; align-items: center; justify-content: center; } |
|
|
|
|
|
.slot text { font-size: 38rpx; } |
|
|
|
|
|
.actions { margin-top: 28rpx; display: flex; gap: 18rpx; } |
|
|
|
|
|
.sub-btn, .main-btn { flex: 1; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 999rpx; font-size: 28rpx; font-weight: 900; } |
|
|
|
|
|
.sub-btn { background: rgba(255,255,255,0.78); color: #42635E; } |
|
|
|
|
|
.main-btn { background: linear-gradient(135deg, #35D6A6, #4FB7FF); color: #fff; box-shadow: 0 14rpx 34rpx rgba(53,214,166,0.2); } |
|
|
|
|
|
.main-btn.disabled { opacity: .45; } |
|
|
|
|
|
.modal { position: fixed; inset: 0; z-index: 40; background: rgba(18,52,47,0.2); display: flex; align-items: center; justify-content: center; padding: 46rpx; } |
|
|
|
|
|
.modal-card { width: 100%; border-radius: 42rpx; background: #fff; padding: 42rpx; text-align: center; } |
|
|
|
|
|
.modal-title { font-size: 40rpx; font-weight: 900; } |
|
|
|
|
|
.modal-sub { margin: 18rpx 0 30rpx; color: #6B817D; font-size: 26rpx; line-height: 1.55; } |
|
|
</style> |
|
|
</style> |
|
|
|