Files
shoot-miniprograms/src/pages/friend-battle.vue
T

476 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { t } from "@/locales";
import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import GuideTwo from "@/components/GuideTwo.vue";
import SButton from "@/components/SButton.vue";
import SModal from "@/components/SModal.vue";
import Signin from "@/components/Signin.vue";
import CreateRoom from "@/components/CreateRoom.vue";
import Avatar from "@/components/Avatar.vue";
import ModalDialog from "@/components/ModalDialog.vue";
import { getRoomAPI, joinRoomAPI, getBattleDataAPI, getDailyCountAPI } from "@/apis";
import { debounce, canEenter, getLimitCountText, isLimitReached } from "@/util";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user, device, online, game, dailyCount } = storeToRefs(store);
const { updateDailyCount } = store;
const showModal = ref(false);
const showSignin = ref(false);
const warnning = ref("");
const roomNumber = ref("");
const data = ref({});
const roomID = ref("");
const loading = ref(false);
const showLimitModal = ref(false);
const createRoomLoading = ref(false);
const isSVip = computed(() => user.value.sVip === true);
const isVip = computed(() => user.value.vip === true && !isSVip.value);
const challengeLimitText = computed(() =>
getLimitCountText(t('challenge'), dailyCount.value.challenge)
);
const enterRoom = debounce(async (number) => {
if (loading.value) return;
if (!canEenter(user.value, device.value, online.value)) return;
if (game.value.inBattle) {
uni.$showHint(1);
return;
}
if (!number) {
warnning.value = t('please_enter_a_room_number');
showModal.value = true;
return;
}
loading.value = true;
let keepLoading = false;
try {
const room = await getRoomAPI(number);
if (!room.number) {
warnning.value = room.started ? t('this_match_has_already_started_you_cannot_join') : t('room_not_found');
showModal.value = true;
return;
}
const alreadyIn = room.members.find(
(item) => item.userInfo.id === user.value.id
);
if (!alreadyIn) {
const result = await joinRoomAPI(number);
if (result.full) {
warnning.value = t('room_is_full');
showModal.value = true;
return;
}
}
keepLoading = true;
uni.navigateTo({
url: "/pages/battle-room?roomNumber=" + number,
fail: () => {
loading.value = false;
},
});
} finally {
if (!keepLoading) loading.value = false;
}
});
const onCreateRoom = debounce(async () => {
if (createRoomLoading.value) return;
if (!canEenter(user.value, device.value, online.value)) return;
createRoomLoading.value = true;
try {
const countData = await loadDailyCount();
if (isLimitReached(countData.challenge)) {
showLimitModal.value = true;
return;
}
warnning.value = "";
showModal.value = true;
} finally {
createRoomLoading.value = false;
}
});
const closeLimitModal = () => {
showLimitModal.value = false;
};
const goVipPage = () => {
showLimitModal.value = false;
uni.navigateTo({
url: "/pages/member/be-vip",
});
};
const loadDailyCount = async () => {
if (!user.value.id) return dailyCount.value;
try {
const result = await getDailyCountAPI();
updateDailyCount(result);
return result || dailyCount.value;
} catch (error) {
console.log("load daily count error", error);
return dailyCount.value;
}
};
const onSignin = () => {
if (roomID.value && user.value.id) enterRoom(roomID.value);
showSignin.value = false;
};
/** 跳转到我的战绩页面,默认展示「好友约战」tab */
const goMyRecord = () => {
uni.navigateTo({
url: '/pages/my-growth?tab=1',
});
};
onShow(async () => {
loading.value = false;
if (user.value.id) {
const [result] = await Promise.all([
getBattleDataAPI(),
loadDailyCount(),
]);
data.value = result;
}
});
onLoad(async (options) => {
if (options.roomID) {
roomID.value = options.roomID;
if (user.value.id) enterRoom(options.roomID);
else showSignin.value = true;
}
});
</script>
<template>
<Container :title="t('friend_challenge')" :showBackToGame="true">
<view :style="{ width: '100%', height: '100%' }">
<GuideTwo>
<view class="guide-tips">
<text class="guide-tips__main">{{ t('invite_friends_for_a_few_matches_and_have_fun_together') }}</text>
<text class="guide-tips__sub">{{ t('practice_together_level_up_faster_and_join_national_ranked_match') }}</text>
</view>
</GuideTwo>
<view class="my-data">
<view>
<Avatar :rankLvl="user.rankLvl" :src="user.avatar" :size="30" />
<view
:class="[
'member-nickname',
isVip ? 'member-nickname--vip' : '',
isSVip ? 'member-nickname--svip' : '',
]"
>
<text class="member-nickname__text">{{ user.nickName }}</text>
<text v-if="isSVip" class="member-nickname__shine">{{
user.nickName
}}</text>
</view>
<text class="my-record-btn" @click="goMyRecord">{{ t('my_results') }}</text>
</view>
<view>
<view>
<view>
<text>{{ data.TotalBattle }}</text>
<text>{{ t('matches') }}</text>
</view>
<text>{{ t('challenges') }}</text>
</view>
<view>
<view>
<text>{{ data.totalArrow }}</text>
<text>{{ t('arrows') }}</text>
</view>
<text>{{ t('arrows_shot') }}</text>
</view>
<view>
<view class="stars">
<block v-for="i in 5" :key="i">
<image v-if="data.totalWinningRate >= i * 0.2" src="https://static.shelingxingqiu.com/shootmini/static/star-full.png" mode="widthFix" />
<image v-else-if="data.totalWinningRate >= (i - 1) * 0.2 + 0.1" src="https://static.shelingxingqiu.com/shootmini/static/star-half.png"
mode="widthFix" />
<image v-else src="https://static.shelingxingqiu.com/shootmini/static/star-empty.png" mode="widthFix" />
</block>
</view>
<text>{{ t('challenge_difficulty') }}</text>
</view>
</view>
</view>
<view class="founded-room">
<image src="https://static.shelingxingqiu.com/shootmini/static/founded-room.png" mode="widthFix" />
<view>
<input :placeholder="t('enter_room_number')" v-model="roomNumber" placeholder-style="color: #ccc" />
<view @click="$clickSound(() => enterRoom(roomNumber))">{{ t('join_room') }}</view>
</view>
</view>
<view class="create-room">
<image src="https://static.shelingxingqiu.com/attachment/2025-07-15/dbcejys872iyun92h6.png" mode="widthFix" />
<image src="https://static.shelingxingqiu.com/shootmini/static/room-notfound-title.png" mode="widthFix" />
<view>
<image :src="user.avatar" mode="widthFix" />
<image src="https://static.shelingxingqiu.com/shootmini/static/versus.png" mode="widthFix" />
<view>
<image src="https://static.shelingxingqiu.com/shootmini/static/question-mark.png" mode="widthFix" />
</view>
</view>
<view>
<view v-if="challengeLimitText" class="pp-text">
{{ challengeLimitText }}
</view>
<SButton
width="80%"
:rounded="30"
:disabled="createRoomLoading"
:onClick="() => { $clickSound(); return onCreateRoom(); }"
>{{ t('create_challenge_room') }}</SButton>
</view>
</view>
<SModal :show="showModal" :onClose="() => (showModal = false)" height="716rpx">
<view v-if="warnning" class="warnning">
{{ warnning }}
</view>
<!-- showModal 关闭时立即销毁组件,重开时重建,确保选项重置为 0 -->
<CreateRoom v-if="!warnning && showModal" :onConfirm="() => (showModal = false)" />
</SModal>
<Signin :show="showSignin" :onClose="onSignin" />
</view>
</Container>
<ModalDialog
:show="showLimitModal"
:content="t('you_have_used_all_your_challenges_for_today_activate_membership_')"
:cancelText="t('got_it_335')"
:confirmText="t('activate')"
:onCancel="closeLimitModal"
:onConfirm="goVipPage"
/>
</template>
<style scoped>
.guide-tips {
display: flex;
flex-direction: column;
padding-left: 112rpx;
width: 100%;
}
.guide-tips__main {
font-weight: 400;
font-size: 26rpx;
color: rgba(255, 217, 71, 0.8);
}
.guide-tips__sub {
font-weight: 400;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
margin-top: 6rpx;
}
.founded-room {
display: flex;
flex-direction: column;
align-items: flex-start;
background-color: #54431d33;
border: 1px solid #54431d;
margin: 15px;
border-radius: 10px;
padding: 15px;
}
.founded-room>image {
width: 16vw;
}
.founded-room>view {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
background-color: #fff;
border-radius: 30px;
width: 100%;
overflow: hidden;
}
.founded-room>view>input {
width: 70%;
text-align: center;
font-size: 14px;
height: 40px;
color: #000;
}
.founded-room>view>view {
background-color: #fed847;
width: 30%;
line-height: 40px;
border-radius: 30px;
font-size: 14px;
padding: 3px 0;
font-weight: bold;
color: #000;
text-align: center;
}
.create-room {
position: relative;
margin: 15px;
height: 50vw;
}
.create-room>image:first-of-type {
position: absolute;
width: 100%;
}
.create-room>image:nth-of-type(2) {
padding: 15px;
width: 25vw;
position: relative;
}
.create-room>view:nth-child(3) {
margin: 12vw auto 5vw auto;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.create-room>view>image:first-child {
width: 19vw;
transform: translateY(-60%);
border-radius: 50%;
position: relative;
}
.create-room>view>image:nth-child(2) {
width: 37vw;
position: relative;
}
.create-room>view>view:nth-child(3) {
position: relative;
width: 19vw;
height: 19vw;
border-radius: 50%;
background-color: #ccc;
display: flex;
justify-content: center;
align-items: center;
transform: translateY(60%);
}
.create-room>view>view:nth-child(3)>image {
width: 20px;
margin-right: 2px;
}
.pp-text{
color: #fff;
text-align: center;
font-size: 22rpx;
margin-bottom: 20rpx;
}
.warnning {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #fff9;
}
.my-data {
width: calc(100% - 30px);
margin: 15px;
margin-top: 0;
border-radius: 10px;
border: 1px solid #54431d;
overflow: hidden;
background-color: #54431d33;
}
.my-data>view {
width: 100%;
display: flex;
color: #fff9;
}
.my-data>view:first-child {
width: calc(100% - 30px);
align-items: flex-end;
padding-bottom: 15px;
border-bottom: 1px solid #48494e;
margin: 15px;
margin-bottom: 0;
}
.my-data>view:first-child>.my-record-btn {
font-weight: 400;
font-size: 24rpx;
color: #76D4FF;
text-align: center;
font-style: normal;
width: auto;
margin-left: auto;
}
.my-data>view:first-child>.member-nickname {
color: #fff;
margin-left: 10px;
width: 120px;
}
.my-data>view:first-child>.member-nickname__text,
.my-data>view:first-child>.member-nickname__shine {
font-size: 17px;
}
.my-data>view:last-child {
margin-bottom: 15px;
}
.my-data>view:last-child>view {
width: 33%;
margin-top: 15px;
display: flex;
flex-direction: column;
align-items: center;
font-size: 12px;
}
.my-data>view:last-child>view>view {
margin-bottom: 5px;
}
.my-data>view:last-child>view>view>text:first-child {
color: #fff;
font-size: 20px;
margin-right: 5px;
transform: translateY(4px);
}
.my-data>view:last-child>view:nth-child(2) {
border-left: 1px solid #48494e;
border-right: 1px solid #48494e;
}
.my-data>view:last-child>view>view {
display: flex;
align-items: flex-end;
height: 20px;
}
.stars>image {
width: 4vw;
height: 4vw;
margin: 0 1px;
}
</style>