Compare commits
9 Commits
e28424456f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dcfdeda68 | |||
| a6becf67ff | |||
| 8c66ef78c6 | |||
| 29a6f46a0d | |||
| 895be17f7c | |||
| 7c8fd9395b | |||
| 972d817629 | |||
| 31140c7ae2 | |||
| 542ee5f031 |
116
doc.md
Normal file
116
doc.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 微信小程序多人协作分支管理规范
|
||||
|
||||
## 一、分支结构
|
||||
|
||||
```
|
||||
main (主分支/生产环境)
|
||||
└── test (测试分支)
|
||||
└── feature/xxx (个人开发分支)
|
||||
```
|
||||
|
||||
| 分支 | 用途 | 稳定性 |
|
||||
|------|------|--------|
|
||||
| main | 生产环境代码 | 最高,仅接受测试通过的代码合并 |
|
||||
| test | 测试环境,用于体验版发布 | 中,需验证后合并到 main |
|
||||
| feature/xxx | 个人开发分支 | 低,按需命名,如 `feature/user-center` |
|
||||
|
||||
---
|
||||
|
||||
## 二、开发流程
|
||||
|
||||
### 1. 开始开发
|
||||
|
||||
```bash
|
||||
# 确保本地 main 最新
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# 从 main 创建自己的开发分支
|
||||
git checkout -b feature/your-name-work
|
||||
```
|
||||
|
||||
### 2. 开发阶段
|
||||
|
||||
- 在个人分支上开发功能
|
||||
- 频繁提交,保持原子性提交
|
||||
- 定期 `git pull origin main` 同步主线变更,避免合并冲突累积
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: 完成xxx功能"
|
||||
```
|
||||
|
||||
### 3. 合并到 test 分支
|
||||
|
||||
```bash
|
||||
# 切换到 test
|
||||
git checkout test
|
||||
git pull origin test
|
||||
|
||||
# 合并个人分支
|
||||
git merge feature/your-name-work
|
||||
|
||||
# 推送 test 分支
|
||||
git push origin test
|
||||
```
|
||||
|
||||
### 4. 打包上传体验版
|
||||
|
||||
```bash
|
||||
# 执行打包
|
||||
npm run build
|
||||
```
|
||||
|
||||
打包完成后:
|
||||
|
||||
1. 打开 **微信开发者工具**
|
||||
2. 导入项目,选择 `dist/build/mp-weixin` 目录
|
||||
3. 在开发者工具中点击 **上传**
|
||||
4. 登录 [微信公众平台](https://mp.weixin.qq.com)
|
||||
5. 进入 **管理->版本管理**
|
||||
6. 找到刚上传的版本,点击 **选为体验版**
|
||||
|
||||
---
|
||||
|
||||
## 三、合并到 main 分支
|
||||
|
||||
当 test 分支验证通过后,将其合并到 main:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
git merge origin/test
|
||||
|
||||
git push origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、冲突处理
|
||||
|
||||
合并时如有冲突,在个人分支解决后再合并:
|
||||
|
||||
```bash
|
||||
git checkout feature/your-name-work
|
||||
git merge main
|
||||
# 解决冲突后
|
||||
git add .
|
||||
git commit -m "merge: 解决与main的冲突"
|
||||
git push origin feature/your-name-work
|
||||
|
||||
# 重新合并到 test
|
||||
git checkout test
|
||||
git merge feature/your-name-work
|
||||
git push origin test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、注意事项
|
||||
|
||||
1. **禁止直接向 main 和 test 分支提交代码**,必须通过合并
|
||||
2. **每次合并前先拉取最新代码**,避免覆盖他人改动
|
||||
3. **体验版发布前确认代码已提交**,避免遗漏
|
||||
4. **开发分支命名建议**:`feature/姓名-功能名`,如 `feature/zhangsan-login`
|
||||
5. **删除已合并的开发分支**:`git branch -d feature/your-name-work`
|
||||
@@ -362,6 +362,7 @@ class AudioManager {
|
||||
}
|
||||
this.allowPlayMap.set(key, false);
|
||||
this.onAudioEnded(key);
|
||||
uni.$emit('audioEnded', key);
|
||||
});
|
||||
|
||||
audio.onStop(() => {
|
||||
|
||||
@@ -27,7 +27,7 @@ defineProps({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 14rpx;
|
||||
width: clac(100% - 30px);
|
||||
}
|
||||
.container .shooter2 {
|
||||
|
||||
@@ -6,7 +6,7 @@ import Avatar from "@/components/Avatar.vue";
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
const store = useStore();
|
||||
const { user } = storeToRefs(store);
|
||||
const { user, game } = storeToRefs(store);
|
||||
|
||||
const currentPage = computed(() => {
|
||||
const pages = getCurrentPages();
|
||||
@@ -59,6 +59,7 @@ const loading = ref(false);
|
||||
const pointBook = ref(null);
|
||||
const showProgress = ref(false);
|
||||
const heat = ref(0);
|
||||
|
||||
const updateLoading = (value) => {
|
||||
loading.value = value;
|
||||
};
|
||||
@@ -172,6 +173,16 @@ onBeforeUnmount(() => {
|
||||
<view v-if="showProgress" class="battle-progress">
|
||||
<HeaderProgress />
|
||||
</view>
|
||||
<!-- 对战房间:整个胶囊为分享按钮,房号从 Store 读取 -->
|
||||
<button
|
||||
v-if="currentPage === 'pages/battle-room' && game.roomNumber"
|
||||
open-type="share"
|
||||
hover-class="none"
|
||||
class="battle-room-number"
|
||||
>
|
||||
<text class="battle-room-number__text">房号: {{ game.roomNumber }}</text>
|
||||
<image src="../static/share2.png" mode="widthFix" class="battle-room-number__icon" />
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -259,4 +270,37 @@ onBeforeUnmount(() => {
|
||||
margin: 0 20rpx;
|
||||
max-width: 300rpx;
|
||||
}
|
||||
/* 对战房间:整个胶囊作为分享按钮,靠右对齐 */
|
||||
.battle-room-number {
|
||||
margin-left: auto;
|
||||
margin-right: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 240rpx;
|
||||
height: 64rpx;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 96rpx;
|
||||
border: 1rpx solid #5b5758;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
/* 重置 button 默认边框 */
|
||||
.battle-room-number::after {
|
||||
border: none;
|
||||
}
|
||||
.battle-room-number__text {
|
||||
width: 156rpx;
|
||||
height: 28rpx;
|
||||
font-weight: 400;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 28rpx;
|
||||
}
|
||||
.battle-room-number__icon {
|
||||
width: 25rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -139,7 +139,7 @@ async function onReceiveMessage(msg) {
|
||||
halfTime.value = false;
|
||||
audioManager.play("比赛开始");
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
|
||||
audioManager.play("比赛结束");
|
||||
audioManager.play("比赛结束", false);
|
||||
} else if (msg.type === MESSAGETYPESV2.ShootResult) {
|
||||
let arrow = {};
|
||||
if (msg.details && Array.isArray(msg.details)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import { RoundGoldImages } from "@/constants";
|
||||
import {ref, watch, onMounted, onBeforeUnmount} from "vue";
|
||||
import {RoundGoldImages} from "@/constants";
|
||||
|
||||
const props = defineProps({
|
||||
tips: {
|
||||
type: String,
|
||||
@@ -19,13 +20,26 @@ const props = defineProps({
|
||||
const barColor = ref("");
|
||||
const remain = ref(15);
|
||||
const timer = ref(null);
|
||||
const laoding = ref(false);
|
||||
const loading = ref(false);
|
||||
const transitionStyle = ref("all 1s linear");
|
||||
const currentTeam = ref(null);
|
||||
|
||||
const updateRemain = (value) => {
|
||||
if (timer.value) clearInterval(timer.value);
|
||||
const newVal = Math.round(value);
|
||||
|
||||
if (value.stop) {
|
||||
if (timer.value) clearInterval(timer.value);
|
||||
return
|
||||
}
|
||||
loading.value = false;
|
||||
currentTeam.value = value.team
|
||||
if (value.team === 'red')
|
||||
barColor.value = "linear-gradient( 180deg, #FFA0A0 0%, #FF6060 100%)";
|
||||
if (value.team === 'blue')
|
||||
barColor.value = "linear-gradient( 180deg, #9AB3FF 0%, #4288FF 100%)";
|
||||
if (value.reset) {
|
||||
remain.value = value.value;
|
||||
return;
|
||||
}
|
||||
const newVal = Math.round(value.value);
|
||||
// 如果剩余时间增加(如轮次切换重置),瞬间变化无动画;否则保持动画
|
||||
if (newVal >= remain.value) {
|
||||
transitionStyle.value = "none";
|
||||
@@ -36,28 +50,21 @@ const updateRemain = (value) => {
|
||||
} else {
|
||||
remain.value = newVal;
|
||||
}
|
||||
|
||||
|
||||
timer.value = setInterval(() => {
|
||||
laoding.value = remain.value === 0;
|
||||
loading.value = remain.value === 0;
|
||||
if (remain.value > 0) remain.value--;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.tips,
|
||||
(newVal) => {
|
||||
if (newVal.includes("红队"))
|
||||
barColor.value = "linear-gradient( 180deg, #FFA0A0 0%, #FF6060 100%)";
|
||||
if (newVal.includes("蓝队"))
|
||||
barColor.value = "linear-gradient( 180deg, #9AB3FF 0%, #4288FF 100%)";
|
||||
if (newVal.includes("重回")) return;
|
||||
if (newVal.includes("红队") || newVal.includes("蓝队")) {
|
||||
updateRemain(props.total);
|
||||
() => props.tips,
|
||||
(newVal) => {
|
||||
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
@@ -72,21 +79,21 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<view class="container">
|
||||
<image :src="RoundGoldImages[props.currentRound]" mode="widthFix" />
|
||||
<image :src="RoundGoldImages[props.currentRound]" mode="widthFix"/>
|
||||
<view
|
||||
:style="{
|
||||
justifyContent: tips.includes('红队') ? 'flex-end' : 'flex-start',
|
||||
:style="{
|
||||
justifyContent: currentTeam==='red' ? 'flex-end' : 'flex-start',
|
||||
}"
|
||||
>
|
||||
<view
|
||||
:style="{
|
||||
:style="{
|
||||
width: `${(remain / total) * 100}%`,
|
||||
background: barColor,
|
||||
right: tips.includes('红队') ? 0 : 'unset',
|
||||
right: currentTeam==='red' ? 0 : 'unset',
|
||||
transition: transitionStyle,
|
||||
}"
|
||||
/>
|
||||
<text v-if="!laoding">剩余{{ remain }}秒</text>
|
||||
<text v-if="!loading">剩余{{ remain }}秒</text>
|
||||
<text v-else>···</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -99,11 +106,13 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container > image {
|
||||
width: 380rpx;
|
||||
height: 80rpx;
|
||||
transform: translateY(18rpx);
|
||||
}
|
||||
|
||||
.container > view:last-child {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
@@ -115,10 +124,12 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container > view:last-child > view {
|
||||
height: 24rpx;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.container > view:last-child > text {
|
||||
font-size: 18rpx;
|
||||
color: #fff;
|
||||
|
||||
@@ -6,6 +6,7 @@ import PlayerSeats from "@/components/PlayerSeats.vue";
|
||||
import GuideTwo from "@/components/GuideTwo.vue";
|
||||
import SButton from "@/components/SButton.vue";
|
||||
import Avatar from "@/components/Avatar.vue";
|
||||
import ScreenHint from "@/components/ScreenHint.vue";
|
||||
import {
|
||||
getRoomAPI,
|
||||
destroyRoomAPI,
|
||||
@@ -29,11 +30,27 @@ const players = ref([]);
|
||||
const blueTeam = ref([]);
|
||||
const redTeam = ref([]);
|
||||
|
||||
/**
|
||||
* 根据对战类型和房间人数动态生成页面标题
|
||||
* battleType=1: 组队对战;count 2/4/6 分别对应 1v1/2v2/3v3
|
||||
* battleType=2: 多人乱斗
|
||||
*/
|
||||
const battleTitle = computed(() => {
|
||||
if (!room.value.battleType) return "好友约战";
|
||||
if (room.value.battleType === 2) return "多人乱斗";
|
||||
const half = room.value.count / 2;
|
||||
return `${half}v${half}对抗赛`;
|
||||
});
|
||||
|
||||
const ready = ref(false);
|
||||
const allReady = ref(false);
|
||||
const timer = ref(null);
|
||||
const goBattle = ref(false);
|
||||
|
||||
/**
|
||||
* 从服务端刷新当前房间数据,更新成员列表、准备状态等信息
|
||||
* 仅在 roomNumber 有效且房间未开始时执行
|
||||
*/
|
||||
async function refreshRoomData() {
|
||||
if (!roomNumber.value) return;
|
||||
const result = await getRoomAPI(roomNumber.value);
|
||||
@@ -184,8 +201,32 @@ const exitRoom = async () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const removePlayer = async (player) => {
|
||||
await kickPlayerAPI(roomNumber.value, player.id);
|
||||
/** 待确认踢出的玩家信息 */
|
||||
const playerToKick = ref(null);
|
||||
/** 控制踢出确认弹窗的显示状态 */
|
||||
const showKickConfirm = ref(false);
|
||||
|
||||
/**
|
||||
* 点击踢出按钮,弹出二次确认弹窗
|
||||
* @param {object} player - 被踢的玩家信息
|
||||
*/
|
||||
const removePlayer = (player) => {
|
||||
playerToKick.value = player;
|
||||
showKickConfirm.value = true;
|
||||
};
|
||||
|
||||
/** 确认踢出:调用 API 并关闭弹窗 */
|
||||
const confirmKick = async () => {
|
||||
if (!playerToKick.value) return;
|
||||
await kickPlayerAPI(roomNumber.value, playerToKick.value.id);
|
||||
showKickConfirm.value = false;
|
||||
playerToKick.value = null;
|
||||
};
|
||||
|
||||
/** 取消踢出:关闭弹窗 */
|
||||
const cancelKick = () => {
|
||||
showKickConfirm.value = false;
|
||||
playerToKick.value = null;
|
||||
};
|
||||
|
||||
const canClick = computed(() => {
|
||||
@@ -208,15 +249,32 @@ onShareAppMessage(() => {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* onShow 生命周期:页面显示时刷新房间数据
|
||||
* 注:uni-app 中 onShow 可能早于 onLoad 执行,此时 roomNumber 尚未赋值,
|
||||
* refreshRoomData 会提前 return。
|
||||
*/
|
||||
onShow(() => {
|
||||
goBattle.value = false;
|
||||
refreshRoomData();
|
||||
});
|
||||
|
||||
/**
|
||||
* 页面加载时解析路由参数,初始化房间号
|
||||
* - 存入本地 ref(供页面内部逻辑使用)
|
||||
* - 同步到 Pinia Store(供 Header 组件展示房号胶囊)
|
||||
*/
|
||||
onLoad(async (options) => {
|
||||
if (options.roomNumber) roomNumber.value = options.roomNumber;
|
||||
if (options.roomNumber) {
|
||||
roomNumber.value = options.roomNumber;
|
||||
store.updateRoomNumber(options.roomNumber);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 组件挂载后:保持屏幕常亮、注册 WebSocket 消息监听
|
||||
* 房间号已通过 onLoad 同步到 Store,Header 从 Store 直接读取,无需额外通知
|
||||
*/
|
||||
onMounted(() => {
|
||||
uni.setKeepScreenOn({
|
||||
keepScreenOn: true,
|
||||
@@ -236,7 +294,7 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container :title="`好友约战 - ${roomNumber}`">
|
||||
<Container :title="battleTitle">
|
||||
<view class="standby-phase">
|
||||
<GuideTwo>
|
||||
<view class="battle-guide">
|
||||
@@ -247,7 +305,6 @@ onBeforeUnmount(() => {
|
||||
}}</text>
|
||||
<text v-if="room.battleType === 2">大乱斗即将开始! </text>
|
||||
</view>
|
||||
<!-- <button hover-class="none" open-type="share">邀请</button> -->
|
||||
</view>
|
||||
</GuideTwo>
|
||||
<view v-if="room.battleType === 1 && room.count === 2" class="team-mode">
|
||||
@@ -343,6 +400,20 @@ onBeforeUnmount(() => {
|
||||
</view>
|
||||
</view>
|
||||
</Container>
|
||||
<!-- 踢出玩家二次确认弹窗(不传 onClose,屏蔽 X 关闭按钮) -->
|
||||
<ScreenHint :show="showKickConfirm">
|
||||
<view class="kick-confirm">
|
||||
<!-- 弹窗标题,玩家名字高亮显示 -->
|
||||
<text class="kick-confirm__title">
|
||||
是否把「<text style="color: #F7D550">{{ playerToKick && playerToKick.name }}</text>」移出房间?
|
||||
</text>
|
||||
<!-- 按钮区:确认在左,取消在右 -->
|
||||
<view class="kick-confirm__btns">
|
||||
<button hover-class="none" class="kick-confirm__btn kick-confirm__btn--confirm" @click="confirmKick">确定</button>
|
||||
<button hover-class="none" class="kick-confirm__btn kick-confirm__btn--cancel" @click="cancelKick">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</ScreenHint>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -380,7 +451,7 @@ onBeforeUnmount(() => {
|
||||
.team-mode {
|
||||
width: calc(100vw - 30px);
|
||||
height: 125vw;
|
||||
margin: 15px;
|
||||
margin: 0 15px 15px 15px;
|
||||
}
|
||||
|
||||
.team-mode>image:first-child {
|
||||
@@ -445,6 +516,66 @@ onBeforeUnmount(() => {
|
||||
color: #fed847 !important;
|
||||
}
|
||||
|
||||
/* 踢出确认弹窗内容 */
|
||||
.kick-confirm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 30rpx;
|
||||
color: #fff;
|
||||
}
|
||||
.kick-confirm__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
/* 标题与按钮区间距 */
|
||||
margin-bottom: 58rpx;
|
||||
}
|
||||
.kick-confirm__btns {
|
||||
display: flex;
|
||||
/* 两个按钮间距 */
|
||||
gap: 16rpx;
|
||||
}
|
||||
/* 按钮公共基础样式:固定宽高与圆角 */
|
||||
.kick-confirm__btn {
|
||||
font-size: 28rpx;
|
||||
width: 232rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 44rpx;
|
||||
line-height: 70rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* 取消按钮:白色半透明背景(用 rgba 避免 opacity 平铺到文字) */
|
||||
.kick-confirm__btn--cancel {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
.kick-confirm__btn--cancel::after {
|
||||
border: none;
|
||||
}
|
||||
/* 确认按钮:黄色实心背景 */
|
||||
.kick-confirm__btn--confirm {
|
||||
background: #FED847;
|
||||
border: none;
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
.kick-confirm__btn--confirm::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.remove-player {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
|
||||
@@ -73,10 +73,10 @@ const onSignin = () => {
|
||||
showSignin.value = false;
|
||||
};
|
||||
|
||||
/** 跳转到我的战绩页面 */
|
||||
/** 跳转到我的战绩页面,默认展示「好友约战」tab */
|
||||
const goMyRecord = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/my-growth',
|
||||
url: '/pages/my-growth?tab=1',
|
||||
});
|
||||
};
|
||||
onShow(async () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import Avatar from "@/components/Avatar.vue";
|
||||
import BowData from "@/components/BowData.vue";
|
||||
@@ -56,6 +57,17 @@ const getName = (battle) => {
|
||||
if (battle.mode <= 3) return `${battle.mode}V${battle.mode}`;
|
||||
return battle.mode + "人大乱斗";
|
||||
};
|
||||
|
||||
/**
|
||||
* 支持通过 URL 参数指定初始 tab
|
||||
* @example /pages/my-growth?tab=1 跳转到「好友约战」tab
|
||||
*/
|
||||
onLoad((options) => {
|
||||
if (options && options.tab !== undefined) {
|
||||
const tabIndex = parseInt(options.tab, 10);
|
||||
if (!isNaN(tabIndex)) selectedIndex.value = tabIndex;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -59,7 +59,7 @@ async function onReceiveMessage(msg) {
|
||||
if (msg.type === MESSAGETYPESV2.ShootResult) {
|
||||
scores.value = msg.details;
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
|
||||
setTimeout(onOver, 1500);
|
||||
// setTimeout(onOver, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,12 @@ const onClickShare = debounce(async () => {
|
||||
await wxShare("shareCanvas");
|
||||
});
|
||||
|
||||
function onAudioEnded(s) {
|
||||
if (s.indexOf("比赛结束") >= 0) {
|
||||
onOver()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// audioManager.play("第一轮");
|
||||
uni.setKeepScreenOn({
|
||||
@@ -91,6 +97,7 @@ onMounted(async () => {
|
||||
});
|
||||
uni.$on("socket-inbox", onReceiveMessage);
|
||||
uni.$on("share-image", onClickShare);
|
||||
uni.$on("audioEnded", onAudioEnded);
|
||||
const result = await createPractiseAPI(total, 120, targetType.value);
|
||||
if (result) practiseId.value = result.id;
|
||||
});
|
||||
@@ -101,6 +108,7 @@ onBeforeUnmount(() => {
|
||||
});
|
||||
uni.$off("socket-inbox", onReceiveMessage);
|
||||
uni.$off("share-image", onClickShare);
|
||||
uni.$off("audioEnded", onAudioEnded);
|
||||
audioManager.stopAll();
|
||||
endPractiseAPI();
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
|
||||
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
|
||||
import {ref, onMounted, onBeforeUnmount, nextTick} from "vue";
|
||||
import {onLoad, onShow, onHide} from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import BattleHeader from "@/components/BattleHeader.vue";
|
||||
import BowTarget from "@/components/BowTarget.vue";
|
||||
@@ -11,13 +11,14 @@ import RoundEndTip from "@/components/RoundEndTip.vue";
|
||||
import TestDistance from "@/components/TestDistance.vue";
|
||||
import TeamAvatars from "@/components/TeamAvatars.vue";
|
||||
import ShootProgress2 from "@/components/ShootProgress2.vue";
|
||||
import { laserCloseAPI, getBattleAPI } from "@/apis";
|
||||
import { MESSAGETYPESV2 } from "@/constants";
|
||||
import {laserCloseAPI, getBattleAPI} from "@/apis";
|
||||
import {MESSAGETYPESV2} from "@/constants";
|
||||
import audioManager from "@/audioManager";
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
import {storeToRefs} from "pinia";
|
||||
|
||||
const store = useStore();
|
||||
const { user } = storeToRefs(store);
|
||||
const {user} = storeToRefs(store);
|
||||
const start = ref(null);
|
||||
const tips = ref("");
|
||||
const battleId = ref("");
|
||||
@@ -36,15 +37,16 @@ const bluePoints = ref(0);
|
||||
const showRoundTip = ref(false);
|
||||
const isFinalShoot = ref(false);
|
||||
const matchStatus = ref(undefined);
|
||||
const updateRemainSecond = ref(0);
|
||||
|
||||
const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
|
||||
const recoverData = (battleInfo, {force = false, arrowOnly = false} = {}) => {
|
||||
try {
|
||||
battleId.value = battleInfo.matchId;
|
||||
|
||||
|
||||
// 优先使用接口返回的队伍数据,如果没有则尝试从缓存读取(应对匹配刚完成接口未就绪的情况)
|
||||
const t1 = battleInfo.teams?.[1] || {};
|
||||
const t2 = battleInfo.teams?.[2] || {};
|
||||
|
||||
|
||||
if (t1.players) blueTeam.value = [...t1.players];
|
||||
else {
|
||||
const cached = uni.getStorageSync("blue-team");
|
||||
@@ -56,7 +58,7 @@ const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
|
||||
const cached = uni.getStorageSync("red-team");
|
||||
if (cached && cached.length) redTeam.value = cached;
|
||||
}
|
||||
|
||||
|
||||
start.value = battleInfo.status !== 0;
|
||||
|
||||
if (battleInfo.status === 0) {
|
||||
@@ -71,28 +73,27 @@ const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
|
||||
if (!arrowOnly) {
|
||||
currentShooterId.value = battleInfo.current.playerId;
|
||||
const redPlayer = battleInfo.teams[2].players.find(
|
||||
(item) => item.id === battleInfo.current.playerId
|
||||
(item) => item.id === battleInfo.current.playerId
|
||||
);
|
||||
let nextTips = redPlayer ? "请红队射箭" : "请蓝队射箭";
|
||||
if (force) nextTips += "重回";
|
||||
if (
|
||||
battleInfo.current.playerId === user.value.id &&
|
||||
redTeam.value.length > 1
|
||||
battleInfo.current.playerId === user.value.id &&
|
||||
redTeam.value.length > 1
|
||||
) {
|
||||
nextTips += "你";
|
||||
}
|
||||
tips.value = nextTips;
|
||||
uni.$emit("update-tips", nextTips);
|
||||
uni.$emit("update-remain", {reset: true, value: 15, team: redTeam?'red':'blue'});
|
||||
if (force) {
|
||||
const remain = (Date.now() - battleInfo.current.startTime) / 1000;
|
||||
console.log(`当前轮已进行${remain}秒`);
|
||||
if (remain > 0 && remain < 15) {
|
||||
setTimeout(() => {
|
||||
uni.$emit("update-remain", 15 - remain - 0.2);
|
||||
}, 200);
|
||||
updateRemainSecond.value = 15 - remain - 0.2
|
||||
}
|
||||
} else {
|
||||
uni.$emit("update-remain", battleInfo.readyTime);
|
||||
updateRemainSecond.value = battleInfo.readyTime
|
||||
}
|
||||
} else {
|
||||
currentRound.value = battleInfo.current.round || 1;
|
||||
@@ -111,15 +112,39 @@ const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
|
||||
}
|
||||
};
|
||||
|
||||
function onBattleEnd(){
|
||||
showRoundTip.value = false
|
||||
console.log("onBattleEnd ended...")
|
||||
function onAudioEnded(s) {
|
||||
if (s.indexOf('请红方射箭') >= 0 || s.indexOf('请蓝方射箭') >= 0) {
|
||||
let team = s.indexOf('请红方射箭') >= 0 ? 'red' : 'blue';
|
||||
uni.$emit("update-remain", {stop: false, value: updateRemainSecond.value, team: team});
|
||||
}
|
||||
if (s.indexOf("比赛结束") >= 0) {
|
||||
onBattleEnd()
|
||||
}
|
||||
}
|
||||
|
||||
function onBattleEnd() {
|
||||
if (matchStatus.value === 2) {
|
||||
uni.redirectTo({
|
||||
url: `/pages/battle-result?battleId=${battleId.value}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onNewRound(msg) {
|
||||
showRoundTip.value = true;
|
||||
isFinalShoot.value = msg.current.goldRound;
|
||||
const latestRound = msg.rounds[currentRound.value - 1];
|
||||
if (latestRound) {
|
||||
if (isFinalShoot.value) {
|
||||
currentBluePoint.value = msg.teams[1].score;
|
||||
currentRedPoint.value = msg.teams[2].score;
|
||||
} else {
|
||||
currentBluePoint.value = latestRound.scores[1].score;
|
||||
currentRedPoint.value = latestRound.scores[2].score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onReceiveMessage(msg) {
|
||||
if (Array.isArray(msg)) return;
|
||||
if (msg.type === MESSAGETYPESV2.BattleStart) {
|
||||
@@ -127,21 +152,13 @@ async function onReceiveMessage(msg) {
|
||||
} else if (msg.type === MESSAGETYPESV2.ToSomeoneShoot) {
|
||||
recoverData(msg);
|
||||
} else if (msg.type === MESSAGETYPESV2.ShootResult) {
|
||||
uni.$emit("update-remain", {stop: true})
|
||||
showRoundTip.value = false;
|
||||
recoverData(msg, { arrowOnly: true });
|
||||
recoverData(msg, {arrowOnly: true});
|
||||
} else if (msg.type === MESSAGETYPESV2.NewRound) {
|
||||
showRoundTip.value = true;
|
||||
isFinalShoot.value = msg.current.goldRound;
|
||||
const latestRound = msg.rounds[currentRound.value - 1];
|
||||
if (latestRound) {
|
||||
if (isFinalShoot.value) {
|
||||
currentBluePoint.value = msg.teams[1].score;
|
||||
currentRedPoint.value = msg.teams[2].score;
|
||||
} else {
|
||||
currentBluePoint.value = latestRound.scores[1].score;
|
||||
currentRedPoint.value = latestRound.scores[2].score;
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
onNewRound(msg)
|
||||
}, 800)
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
|
||||
matchStatus.value = msg.status;
|
||||
if (msg.status === 4) {
|
||||
@@ -169,13 +186,14 @@ onMounted(async () => {
|
||||
keepScreenOn: true,
|
||||
});
|
||||
uni.$on("socket-inbox", onReceiveMessage);
|
||||
uni.$on("audioEnded", onAudioEnded);
|
||||
await laserCloseAPI();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
uni.setKeepScreenOn({
|
||||
keepScreenOn: false,
|
||||
});
|
||||
uni.$off("socket-inbox", onReceiveMessage);
|
||||
uni.$off("audioEnded", onAudioEnded);
|
||||
audioManager.stopAll();
|
||||
});
|
||||
const refreshTimer = ref(null);
|
||||
@@ -192,7 +210,7 @@ onShow(async () => {
|
||||
delta: 2,
|
||||
});
|
||||
} else {
|
||||
recoverData(result, { force: true });
|
||||
recoverData(result, {force: true});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -202,53 +220,53 @@ onShow(async () => {
|
||||
<Container :bgType="start ? 3 : 1">
|
||||
<view class="container">
|
||||
<BattleHeader
|
||||
v-if="start === false"
|
||||
:redTeam="redTeam"
|
||||
:blueTeam="blueTeam"
|
||||
v-if="start === false"
|
||||
:redTeam="redTeam"
|
||||
:blueTeam="blueTeam"
|
||||
/>
|
||||
<TestDistance v-if="start === false" :guide="false" :isBattle="true" />
|
||||
<TestDistance v-if="start === false" :guide="false" :isBattle="true"/>
|
||||
<view v-if="start" class="players-row">
|
||||
<TeamAvatars
|
||||
:team="blueTeam"
|
||||
:isRed="false"
|
||||
:currentShooterId="currentShooterId"
|
||||
:team="blueTeam"
|
||||
:isRed="false"
|
||||
:currentShooterId="currentShooterId"
|
||||
/>
|
||||
<ShootProgress2
|
||||
:tips="tips"
|
||||
:currentRound="
|
||||
:tips="tips"
|
||||
:currentRound="
|
||||
goldenRound > 0 ? 'gold' + goldenRound : 'round' + currentRound
|
||||
"
|
||||
/>
|
||||
<TeamAvatars :team="redTeam" :currentShooterId="currentShooterId" />
|
||||
<TeamAvatars :team="redTeam" :currentShooterId="currentShooterId"/>
|
||||
</view>
|
||||
<BowTarget
|
||||
v-if="start"
|
||||
mode="team"
|
||||
:scores="scores"
|
||||
:blueScores="blueScores"
|
||||
v-if="start"
|
||||
mode="team"
|
||||
:scores="scores"
|
||||
:blueScores="blueScores"
|
||||
/>
|
||||
<BattleFooter
|
||||
v-if="start"
|
||||
:roundResults="roundResults"
|
||||
:redPoints="redPoints"
|
||||
:bluePoints="bluePoints"
|
||||
:goldenRound="goldenRound"
|
||||
v-if="start"
|
||||
:roundResults="roundResults"
|
||||
:redPoints="redPoints"
|
||||
:bluePoints="bluePoints"
|
||||
:goldenRound="goldenRound"
|
||||
/>
|
||||
<ScreenHint
|
||||
:show="showRoundTip"
|
||||
:onClose="() => (showRoundTip = false)"
|
||||
:mode="isFinalShoot ? 'tall' : 'normal'"
|
||||
:show="showRoundTip"
|
||||
:onClose="() => (showRoundTip = false)"
|
||||
:mode="isFinalShoot ? 'tall' : 'normal'"
|
||||
>
|
||||
<RoundEndTip
|
||||
v-if="showRoundTip"
|
||||
:isFinal="isFinalShoot"
|
||||
:round="currentRound"
|
||||
:bluePoint="currentBluePoint"
|
||||
:redPoint="currentRedPoint"
|
||||
:roundData="
|
||||
v-if="showRoundTip"
|
||||
:isFinal="isFinalShoot"
|
||||
:round="currentRound"
|
||||
:bluePoint="currentBluePoint"
|
||||
:redPoint="currentRedPoint"
|
||||
:roundData="
|
||||
roundResults[currentRound - 1] ? roundResults[currentRound - 1] : []
|
||||
"
|
||||
:onAutoClose="onBattleEnd"
|
||||
:onAutoClose="()=>{ showRoundTip = false}"
|
||||
/>
|
||||
</ScreenHint>
|
||||
</view>
|
||||
@@ -260,6 +278,7 @@ onShow(async () => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.players-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -79,6 +79,7 @@ export default defineStore("store", {
|
||||
game: {
|
||||
roomID: "",
|
||||
inBattle: false,
|
||||
roomNumber: "", // 当前房间号,供 Header 展示房号胶囊
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -135,6 +136,10 @@ export default defineStore("store", {
|
||||
this.game.roomID = roomID;
|
||||
this.game.inBattle = inBattle;
|
||||
},
|
||||
/** 更新当前房间号,供 Header 组件展示房号胶囊 */
|
||||
updateRoomNumber(number) {
|
||||
this.game.roomNumber = number;
|
||||
},
|
||||
},
|
||||
|
||||
// 开启数据持久化
|
||||
|
||||
Reference in New Issue
Block a user