新建项目

This commit is contained in:
2026-07-29 10:50:33 +08:00
parent bfd3841cbd
commit 4f69b859a8
49 changed files with 5565 additions and 0 deletions

98
pages/result/result.js Normal file
View File

@@ -0,0 +1,98 @@
const service = require("../../services/game-service");
const { formatCountdown } = require("../../utils/format");
Page({
data: {
code: "",
room: null,
winnerName: "",
winnerCopy: "",
ranking: [],
elapsedText: "00:00",
eventCount: 0,
errorMessage: "",
replaying: false
},
onLoad(options) {
const code = options.code || "";
const room = service.getRoom(code);
if (!room) {
this.setData({ errorMessage: "战报不存在或本地数据已被清理" });
return;
}
const hidersWin = room.winner === "hiders";
const snapshot = room.resultSnapshot;
const sourcePlayers = snapshot
? snapshot.players
: room.players.map((player) => ({
...player,
initialRole: player.initialRole || player.role,
finalRole: player.role,
survivedSeconds: 0
}));
const ranking = [...sourcePlayers]
.sort((a, b) => {
if (b.captures !== a.captures) return b.captures - a.captures;
if (b.survivedSeconds !== a.survivedSeconds) {
return b.survivedSeconds - a.survivedSeconds;
}
if (a.joinedAt !== b.joinedAt) return a.joinedAt - b.joinedAt;
return a.id.localeCompare(b.id);
})
.map((player, index) => ({
...player,
rank: index + 1,
roleText: player.initialRole === "hider" && player.finalRole === "seeker"
? "躲藏者→寻找者"
: player.initialRole === "seeker" ? "寻找者" : "躲藏者",
resultText:
player.captures > 0
? `完成 ${player.captures} 次抓捕`
: player.caughtAt
? "勇敢参与追逃"
: player.initialRole === "hider" ? "坚持到游戏结束" : "持续追踪目标"
}));
this.setData({
code,
room,
errorMessage: "",
winnerName: hidersWin ? "躲藏者胜利" : "寻找者胜利",
winnerCopy: hidersWin
? "有人成功坚持到了倒计时结束。"
: "所有躲藏者都已被找到。",
ranking,
elapsedText: formatCountdown(snapshot ? snapshot.elapsedSeconds : 0),
eventCount: snapshot ? snapshot.eventCount : room.events.length
});
},
replay() {
if (this.data.replaying) return;
this.setData({ replaying: true });
try {
service.resetRoom(this.data.code);
wx.redirectTo({
url: `/pages/lobby/lobby?code=${this.data.code}`
});
} catch (error) {
this.setData({ replaying: false });
wx.showToast({ title: error.message, icon: "none" });
}
},
backHome() {
service.clearCurrentRoom();
wx.reLaunch({ url: "/pages/home/home" });
},
onShareAppMessage() {
if (!this.data.room) {
return { title: "藏猫猫计划", path: "/pages/home/home" };
}
return {
title: `${this.data.room.name}${this.data.winnerName}`,
path: "/pages/home/home"
};
}
});

3
pages/result/result.json Normal file
View File

@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "本局战报"
}

65
pages/result/result.wxml Normal file
View File

@@ -0,0 +1,65 @@
<view class="page result-page" wx:if="{{room}}">
<view class="result-hero">
<view class="confetti confetti-a"></view>
<view class="confetti confetti-b"></view>
<view class="trophy">★</view>
<view class="result-eyebrow">GAME COMPLETE</view>
<view class="winner">{{winnerName}}</view>
<view class="winner-copy">{{winnerCopy}}</view>
<view class="result-meta">{{room.name}} · 房间 {{room.code}}</view>
</view>
<view class="highlight-grid">
<view class="highlight">
<view class="highlight-value">{{room.players.length}}</view>
<view class="highlight-label">参与玩家</view>
</view>
<view class="highlight">
<view class="highlight-value">{{elapsedText}}</view>
<view class="highlight-label">实际用时</view>
</view>
<view class="highlight">
<view class="highlight-value">{{eventCount}}</view>
<view class="highlight-label">关键事件</view>
</view>
</view>
<view class="section-label">本局表现</view>
<view class="ranking-list">
<view wx:for="{{ranking}}" wx:key="id" class="ranking-row">
<view class="rank {{item.rank <= 3 ? 'top' : ''}}">{{item.rank}}</view>
<view class="rank-avatar">{{item.avatar}}</view>
<view class="rank-main">
<view class="rank-name">{{item.name}}</view>
<view class="rank-copy">{{item.resultText}}</view>
</view>
<view class="rank-role">{{item.roleText}}</view>
</view>
</view>
<view class="badge-card">
<view class="badge-icon">↗</view>
<view>
<view class="badge-title">本局称号 · 城市追踪者</view>
<view class="badge-copy">完成了第一场真实世界追逃。下一局试试更大的场地吧。</view>
</view>
</view>
<view class="button-stack">
<button class="secondary-button" open-type="share">分享本局战报</button>
<button
class="primary-button"
loading="{{replaying}}"
disabled="{{replaying}}"
bindtap="replay"
>按原规则再来一局</button>
<button class="ghost-button" bindtap="backHome">返回首页</button>
</view>
</view>
<view class="state-page" wx:elif="{{errorMessage}}">
<view class="state-icon">!</view>
<view class="state-title">战报已不可用</view>
<view class="state-copy">{{errorMessage}}</view>
<button class="primary-button state-action" bindtap="backHome">返回首页</button>
</view>

200
pages/result/result.wxss Normal file
View File

@@ -0,0 +1,200 @@
.result-hero {
position: relative;
overflow: hidden;
padding: 50rpx 30rpx;
background: #183f31;
border-radius: 36rpx;
color: #fff;
text-align: center;
}
.trophy {
display: flex;
align-items: center;
justify-content: center;
width: 100rpx;
height: 100rpx;
margin: 0 auto 24rpx;
background: #d9ef72;
border-radius: 32rpx;
color: #17231d;
font-size: 54rpx;
}
.result-eyebrow {
color: rgba(255, 255, 255, 0.58);
font-size: 20rpx;
font-weight: 800;
letter-spacing: 4rpx;
}
.winner {
margin-top: 12rpx;
font-size: 52rpx;
font-weight: 900;
}
.winner-copy {
margin-top: 12rpx;
color: rgba(255, 255, 255, 0.72);
font-size: 25rpx;
}
.result-meta {
margin-top: 32rpx;
padding-top: 24rpx;
border-top: 2rpx solid rgba(255, 255, 255, 0.12);
color: #d9ef72;
font-size: 22rpx;
}
.confetti {
position: absolute;
width: 20rpx;
height: 70rpx;
background: #ef8f4a;
transform: rotate(28deg);
}
.confetti-a {
top: 70rpx;
left: 45rpx;
}
.confetti-b {
top: 120rpx;
right: 50rpx;
height: 48rpx;
background: #d9ef72;
transform: rotate(-35deg);
}
.highlight-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
margin-top: 20rpx;
}
.highlight {
padding: 24rpx 10rpx;
background: #fffdf7;
border-radius: 22rpx;
text-align: center;
}
.highlight-value {
font-size: 38rpx;
font-weight: 900;
}
.highlight-label {
margin-top: 5rpx;
color: #7c8580;
font-size: 20rpx;
}
.ranking-list {
overflow: hidden;
background: #fffdf7;
border-radius: 28rpx;
}
.ranking-row {
display: flex;
align-items: center;
gap: 16rpx;
padding: 22rpx 20rpx;
border-bottom: 2rpx solid rgba(23, 35, 29, 0.07);
}
.ranking-row:last-child {
border-bottom: none;
}
.rank {
width: 32rpx;
color: #8b938e;
font-size: 23rpx;
font-weight: 900;
text-align: center;
}
.rank.top {
color: #ef8f4a;
}
.rank-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 62rpx;
height: 62rpx;
background: #e7eee8;
border-radius: 20rpx;
font-size: 23rpx;
font-weight: 800;
}
.rank-main {
flex: 1;
min-width: 0;
}
.rank-name {
overflow: hidden;
font-size: 26rpx;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.rank-copy {
margin-top: 5rpx;
color: #7c8580;
font-size: 20rpx;
}
.rank-role {
flex: 0 0 auto;
max-width: 150rpx;
color: #1d6b4f;
font-size: 21rpx;
font-weight: 700;
text-align: right;
}
.badge-card {
display: flex;
gap: 20rpx;
margin-top: 26rpx;
padding: 26rpx;
background: #fff0df;
border-radius: 26rpx;
}
.badge-icon {
display: flex;
flex: 0 0 auto;
align-items: center;
justify-content: center;
width: 70rpx;
height: 70rpx;
background: #ef8f4a;
border-radius: 22rpx;
color: #fff;
font-size: 36rpx;
}
.badge-title {
color: #813f16;
font-size: 26rpx;
font-weight: 800;
}
.badge-copy {
margin-top: 8rpx;
color: #9c531e;
font-size: 21rpx;
line-height: 1.5;
}