新建项目

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

82
pages/join/join.js Normal file
View File

@@ -0,0 +1,82 @@
const service = require("../../services/game-service");
Page({
data: {
code: "",
name: "我",
avatar: "我",
preview: null,
previewHint: "",
canJoin: false,
submitting: false
},
onLoad(options) {
if (options.code) {
this.setData({ code: options.code });
this.previewRoom(options.code);
}
},
onCodeInput(event) {
const code = event.detail.value.replace(/\D/g, "").slice(0, 6);
this.setData({ code, preview: null, previewHint: "", canJoin: false });
if (code.length === 6) this.previewRoom(code);
},
onNameInput(event) {
const name = event.detail.value;
this.setData({ name, avatar: name.slice(0, 1) || "?" });
},
previewRoom(code) {
const preview = service.getRoom(code);
let previewHint = "";
let canJoin = false;
if (!preview) {
previewHint = "没有找到这个房间,请检查房间码";
} else if (preview.status === "finished") {
previewHint = "这场游戏已经结束";
} else if (preview.status === "cancelled") {
previewHint = "房间已被房主取消";
} else if (preview.status !== "waiting") {
previewHint = "游戏已经开始,暂时无法加入";
} else {
const alreadyJoined = preview.players.some(
(player) => player.id === service.USER_ID
);
canJoin = alreadyJoined || preview.players.length < preview.settings.maxPlayers;
if (!canJoin) previewHint = "房间人数已满";
}
this.setData({ preview, previewHint, canJoin });
},
useDemoCode() {
this.setData({ code: "731204" });
this.previewRoom("731204");
},
join() {
if (this.data.submitting) return;
if (this.data.code.length !== 6) {
wx.showToast({ title: "请输入六位房间码", icon: "none" });
return;
}
if (!this.data.name.trim()) {
wx.showToast({ title: "请输入游戏昵称", icon: "none" });
return;
}
if (!this.data.canJoin) {
wx.showToast({ title: this.data.previewHint || "当前不能加入房间", icon: "none" });
return;
}
this.setData({ submitting: true });
try {
const room = service.joinRoom(this.data.code, this.data.name.trim());
wx.redirectTo({ url: `/pages/lobby/lobby?code=${room.code}` });
} catch (error) {
this.setData({ submitting: false });
wx.showToast({ title: error.message, icon: "none" });
}
}
});

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

@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "加入游戏"
}

57
pages/join/join.wxml Normal file
View File

@@ -0,0 +1,57 @@
<view class="page join-page">
<view class="eyebrow">JOIN THE GAME</view>
<view class="title">找到你的队伍</view>
<view class="subtitle">向房主索取六位房间码,或直接打开好友分享的小程序卡片。</view>
<view class="code-card">
<view class="code-label">房间码</view>
<input
type="number"
maxlength="6"
value="{{code}}"
placeholder="000000"
focus
bindinput="onCodeInput"
/>
<view class="demo-link" bindtap="useDemoCode">没有房间?使用演示房间 731204</view>
</view>
<view wx:if="{{preview}}" class="card room-preview">
<view class="row between">
<view>
<view class="preview-label">找到房间</view>
<view class="card-title">{{preview.name}}</view>
</view>
<view class="pill">{{preview.players.length}} / {{preview.settings.maxPlayers}} 人</view>
</view>
<view class="divider"></view>
<view class="preview-meta">
<text>{{preview.settings.mode === 'infection' ? '感染模式' : '经典模式'}}</text>
<text>{{preview.settings.durationMinutes}} 分钟</text>
<text>{{preview.settings.radiusMeters}} 米范围</text>
</view>
</view>
<view wx:if="{{previewHint}}" class="preview-error">{{previewHint}}</view>
<view class="section-label">你的游戏昵称</view>
<view class="name-input">
<view class="name-avatar">{{avatar}}</view>
<input
maxlength="10"
value="{{name}}"
placeholder="输入昵称"
bindinput="onNameInput"
/>
</view>
<button
class="primary-button join-button"
loading="{{submitting}}"
disabled="{{submitting || !canJoin}}"
bindtap="join"
>
加入房间
</button>
<view class="privacy-tip">加入即表示你同意本局安全规则。精确位置不会直接展示给其他玩家。</view>
</view>

91
pages/join/join.wxss Normal file
View File

@@ -0,0 +1,91 @@
.code-card {
margin-top: 44rpx;
padding: 34rpx 30rpx;
background: #183f31;
border-radius: 32rpx;
color: #fff;
text-align: center;
}
.code-label {
color: rgba(255, 255, 255, 0.66);
font-size: 23rpx;
font-weight: 700;
}
.code-card input {
height: 130rpx;
color: #d9ef72;
font-size: 70rpx;
font-weight: 900;
letter-spacing: 16rpx;
}
.demo-link {
color: rgba(255, 255, 255, 0.7);
font-size: 23rpx;
text-decoration: underline;
}
.preview-label {
margin-bottom: 8rpx;
color: #1d6b4f;
font-size: 22rpx;
font-weight: 800;
}
.preview-meta {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
color: #68726c;
font-size: 23rpx;
}
.preview-error {
margin-top: 18rpx;
padding: 20rpx 24rpx;
background: #fff0ee;
border-radius: 20rpx;
color: #a63b35;
font-size: 23rpx;
line-height: 1.5;
}
.name-input {
display: flex;
align-items: center;
gap: 20rpx;
padding: 18rpx 24rpx;
background: #fffdf7;
border-radius: 24rpx;
}
.name-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 70rpx;
height: 70rpx;
background: #d9ef72;
border-radius: 50%;
font-weight: 900;
}
.name-input input {
flex: 1;
height: 70rpx;
font-size: 29rpx;
}
.join-button {
margin-top: 40rpx;
}
.privacy-tip {
padding: 22rpx;
color: #7c8580;
font-size: 22rpx;
line-height: 1.6;
text-align: center;
}