新建项目

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

116
pages/create/create.js Normal file
View File

@@ -0,0 +1,116 @@
const service = require("../../services/game-service");
const { GAME_MODES, DEFAULT_SETTINGS } = require("../../utils/constants");
Page({
data: {
name: "周末藏猫猫",
mode: DEFAULT_SETTINGS.mode,
modes: Object.values(GAME_MODES),
durationOptions: [15, 20, 30, 45, 60],
durationIndex: 2,
prepareOptions: [1, 3, 5, 8],
prepareIndex: 1,
maxPlayerOptions: [4, 6, 8, 10, 12, 20],
maxPlayerIndex: 2,
seekerOptions: [1, 2, 3, 4],
seekerIndex: 1,
clueOptions: [3, 5, 8, 10],
clueIndex: 1,
radiusOptions: [200, 300, 500, 800, 1000],
radiusIndex: 2,
skillsEnabled: true,
safetyAccepted: false,
submitting: false
},
updateName(event) {
this.setData({ name: event.detail.value });
},
chooseMode(event) {
this.setData({ mode: event.currentTarget.dataset.mode });
},
onDurationChange(event) {
this.setData({ durationIndex: Number(event.detail.value) });
},
onPrepareChange(event) {
this.setData({ prepareIndex: Number(event.detail.value) });
},
onMaxPlayerChange(event) {
const maxPlayerIndex = Number(event.detail.value);
const maxPlayers = this.data.maxPlayerOptions[maxPlayerIndex];
let seekerIndex = this.data.seekerIndex;
while (
seekerIndex > 0 &&
this.data.seekerOptions[seekerIndex] >= maxPlayers
) {
seekerIndex -= 1;
}
this.setData({ maxPlayerIndex, seekerIndex });
},
onSeekerChange(event) {
const seekerIndex = Number(event.detail.value);
const seekers = this.data.seekerOptions[seekerIndex];
const maxPlayers = this.data.maxPlayerOptions[this.data.maxPlayerIndex];
if (seekers >= maxPlayers) {
wx.showToast({ title: "寻找者必须少于人数上限", icon: "none" });
return;
}
this.setData({ seekerIndex });
},
onClueChange(event) {
this.setData({ clueIndex: Number(event.detail.value) });
},
onRadiusChange(event) {
this.setData({ radiusIndex: Number(event.detail.value) });
},
onSkillsChange(event) {
this.setData({ skillsEnabled: event.detail.value });
},
onSafetyChange(event) {
this.setData({ safetyAccepted: event.detail.value.length > 0 });
},
openRules() {
wx.navigateTo({ url: "/pages/rules/rules" });
},
submit() {
if (!this.data.name.trim()) {
wx.showToast({ title: "请填写房间名称", icon: "none" });
return;
}
if (!this.data.safetyAccepted) {
wx.showToast({ title: "请先确认安全规则", icon: "none" });
return;
}
this.setData({ submitting: true });
const data = this.data;
try {
const room = service.createRoom({
name: data.name.trim(),
mode: data.mode,
durationMinutes: data.durationOptions[data.durationIndex],
prepareMinutes: data.prepareOptions[data.prepareIndex],
maxPlayers: data.maxPlayerOptions[data.maxPlayerIndex],
seekerCount: data.seekerOptions[data.seekerIndex],
clueIntervalMinutes: data.clueOptions[data.clueIndex],
radiusMeters: data.radiusOptions[data.radiusIndex],
skillsEnabled: data.skillsEnabled
});
wx.redirectTo({ url: `/pages/lobby/lobby?code=${room.code}` });
} catch (error) {
this.setData({ submitting: false });
wx.showToast({ title: error.message, icon: "none" });
}
}
});

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

@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "创建游戏"
}

148
pages/create/create.wxml Normal file
View File

@@ -0,0 +1,148 @@
<view class="page">
<view class="eyebrow">CREATE A GAME</view>
<view class="title">设置这场追逃</view>
<view class="subtitle">先用推荐规则开局,之后可以在候场页继续调整。</view>
<view class="section-label">房间名称</view>
<view class="input-card">
<input
maxlength="16"
value="{{name}}"
placeholder="给这场游戏起个名字"
bindinput="updateName"
/>
<text>{{name.length}}/16</text>
</view>
<view class="section-label">游戏模式</view>
<view
wx:for="{{modes}}"
wx:key="id"
class="mode-card {{mode === item.id ? 'selected' : ''}}"
data-mode="{{item.id}}"
bindtap="chooseMode"
>
<view class="mode-icon">{{item.icon}}</view>
<view class="mode-main">
<view class="mode-name">{{item.name}}</view>
<view class="mode-description">{{item.description}}</view>
</view>
<view class="radio">{{mode === item.id ? '✓' : ''}}</view>
</view>
<view class="section-label">基础规则</view>
<view class="settings-card">
<picker
mode="selector"
range="{{durationOptions}}"
value="{{durationIndex}}"
bindchange="onDurationChange"
>
<view class="setting-row">
<view>
<view class="setting-name">游戏时长</view>
<view class="setting-hint">正式追逃阶段</view>
</view>
<view class="setting-value">{{durationOptions[durationIndex]}} 分钟 </view>
</view>
</picker>
<picker
mode="selector"
range="{{prepareOptions}}"
value="{{prepareIndex}}"
bindchange="onPrepareChange"
>
<view class="setting-row">
<view>
<view class="setting-name">躲藏准备</view>
<view class="setting-hint">寻找者原地等待</view>
</view>
<view class="setting-value">{{prepareOptions[prepareIndex]}} 分钟 </view>
</view>
</picker>
<picker
mode="selector"
range="{{maxPlayerOptions}}"
value="{{maxPlayerIndex}}"
bindchange="onMaxPlayerChange"
>
<view class="setting-row">
<view>
<view class="setting-name">人数上限</view>
<view class="setting-hint">至少 4 人开局</view>
</view>
<view class="setting-value">{{maxPlayerOptions[maxPlayerIndex]}} 人 </view>
</view>
</picker>
<picker
mode="selector"
range="{{seekerOptions}}"
value="{{seekerIndex}}"
bindchange="onSeekerChange"
>
<view class="setting-row">
<view>
<view class="setting-name">初始寻找者</view>
<view class="setting-hint">建议约占总人数 1/4</view>
</view>
<view class="setting-value">{{seekerOptions[seekerIndex]}} 人 </view>
</view>
</picker>
<picker
mode="selector"
range="{{clueOptions}}"
value="{{clueIndex}}"
bindchange="onClueChange"
>
<view class="setting-row">
<view>
<view class="setting-name">位置线索</view>
<view class="setting-hint">只广播模糊区域</view>
</view>
<view class="setting-value">每 {{clueOptions[clueIndex]}} 分钟 </view>
</view>
</picker>
<picker
mode="selector"
range="{{radiusOptions}}"
value="{{radiusIndex}}"
bindchange="onRadiusChange"
>
<view class="setting-row">
<view>
<view class="setting-name">活动半径</view>
<view class="setting-hint">原型暂以文字展示</view>
</view>
<view class="setting-value">{{radiusOptions[radiusIndex]}} 米 </view>
</view>
</picker>
<view class="setting-row no-border">
<view>
<view class="setting-name">启用角色技能</view>
<view class="setting-hint">雷达、静默等轻量能力</view>
</view>
<switch
color="#1d6b4f"
checked="{{skillsEnabled}}"
bindchange="onSkillsChange"
/>
</view>
</view>
<checkbox-group class="safety-check" bindchange="onSafetyChange">
<label>
<checkbox value="accepted" color="#1d6b4f" />
<text>我已确认场地安全,并同意</text>
<text class="link" catchtap="openRules">《户外游戏安全规则》</text>
</label>
</checkbox-group>
<button
class="primary-button"
loading="{{submitting}}"
disabled="{{submitting}}"
bindtap="submit"
>
创建房间
</button>
</view>

129
pages/create/create.wxss Normal file
View File

@@ -0,0 +1,129 @@
.input-card {
display: flex;
align-items: center;
padding: 10rpx 26rpx;
background: #fffdf7;
border: 2rpx solid rgba(23, 35, 29, 0.1);
border-radius: 24rpx;
}
.input-card input {
flex: 1;
height: 86rpx;
font-size: 30rpx;
}
.input-card text {
color: #8b938e;
font-size: 22rpx;
}
.mode-card {
display: flex;
align-items: center;
gap: 22rpx;
margin-bottom: 18rpx;
padding: 26rpx;
background: #fffdf7;
border: 3rpx solid transparent;
border-radius: 26rpx;
}
.mode-card.selected {
border-color: #1d6b4f;
background: #f2f8ef;
}
.mode-icon {
display: flex;
align-items: center;
justify-content: center;
width: 74rpx;
height: 74rpx;
background: #d9ef72;
border-radius: 22rpx;
font-size: 36rpx;
font-weight: 900;
}
.mode-main {
flex: 1;
}
.mode-name {
font-size: 29rpx;
font-weight: 800;
}
.mode-description {
margin-top: 8rpx;
color: #68726c;
font-size: 23rpx;
line-height: 1.5;
}
.radio {
display: flex;
align-items: center;
justify-content: center;
width: 42rpx;
height: 42rpx;
border: 2rpx solid #9ba39e;
border-radius: 50%;
color: #fff;
}
.selected .radio {
background: #1d6b4f;
border-color: #1d6b4f;
}
.settings-card {
padding: 0 26rpx;
background: #fffdf7;
border-radius: 28rpx;
}
.setting-row {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 120rpx;
border-bottom: 2rpx solid rgba(23, 35, 29, 0.07);
}
.setting-row.no-border {
border-bottom: none;
}
.setting-name {
font-size: 27rpx;
font-weight: 750;
}
.setting-hint {
margin-top: 7rpx;
color: #8b938e;
font-size: 22rpx;
}
.setting-value {
color: #1d6b4f;
font-size: 26rpx;
font-weight: 700;
}
.safety-check {
margin: 32rpx 4rpx;
color: #68726c;
font-size: 24rpx;
line-height: 1.7;
}
.safety-check checkbox {
transform: scale(0.8);
}
.link {
color: #1d6b4f;
}