新建项目
This commit is contained in:
278
pages/game/game.js
Normal file
278
pages/game/game.js
Normal file
@@ -0,0 +1,278 @@
|
||||
const service = require("../../services/game-service");
|
||||
const { formatCountdown, formatTime } = require("../../utils/format");
|
||||
|
||||
Page({
|
||||
data: {
|
||||
code: "",
|
||||
room: null,
|
||||
me: null,
|
||||
readyToRender: false,
|
||||
stageLabel: "",
|
||||
countdown: "00:00",
|
||||
survivors: 0,
|
||||
seekers: 0,
|
||||
captureCode: "",
|
||||
captureTargets: [],
|
||||
selectedTargetId: "",
|
||||
submittedCode: "",
|
||||
skillUsed: false,
|
||||
canManage: false,
|
||||
showDemoCode: false,
|
||||
captureSubmitting: false,
|
||||
errorMessage: "",
|
||||
eventViews: []
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.setData({ code: options.code || "" });
|
||||
this.tick();
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (!this.data.code) return;
|
||||
this.startTimer();
|
||||
this.tick();
|
||||
},
|
||||
|
||||
onHide() {
|
||||
this.stopTimer();
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
this.stopTimer();
|
||||
},
|
||||
|
||||
startTimer() {
|
||||
this.stopTimer();
|
||||
this.timer = setInterval(() => this.tick(), 1000);
|
||||
},
|
||||
|
||||
stopTimer() {
|
||||
if (!this.timer) return;
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
|
||||
tick() {
|
||||
let room = service.getRoom(this.data.code);
|
||||
if (!room) {
|
||||
this.stopTimer();
|
||||
this.setData({ readyToRender: false, errorMessage: "房间不存在或游戏数据已被清理" });
|
||||
return;
|
||||
}
|
||||
if (room.status === "preparing" && Date.now() >= room.phaseEndsAt) {
|
||||
room = service.beginPlaying(room.code);
|
||||
wx.vibrateLong();
|
||||
}
|
||||
if (room.status === "playing") {
|
||||
if (Date.now() >= room.phaseEndsAt) {
|
||||
service.finishGame(room.code, "hiders", null, "timeout");
|
||||
this.goResult();
|
||||
return;
|
||||
}
|
||||
if (room.nextClueAt && Date.now() >= room.nextClueAt) {
|
||||
service.revealClue(room.code);
|
||||
wx.vibrateShort({ type: "medium" });
|
||||
}
|
||||
}
|
||||
if (room.status === "finished") {
|
||||
this.goResult();
|
||||
return;
|
||||
}
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
refresh() {
|
||||
const room = service.getRoom(this.data.code);
|
||||
if (!room) {
|
||||
this.stopTimer();
|
||||
this.setData({ readyToRender: false, errorMessage: "房间不存在或游戏数据已被清理" });
|
||||
return;
|
||||
}
|
||||
const me =
|
||||
room.players.find((player) => player.id === service.USER_ID) ||
|
||||
room.players[0];
|
||||
const seconds = room.status === "paused"
|
||||
? Math.ceil(room.pausedRemainingMs / 1000)
|
||||
: Math.ceil((room.phaseEndsAt - Date.now()) / 1000);
|
||||
const survivors = room.players.filter(
|
||||
(player) => player.role === "hider" && player.status === "hiding"
|
||||
).length;
|
||||
const seekers = room.players.filter(
|
||||
(player) => player.role === "seeker"
|
||||
).length;
|
||||
const captureTargets = room.players.filter(
|
||||
(player) => player.role === "hider" && player.status === "hiding"
|
||||
);
|
||||
const selectedTargetId = captureTargets.some(
|
||||
(player) => player.id === this.data.selectedTargetId
|
||||
)
|
||||
? this.data.selectedTargetId
|
||||
: captureTargets.length ? captureTargets[0].id : "";
|
||||
const currentSkill = me.role === "seeker" ? "radar" : "silent";
|
||||
const captureCode = me.role === "seeker"
|
||||
? service.getCaptureCode(room.code, selectedTargetId)
|
||||
: service.getCaptureCode(room.code, me.id);
|
||||
this.setData({
|
||||
room,
|
||||
errorMessage: "",
|
||||
me,
|
||||
readyToRender: true,
|
||||
stageLabel: room.status === "preparing"
|
||||
? "躲藏准备"
|
||||
: room.status === "paused" ? "游戏已暂停" : "追逃进行中",
|
||||
countdown: formatCountdown(seconds),
|
||||
survivors,
|
||||
seekers,
|
||||
captureTargets,
|
||||
selectedTargetId,
|
||||
captureCode,
|
||||
skillUsed: Boolean(me.skillsUsed && me.skillsUsed[currentSkill] > 0),
|
||||
canManage: room.hostId === service.USER_ID || room.code === "731204",
|
||||
showDemoCode: Boolean(room.runtime && room.runtime.demoAccelerated),
|
||||
eventViews: room.events.slice(0, 6).map((event) => ({
|
||||
...event,
|
||||
time: formatTime(event.createdAt)
|
||||
}))
|
||||
});
|
||||
},
|
||||
|
||||
useSkill() {
|
||||
if (this.data.skillUsed) {
|
||||
wx.showToast({ title: "本局体验技能已使用", icon: "none" });
|
||||
return;
|
||||
}
|
||||
const skill = this.data.me.role === "seeker" ? "radar" : "silent";
|
||||
try {
|
||||
service.useSkill(this.data.code, skill);
|
||||
wx.vibrateShort({ type: "heavy" });
|
||||
this.refresh();
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
chooseCaptureTarget(event) {
|
||||
this.setData({
|
||||
selectedTargetId: event.currentTarget.dataset.id,
|
||||
submittedCode: ""
|
||||
});
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
inputCaptureCode(event) {
|
||||
this.setData({
|
||||
submittedCode: event.detail.value.replace(/\D/g, "").slice(0, 4)
|
||||
});
|
||||
},
|
||||
|
||||
fillDemoCode() {
|
||||
this.setData({ submittedCode: this.data.captureCode });
|
||||
},
|
||||
|
||||
submitCapture() {
|
||||
if (this.data.captureSubmitting) return;
|
||||
this.setData({ captureSubmitting: true });
|
||||
try {
|
||||
const room = service.capture(
|
||||
this.data.code,
|
||||
this.data.selectedTargetId,
|
||||
this.data.submittedCode
|
||||
);
|
||||
wx.vibrateLong();
|
||||
this.setData({ submittedCode: "" });
|
||||
if (room.status === "finished") {
|
||||
this.goResult();
|
||||
} else {
|
||||
this.setData({ captureSubmitting: false });
|
||||
wx.showToast({ title: "抓捕成功", icon: "success" });
|
||||
this.refresh();
|
||||
}
|
||||
} catch (error) {
|
||||
this.setData({ captureSubmitting: false });
|
||||
wx.showToast({ title: error.message, icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
showSafety() {
|
||||
wx.showActionSheet({
|
||||
itemList: ["我已安全,继续游戏", "联系房主", "退出并前往集合点"],
|
||||
success: (result) => {
|
||||
if (result.tapIndex === 1) {
|
||||
wx.showModal({
|
||||
title: "联系房主",
|
||||
content: "当前原型暂不提供即时通讯,请通过约定的电话或微信联系房主。",
|
||||
showCancel: false
|
||||
});
|
||||
}
|
||||
if (result.tapIndex === 2) {
|
||||
wx.showModal({
|
||||
title: "确认退出?",
|
||||
content: "请先确保自己处于安全位置,并前往约定集合点。",
|
||||
confirmText: "安全退出",
|
||||
confirmColor: "#d9554d",
|
||||
success: (modalResult) => {
|
||||
if (!modalResult.confirm) return;
|
||||
try {
|
||||
const room = service.quitGame(this.data.code);
|
||||
if (room.status === "finished") {
|
||||
this.goResult();
|
||||
} else {
|
||||
this.stopTimer();
|
||||
service.clearCurrentRoom();
|
||||
wx.reLaunch({ url: "/pages/home/home" });
|
||||
}
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: "none" });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
togglePause() {
|
||||
try {
|
||||
if (this.data.room.status === "paused") {
|
||||
service.resumeGame(this.data.code);
|
||||
} else {
|
||||
service.pauseGame(this.data.code);
|
||||
}
|
||||
this.refresh();
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: "none" });
|
||||
}
|
||||
},
|
||||
|
||||
finishEarly() {
|
||||
wx.showModal({
|
||||
title: "提前结束游戏?",
|
||||
content: "系统将根据当前存活情况立即生成本局战报。",
|
||||
confirmText: "结束游戏",
|
||||
confirmColor: "#d9554d",
|
||||
success: (result) => {
|
||||
if (!result.confirm) return;
|
||||
try {
|
||||
service.finishGame(this.data.code, "", service.USER_ID, "host_ended");
|
||||
this.goResult();
|
||||
} catch (error) {
|
||||
wx.showToast({ title: error.message, icon: "none" });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
goResult() {
|
||||
this.stopTimer();
|
||||
wx.redirectTo({
|
||||
url: `/pages/result/result?code=${this.data.code}`
|
||||
});
|
||||
},
|
||||
|
||||
backHome() {
|
||||
this.stopTimer();
|
||||
service.clearCurrentRoom();
|
||||
wx.reLaunch({ url: "/pages/home/home" });
|
||||
}
|
||||
});
|
||||
4
pages/game/game.json
Normal file
4
pages/game/game.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "游戏进行中",
|
||||
"disableScroll": false
|
||||
}
|
||||
137
pages/game/game.wxml
Normal file
137
pages/game/game.wxml
Normal file
@@ -0,0 +1,137 @@
|
||||
<view class="game-page" wx:if="{{readyToRender}}">
|
||||
<view class="game-head {{me.role === 'seeker' ? 'seeker-head' : 'hider-head'}}">
|
||||
<view class="row between">
|
||||
<view class="live-pill"><view class="live-dot"></view>{{stageLabel}}</view>
|
||||
<view class="safe-state">● 边界内</view>
|
||||
</view>
|
||||
<view class="role-label">你的身份</view>
|
||||
<view class="role-name">{{me.role === 'seeker' ? '寻找者' : '躲藏者'}}</view>
|
||||
<view class="role-task">
|
||||
{{me.role === 'seeker' ? '根据线索找到目标,用动态口令完成抓捕。' : '保持隐蔽,在倒计时结束前不要被发现。'}}
|
||||
</view>
|
||||
<view class="countdown">{{countdown}}</view>
|
||||
</view>
|
||||
|
||||
<view class="page game-content">
|
||||
<view class="stat-grid">
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{survivors}}</view>
|
||||
<view class="stat-label">仍在躲藏</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{seekers}}</view>
|
||||
<view class="stat-label">正在寻找</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-value">{{room.settings.radiusMeters}}m</view>
|
||||
<view class="stat-label">安全半径</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{room.status === 'preparing'}}" class="preparing-card">
|
||||
<view class="preparing-icon">◌</view>
|
||||
<view class="card-title">准备阶段</view>
|
||||
<view class="preparing-copy">
|
||||
{{me.role === 'seeker' ? '请留在起点,不要观察躲藏者的方向。倒计时结束后会震动提醒。' : '现在出发寻找安全藏身处,不要进入封闭、危险或私人区域。'}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{room.status === 'paused'}}" class="preparing-card">
|
||||
<view class="preparing-icon">Ⅱ</view>
|
||||
<view class="card-title">游戏暂停中</view>
|
||||
<view class="preparing-copy">请留在安全位置并等待房主继续游戏,暂停期间倒计时和线索计时均不会推进。</view>
|
||||
</view>
|
||||
|
||||
<block wx:else>
|
||||
<view wx:if="{{room.settings.skillsEnabled && (me.status === 'seeking' || me.status === 'hiding')}}">
|
||||
<view class="section-label">角色能力</view>
|
||||
<view class="skill-card">
|
||||
<view class="skill-icon">{{me.role === 'seeker' ? '⌁' : '◐'}}</view>
|
||||
<view class="skill-main">
|
||||
<view class="card-title">{{me.role === 'seeker' ? '区域雷达' : '静默信号'}}</view>
|
||||
<view class="skill-copy">
|
||||
{{me.role === 'seeker' ? '检测附近是否可能存在躲藏者,不显示精确方向。' : '跳过下一次周期性位置广播。'}}
|
||||
</view>
|
||||
</view>
|
||||
<button
|
||||
class="skill-button {{skillUsed ? 'used' : ''}}"
|
||||
disabled="{{skillUsed}}"
|
||||
bindtap="useSkill"
|
||||
>{{skillUsed ? '已使用' : '使用'}}</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{me.role === 'seeker' && me.status === 'seeking'}}">
|
||||
<view class="section-label">抓捕确认</view>
|
||||
<view class="capture-card">
|
||||
<view class="capture-title">选择你在线下找到的目标</view>
|
||||
<view class="target-list">
|
||||
<view
|
||||
wx:for="{{captureTargets}}"
|
||||
wx:key="id"
|
||||
class="target-chip {{selectedTargetId === item.id ? 'selected' : ''}}"
|
||||
data-id="{{item.id}}"
|
||||
bindtap="chooseCaptureTarget"
|
||||
>{{item.name}}</view>
|
||||
</view>
|
||||
<view class="capture-copy">请在安全距离内完成确认,禁止追逐中发生身体接触。</view>
|
||||
<input
|
||||
type="number"
|
||||
maxlength="4"
|
||||
value="{{submittedCode}}"
|
||||
placeholder="0000"
|
||||
bindinput="inputCaptureCode"
|
||||
/>
|
||||
<view wx:if="{{showDemoCode}}" class="demo-code" bindtap="fillDemoCode">
|
||||
原型演示:点击填入口令 {{captureCode}}
|
||||
</view>
|
||||
<button
|
||||
class="secondary-button capture-button"
|
||||
loading="{{captureSubmitting}}"
|
||||
disabled="{{captureSubmitting || !selectedTargetId || submittedCode.length !== 4}}"
|
||||
bindtap="submitCapture"
|
||||
>确认抓捕</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{me.role === 'hider' && me.status === 'hiding'}}">
|
||||
<view class="section-label">你的动态口令</view>
|
||||
<view class="token-card">
|
||||
<view class="token-label">被找到后,将口令展示给寻找者</view>
|
||||
<view class="token-code">{{captureCode}}</view>
|
||||
<view class="token-note">每 30 秒刷新 · 不要远程发送口令</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="preparing-card player-finished-card">
|
||||
<view class="card-title">你已结束躲藏</view>
|
||||
<view class="preparing-copy">经典模式下可以留在安全区域观战,请勿向其他玩家透露信息。</view>
|
||||
</view>
|
||||
|
||||
<view class="section-label">实时事件</view>
|
||||
<view class="event-list">
|
||||
<view wx:if="{{eventViews.length === 0}}" class="empty-inline">还没有游戏事件</view>
|
||||
<view wx:for="{{eventViews}}" wx:key="id" class="event-row">
|
||||
<view class="event-mark {{item.type}}"></view>
|
||||
<view class="event-message">{{item.message}}</view>
|
||||
<view class="event-time">{{item.time}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view class="safety-actions">
|
||||
<button class="danger-button" bindtap="showSafety">安全与退出</button>
|
||||
<button wx:if="{{canManage}}" class="ghost-button" bindtap="togglePause">
|
||||
{{room.status === 'paused' ? '继续游戏' : '暂停游戏'}}
|
||||
</button>
|
||||
<button wx:if="{{canManage}}" class="ghost-button" bindtap="finishEarly">提前结束</button>
|
||||
</view>
|
||||
</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>
|
||||
333
pages/game/game.wxss
Normal file
333
pages/game/game.wxss
Normal file
@@ -0,0 +1,333 @@
|
||||
.game-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f4ec;
|
||||
}
|
||||
|
||||
.game-head {
|
||||
padding: 34rpx 34rpx 46rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.seeker-head {
|
||||
background: #183f31;
|
||||
}
|
||||
|
||||
.hider-head {
|
||||
background: #914a22;
|
||||
}
|
||||
|
||||
.live-pill,
|
||||
.safe-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.live-pill {
|
||||
padding: 10rpx 16rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 99rpx;
|
||||
}
|
||||
|
||||
.live-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
background: #d9ef72;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.role-label {
|
||||
margin-top: 38rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.role-name {
|
||||
margin-top: 7rpx;
|
||||
font-size: 58rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.role-task {
|
||||
width: 75%;
|
||||
margin-top: 12rpx;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
font-size: 24rpx;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
margin-top: 34rpx;
|
||||
color: #d9ef72;
|
||||
font-size: 90rpx;
|
||||
font-weight: 900;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
.game-content {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 14rpx;
|
||||
margin-top: -24rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 24rpx 12rpx;
|
||||
background: #fffdf7;
|
||||
border-radius: 22rpx;
|
||||
box-shadow: 0 10rpx 30rpx rgba(35, 46, 40, 0.07);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 33rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin-top: 7rpx;
|
||||
color: #7c8580;
|
||||
font-size: 19rpx;
|
||||
}
|
||||
|
||||
.preparing-card {
|
||||
margin-top: 32rpx;
|
||||
padding: 48rpx 32rpx;
|
||||
background: #fffdf7;
|
||||
border-radius: 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preparing-icon {
|
||||
color: #ef8f4a;
|
||||
font-size: 90rpx;
|
||||
}
|
||||
|
||||
.preparing-copy {
|
||||
margin-top: 16rpx;
|
||||
color: #68726c;
|
||||
font-size: 25rpx;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.skill-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
padding: 24rpx;
|
||||
background: #fffdf7;
|
||||
border-radius: 26rpx;
|
||||
}
|
||||
|
||||
.skill-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
background: #d9ef72;
|
||||
border-radius: 22rpx;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.skill-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.skill-copy {
|
||||
margin-top: 7rpx;
|
||||
color: #68726c;
|
||||
font-size: 21rpx;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.skill-button {
|
||||
width: 120rpx;
|
||||
height: 64rpx;
|
||||
margin: 0;
|
||||
background: #1d6b4f;
|
||||
color: #fff;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.skill-button.used {
|
||||
background: #dfe4e0;
|
||||
color: #7c8580;
|
||||
}
|
||||
|
||||
.capture-card,
|
||||
.token-card {
|
||||
padding: 28rpx;
|
||||
background: #183f31;
|
||||
border-radius: 28rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.capture-title {
|
||||
font-size: 29rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.capture-copy,
|
||||
.token-note {
|
||||
margin-top: 9rpx;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
font-size: 21rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.target-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.target-chip {
|
||||
padding: 12rpx 20rpx;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.24);
|
||||
border-radius: 99rpx;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.target-chip.selected {
|
||||
background: #d9ef72;
|
||||
border-color: #d9ef72;
|
||||
color: #17231d;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.player-finished-card {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.capture-card input {
|
||||
height: 120rpx;
|
||||
margin-top: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20rpx;
|
||||
color: #d9ef72;
|
||||
font-size: 60rpx;
|
||||
font-weight: 900;
|
||||
letter-spacing: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-code {
|
||||
padding: 18rpx 0;
|
||||
color: #d9ef72;
|
||||
font-size: 22rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.capture-button {
|
||||
height: 80rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.token-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.token-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.token-code {
|
||||
margin: 20rpx 0;
|
||||
color: #d9ef72;
|
||||
font-size: 74rpx;
|
||||
font-weight: 900;
|
||||
letter-spacing: 18rpx;
|
||||
}
|
||||
|
||||
.event-list {
|
||||
overflow: hidden;
|
||||
background: #fffdf7;
|
||||
border-radius: 26rpx;
|
||||
}
|
||||
|
||||
.event-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 22rpx 24rpx;
|
||||
border-bottom: 2rpx solid rgba(23, 35, 29, 0.07);
|
||||
}
|
||||
|
||||
.event-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.event-mark {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
background: #7c8580;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.event-mark.clue {
|
||||
background: #ef8f4a;
|
||||
}
|
||||
|
||||
.event-mark.skill {
|
||||
background: #1d6b4f;
|
||||
}
|
||||
|
||||
.event-mark.capture {
|
||||
background: #d9554d;
|
||||
}
|
||||
|
||||
.event-message {
|
||||
flex: 1;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
color: #939b96;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.safety-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.safety-actions button {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.safety-actions button:nth-child(3) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 340px) {
|
||||
.role-task {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-size: 76rpx;
|
||||
}
|
||||
|
||||
.stat-grid {
|
||||
gap: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
Reference in New Issue
Block a user