6 Commits
Author SHA1 Message Date
linyimin e1a9d97596 pref: 小程序添加静默登录 2026-04-28 10:31:54 +08:00
linyimin f07facd98b pref: 绑定设备、匹配pk 2026-04-28 08:59:16 +08:00
linyimin 8c48216a75 feat: 好友约战选择20、40厘米靶 2026-04-20 17:43:38 +08:00
linyimin c1ff0cedad feat:选择20cm、40cm全环靶 2026-04-20 16:00:10 +08:00
linyimin 56650793e8 pref: 更新音频 2026-04-20 11:13:59 +08:00
linyimin e8568ee6a8 feat: 添加射箭声音 2026-04-16 18:06:59 +08:00
18 changed files with 1137 additions and 694 deletions
+52 -16
View File
@@ -1,13 +1,28 @@
<script setup> <script setup>
import { watch } from "vue"; import {
import { onShow, onHide } from "@dcloudio/uni-app"; watch
} from "vue";
import {
onShow,
onHide
} from "@dcloudio/uni-app";
import websocket from "@/websocket"; import websocket from "@/websocket";
import { getDeviceBatteryAPI } from "@/apis"; import {
getDeviceBatteryAPI
} from "@/apis";
import useStore from "@/store"; import useStore from "@/store";
import { storeToRefs } from "pinia"; import {
storeToRefs
} from "pinia";
import audioManager from "./audioManager";
const store = useStore(); const store = useStore();
const { user } = storeToRefs(store); const {
const { updateUser, updateOnline } = store; user
} = storeToRefs(store);
const {
updateUser,
updateOnline
} = store;
watch( watch(
() => user.value.id, () => user.value.id,
@@ -16,15 +31,12 @@ watch(
`${uni.getAccountInfoSync().miniProgram.envVersion}_token` `${uni.getAccountInfoSync().miniProgram.envVersion}_token`
); );
if (newVal && token) { if (newVal && token) {
websocket.createWebSocket(token, (content) => { websocket.createWebSocket(token, onShootWsMsg);
uni.$emit("socket-inbox", content);
});
} }
if (!newVal) { if (!newVal) {
websocket.closeWebSocket(); websocket.closeWebSocket();
} }
}, }, {
{
deep: false, // 如果 user 是一个对象或数组,建议开启 deep: false, // 如果 user 是一个对象或数组,建议开启
immediate: false, // 若想在初始化时立即执行一次回调,可开启。 immediate: false, // 若想在初始化时立即执行一次回调,可开启。
} }
@@ -39,6 +51,17 @@ async function emitUpdateOnline() {
updateOnline(data.online); updateOnline(data.online);
} }
function onDeviceShoot() {
audioManager.play("射箭声音")
}
function onShootWsMsg(content) {
if(content.type === 'shoot-trigger'){
onDeviceShoot()
}
uni.$emit("socket-inbox", content);
}
onShow(() => { onShow(() => {
uni.$on("update-user", emitUpdateUser); uni.$on("update-user", emitUpdateUser);
uni.$on("update-online", emitUpdateOnline); uni.$on("update-online", emitUpdateOnline);
@@ -47,9 +70,7 @@ onShow(() => {
); );
if (user.value.id && token) { if (user.value.id && token) {
console.log("回到前台,重新连接 websocket"); console.log("回到前台,重新连接 websocket");
websocket.createWebSocket(token, (content) => { websocket.createWebSocket(token, onShootWsMsg);
uni.$emit("socket-inbox", content);
});
} }
}); });
@@ -93,9 +114,11 @@ button::after {
flex-direction: column; flex-direction: column;
font-size: 28rpx; font-size: 28rpx;
} }
.guide-tips>text:first-child { .guide-tips>text:first-child {
color: #fed847; color: #fed847;
} }
.guide-tips>text:nth-child(2) { .guide-tips>text:nth-child(2) {
font-size: 24rpx; font-size: 24rpx;
} }
@@ -105,13 +128,16 @@ button::after {
transform: translateY(20px); transform: translateY(20px);
opacity: 0; opacity: 0;
} }
30% { 30% {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
80% { 80% {
opacity: 1; opacity: 1;
} }
100% { 100% {
opacity: 0; opacity: 0;
} }
@@ -126,6 +152,7 @@ button::after {
transform: translateY(0); transform: translateY(0);
opacity: 1; opacity: 1;
} }
to { to {
transform: translateY(20px); transform: translateY(20px);
opacity: 0; opacity: 0;
@@ -141,6 +168,7 @@ button::after {
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;
} }
to { to {
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
@@ -157,6 +185,7 @@ button::after {
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
} }
to { to {
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;
@@ -172,6 +201,7 @@ button::after {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
@@ -181,6 +211,7 @@ button::after {
from { from {
transform: scale(2); transform: scale(2);
} }
to { to {
transform: scale(1); transform: scale(1);
} }
@@ -212,6 +243,7 @@ button::after {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.user-row { .user-row {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -220,6 +252,7 @@ button::after {
padding-top: 7px; padding-top: 7px;
position: relative; position: relative;
} }
.half-time-tip { .half-time-tip {
width: 100%; width: 100%;
display: flex; display: flex;
@@ -227,28 +260,31 @@ button::after {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.half-time-tip>text:last-child { .half-time-tip>text:last-child {
margin-top: 20px; margin-top: 20px;
color: #fff9; color: #fff9;
} }
.see-more { .see-more {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-top: 20rpx; margin-top: 20rpx;
} }
.see-more>text { .see-more>text {
color: #39a8ff; color: #39a8ff;
font-size: 13px; font-size: 13px;
} }
.see-more>image { .see-more>image {
width: 15px; width: 15px;
} }
@font-face { @font-face {
font-family: "DINCondensed"; font-family: "DINCondensed";
src: url("https://static.shelingxingqiu.com/font/DIN-Condensed-Bold-2.ttf") src: url("https://static.shelingxingqiu.com/font/DIN-Condensed-Bold-2.ttf") format("truetype");
format("truetype");
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
+35 -5
View File
@@ -6,8 +6,8 @@ try {
switch (envVersion) { switch (envVersion) {
case "develop": // 开发版 case "develop": // 开发版
// BASE_URL = "http://192.168.1.30:8000/api/shoot"; BASE_URL = "http://localhost:8000/api/shoot";
BASE_URL = "https://apitest.shelingxingqiu.com/api/shoot"; // BASE_URL = "https://apitest.shelingxingqiu.com/api/shoot";
break; break;
case "trial": // 体验版 case "trial": // 体验版
BASE_URL = "https://apitest.shelingxingqiu.com/api/shoot"; BASE_URL = "https://apitest.shelingxingqiu.com/api/shoot";
@@ -179,12 +179,39 @@ export const loginAPI = async (phone, nickName, avatarData, code) => {
return result; return result;
}; };
export const silentLoginAPI = async (code) => {
const result = await request("POST", "/index/code", {
appName: "shoot",
appId: "wxa8f5989dcd45cc23",
code,
});
uni.setStorageSync(
`${uni.getAccountInfoSync().miniProgram.envVersion}_token`,
result.token
);
return result;
};
export const checkUserBindAPI = async (code) => {
return request("POST", "/index/checkBind", {
appName: "shoot",
appId: "wxa8f5989dcd45cc23",
code,
});
};
export const bindDeviceAPI = (device) => { export const bindDeviceAPI = (device) => {
return request("POST", "/user/device/bindDevice", { return request("POST", "/user/device/bindDevice", {
device, device,
}); });
}; };
export const bindDeviceAPIV2 = (token) => {
return request("POST", "/user/device/bindDevice/v2", {
token: token,
});
};
export const unbindDeviceAPI = (deviceId) => { export const unbindDeviceAPI = (deviceId) => {
return request("POST", "/user/device/unbindDevice", { return request("POST", "/user/device/unbindDevice", {
deviceId, deviceId,
@@ -196,10 +223,11 @@ export const getMyDevicesAPI = () => {
return request("GET", "/user/device/getBindings"); return request("GET", "/user/device/getBindings");
}; };
export const createPractiseAPI = (arrows, time) => { export const createPractiseAPI = (arrows, time, target) => {
return request("POST", "/user/practice/create", { return request("POST", "/user/practice/create", {
shootNumber: arrows, shootNumber: arrows,
shootTime: time, shootTime: time,
targetType: target * 20,
}); });
}; };
@@ -215,10 +243,11 @@ export const getPractiseAPI = async (id) => {
return request("GET", `/user/practice/get?id=${id}`); return request("GET", `/user/practice/get?id=${id}`);
}; };
export const createRoomAPI = (gameType, teamSize) => { export const createRoomAPI = (gameType, teamSize, targetType) => {
return request("POST", "/user/createroom", { return request("POST", "/user/createroom", {
gameType, gameType,
teamSize, teamSize,
targetType,
}); });
}; };
@@ -260,7 +289,8 @@ export const matchGameAPI = (match, gameType, teamSize) => {
match, match,
gameType, gameType,
teamSize, teamSize,
readyTime: 1.5, readyTime: 15,
targetType: 20,
}); });
}; };
+15 -11
View File
@@ -40,26 +40,26 @@ export const audioFils = {
未上靶: 未上靶:
"https://static.shelingxingqiu.com/attachment/2025-11-12/de6n45o3tsm1v4unam.mp3", "https://static.shelingxingqiu.com/attachment/2025-11-12/de6n45o3tsm1v4unam.mp3",
"1环": "1环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin1aq7gxjih5l.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/1.mp3",
"2环": "2环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin64tdgx2s4at.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/2.mp3",
"3环": "3环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinlmf87vt8z65.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/3.mp3",
"4环": "4环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinniv97sx0q9u.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/4.mp3",
"5环": "5环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin7j01kknpb7k.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/5.mp3",
"6环": "6环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin4syy1015rtq.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/6.mp3",
"7环": "7环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin3iz3dvmjdai.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/7.mp3",
"8环": "8环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinnjd42lhpfiw.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/8.mp3",
"9环": "9环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin69nj1xh7yfz.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/9.mp3",
"10环": "10环":
"https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinnvsx0tt7ksa.mp3", "https://static.shelingxingqiu.com/shootaudio/v3/10.mp3",
X环: "https://static.shelingxingqiu.com/attachment/2026-02-09/dga8puwekpe2gmtbu4.mp3", X环: "https://static.shelingxingqiu.com/shootaudio/v4/v4/X%E7%8E%AF.mp3",
向上调整: 向上调整:
"https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf5pfvu3l8dhr.mp3", "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf5pfvu3l8dhr.mp3",
向右上调整: 向右上调整:
@@ -80,6 +80,10 @@ export const audioFils = {
"https://static.shelingxingqiu.com/attachment/2025-11-13/de7kzzllq0futwynso.mp3", "https://static.shelingxingqiu.com/attachment/2025-11-13/de7kzzllq0futwynso.mp3",
练习开始: 练习开始:
"https://static.shelingxingqiu.com/attachment/2025-11-14/de88w0lmmt43nnfmoi.mp3", "https://static.shelingxingqiu.com/attachment/2025-11-14/de88w0lmmt43nnfmoi.mp3",
射箭声音:
"https://static.shelingxingqiu.com/shootaudio/v4/v4/%E7%AE%AD%E9%A3%9E%E8%A1%8C.mp3",
命中:
"https://static.shelingxingqiu.com/shootaudio/%E5%91%BD%E4%B8%AD.mp3"
}; };
// 版本控制日志函数 // 版本控制日志函数
+6 -2
View File
@@ -5,7 +5,7 @@ import AppBackground from "@/components/AppBackground.vue";
import Header from "@/components/Header.vue"; import Header from "@/components/Header.vue";
import ScreenHint from "@/components/ScreenHint.vue"; import ScreenHint from "@/components/ScreenHint.vue";
import BackToGame from "@/components/BackToGame.vue"; import BackToGame from "@/components/BackToGame.vue";
import { laserAimAPI, getBattleAPI } from "@/apis"; import {laserAimAPI, getBattleAPI, matchGameAPI} from "@/apis";
import { capsuleHeight, debounce } from "@/util"; import { capsuleHeight, debounce } from "@/util";
import AudioManager from "@/audioManager"; import AudioManager from "@/audioManager";
const props = defineProps({ const props = defineProps({
@@ -135,6 +135,10 @@ const goBack = () => {
uni.navigateBack(); uni.navigateBack();
}; };
const cancelMatching = async () => {
uni.$emit("cancelMatching");
}
const goCalibration = async () => { const goCalibration = async () => {
await laserAimAPI(); await laserAimAPI();
uni.navigateTo({ uni.navigateTo({
@@ -202,7 +206,7 @@ const goCalibration = async () => {
<button hover-class="none" @click="() => (showHint = false)"> <button hover-class="none" @click="() => (showHint = false)">
取消 取消
</button> </button>
<button hover-class="none" @click="goBack">确认</button> <button hover-class="none" @click="cancelMatching">确认</button>
</view> </view>
</view> </view>
<view v-if="hintType === 4" class="tip-content"> <view v-if="hintType === 4" class="tip-content">
+73 -3
View File
@@ -18,6 +18,7 @@ const props = defineProps({
}); });
const battleMode = ref(1); const battleMode = ref(1);
const targetMode = ref(1);
const loading = ref(false); const loading = ref(false);
const roomNumber = ref(""); const roomNumber = ref("");
@@ -35,13 +36,14 @@ const createRoom = debounce(async () => {
try { try {
const result = await createRoomAPI( const result = await createRoomAPI(
battleMode.value === 2 ? 2 : 1, battleMode.value === 2 ? 2 : 1,
battleMode.value === 2 ? 10 : size battleMode.value === 2 ? 10 : size,
targetMode.value*20,
); );
if (result.number) { if (result.number) {
props.onConfirm(); props.onConfirm();
await joinRoomAPI(result.number); await joinRoomAPI(result.number);
uni.navigateTo({ uni.navigateTo({
url: "/pages/battle-room?roomNumber=" + result.number, url: "/pages/battle-room?roomNumber=" + result.number + "&target=" + targetMode.value,
}); });
} }
} catch (error) { } catch (error) {
@@ -53,7 +55,11 @@ const createRoom = debounce(async () => {
</script> </script>
<template> <template>
<view class="container"> <view class="container">
<image src="../static/choose-battle-mode.png" mode="widthFix" /> <view class="target-options-header">
<view class="target-options-header-line-left"></view>
<image class="target-options-header-title-img" src="../static/choose-battle-mode.png" mode="widthFix" />
<view class="target-options-header-line-right"></view>
</view>
<view class="create-options"> <view class="create-options">
<view <view
:class="{ 'battle-btn': true, 'battle-choosen': battleMode === 1 }" :class="{ 'battle-btn': true, 'battle-choosen': battleMode === 1 }"
@@ -80,6 +86,25 @@ const createRoom = debounce(async () => {
<text>乱斗模式3-10</text> <text>乱斗模式3-10</text>
</view> </view>
</view> </view>
<view class="target-options-header">
<view class="target-options-header-line-left"></view>
<view class="target-options-header-title">选择靶纸</view>
<view class="target-options-header-line-right"></view>
</view>
<view class="target-options">
<view
:class="{ 'battle-btn': true, 'battle-choosen': targetMode === 1 }"
@click="() => (targetMode = 1)"
>
<text>20厘米全环靶</text>
</view>
<view
:class="{ 'battle-btn': true, 'battle-choosen': targetMode === 2 }"
@click="() => (targetMode = 2)"
>
<text>40厘米全环靶</text>
</view>
</view>
<SButton :onClick="createRoom">创建房间</SButton> <SButton :onClick="createRoom">创建房间</SButton>
</view> </view>
</template> </template>
@@ -91,6 +116,7 @@ const createRoom = debounce(async () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding-top: 44rpx;
} }
.container > image:first-child { .container > image:first-child {
width: 45%; width: 45%;
@@ -105,6 +131,50 @@ const createRoom = debounce(async () => {
justify-content: center; justify-content: center;
margin-bottom: 15px; margin-bottom: 15px;
} }
.target-options-header{
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
}
.target-options-header-title-img{
width: 196rpx;
height: 40rpx;
}
.target-options-header-title{
width: 112rpx;
height: 40rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 28rpx;
text-align: center;
font-style: normal;
text-transform: none;
color: #FFEFBA;
margin: 0 18rpx;
}
.target-options-header-line-left{
width: 214rpx;
height: 0rpx;
border-radius: 0rpx 0rpx 0rpx 0rpx;
border: 1rpx solid;
border-image: linear-gradient(90deg, rgba(133, 119, 96, 0), rgba(133, 119, 96, 1)) 1 1;
}
.target-options-header-line-right{
width: 214rpx;
height: 0rpx;
border-radius: 0rpx 0rpx 0rpx 0rpx;
border: 1rpx solid;
border-image: linear-gradient(90deg, rgba(133, 119, 96, 1), rgba(133, 119, 96, 0)) 1 1;
}
.target-options {
width: 100%;
padding: 0 10px;
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 15px;
}
.battle-btn { .battle-btn {
width: 45%; width: 45%;
height: 55px; height: 55px;
+1
View File
@@ -88,6 +88,7 @@ watch(
transform: translateY(100%); transform: translateY(100%);
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative; position: relative;
background-color: #372E1D;
} }
.modal-content > image:first-child { .modal-content > image:first-child {
width: 100%; width: 100%;
+9 -6
View File
@@ -86,18 +86,17 @@ const handleLogin = async () => {
icon: "none", icon: "none",
}); });
} }
await doLogin();
};
async function doLogin() {
loading.value = true; loading.value = true;
try { try {
const wxResult = await wxLogin(); const wxResult = await wxLogin();
const fileManager = uni.getFileSystemManager(); const fileManager = uni.getFileSystemManager();
const avatarBase64 = fileManager.readFileSync(avatarUrl.value, "base64"); const avatarBase64 = fileManager.readFileSync(avatarUrl.value, "base64");
const base64Url = `data:image/png;base64,${avatarBase64}`; const base64Url = `data:image/png;base64,${avatarBase64}`;
const result = await loginAPI( await loginAPI(phone.value, nickName.value, base64Url, wxResult.code);
phone.value,
nickName.value,
base64Url,
wxResult.code
);
const data = await getHomeData(); const data = await getHomeData();
if (data.user) updateUser(data.user); if (data.user) updateUser(data.user);
const devices = await getMyDevicesAPI(); const devices = await getMyDevicesAPI();
@@ -139,6 +138,10 @@ const openPrivacyLink = () => {
onShow(() => { onShow(() => {
loading.value = false; loading.value = false;
agree.value = false;
phone.value = "";
avatarUrl.value = "";
nickName.value = "";
}); });
</script> </script>
+197
View File
@@ -0,0 +1,197 @@
<script setup>
import { ref, watch } from "vue";
import SButton from "@/components/SButton.vue";
const props = defineProps({
show: {
type: Boolean,
default: false,
},
onClose: {
type: Function,
default: () => {},
},
onConfirm: {
type: Function,
default: () => {},
},
});
const selectedTarget = ref(2);
const showContainer = ref(false);
const showContent = ref(false);
watch(
() => props.show,
(newValue) => {
if (newValue) {
showContainer.value = true;
setTimeout(() => {
showContent.value = true;
}, 100);
} else {
showContent.value = false;
setTimeout(() => {
showContainer.value = false;
}, 100);
}
},
{}
);
const handleConfirm = () => {
props.onConfirm(selectedTarget.value);
props.onClose();
};
</script>
<template>
<view
class="container"
v-if="showContainer"
:class="{ 'container-show': showContent }"
@click="onClose"
>
<view
class="modal-content"
:class="{ 'modal-show': showContent }"
@click.stop=""
>
<view class="header">
<view class="header-title">
<view class="header-title-line-left"></view>
<text>选择靶型</text>
<view class="header-title-line-right"></view>
</view>
<view class="close-btn" @click="onClose">
<image src="../static/close-yellow.png" mode="widthFix" />
</view>
</view>
<view class="target-options">
<view
:class="{ 'target-btn': true, 'target-choosen': selectedTarget === 1 }"
@click="() => (selectedTarget = 1)"
>
<text>20厘米全环靶</text>
</view>
<view style="width: 30rpx"></view>
<view
:class="{ 'target-btn': true, 'target-choosen': selectedTarget === 2 }"
@click="() => (selectedTarget = 2)"
>
<text>40厘米全环靶</text>
</view>
</view>
<SButton width="694rpx" :onClick="handleConfirm">确定</SButton>
</view>
</view>
</template>
<style scoped>
.container {
position: fixed;
top: 0;
left: 0;
background-color: #00000099;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
opacity: 0;
transition: all 0.3s ease;
z-index: 999;
}
.container-show {
opacity: 1;
}
.modal-content {
width: 100%;
transform: translateY(100%);
transition: all 0.3s ease;
background: url("https://static.shelingxingqiu.com/attachment/2025-12-04/dep11770wzxg6o2alo.png")
no-repeat center top;
background-size: 100% auto;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding-bottom: 68rpx;
padding-top: 44rpx;
}
.modal-show {
transform: translateY(0%);
}
.header {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-bottom: 44rpx;
}
.header-title{
display: flex;
align-items: center;
justify-content: center;
}
.header-title text{
width: 196rpx;
height: 40rpx;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 28rpx;
text-align: center;
font-style: normal;
text-transform: none;
color: #FFEFBA;
}
.header-title-line-left{
width: 214rpx;
height: 0rpx;
border-radius: 0rpx 0rpx 0rpx 0rpx;
border: 1rpx solid;
border-image: linear-gradient(90deg, rgba(133, 119, 96, 1), rgba(133, 119, 96, 0)) 1 1;
}
.header-title-line-right{
width: 214rpx;
height: 0rpx;
border-radius: 0rpx 0rpx 0rpx 0rpx;
border: 1rpx solid;
border-image: linear-gradient(90deg, rgba(133, 119, 96, 1), rgba(133, 119, 96, 0)) 1 1;
}
.close-btn {
position: absolute;
right: 0;
top: -10px;
}
.close-btn > image {
width: 40px;
height: 40px;
}
.target-options {
width: 750rpx;
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 38rpx;
}
.target-btn {
width: 332rpx;
height: 92rpx;
text-align: center;
border-radius: 10px;
border: 2rpx solid #fff3;
box-sizing: border-box;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.target-choosen {
color: #fed847;
border: 4rpx solid #fed847;
}
</style>
+4 -2
View File
@@ -1,6 +1,6 @@
{ {
"name" : "shoot-miniprograms", "name" : "shoot-miniprograms",
"appid": "", "appid" : "__UNI__B03E251",
"description" : "", "description" : "",
"versionName" : "1.0.0", "versionName" : "1.0.0",
"versionCode" : "100", "versionCode" : "100",
@@ -40,7 +40,9 @@
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
] ]
}, },
"ios": {}, "ios" : {
"dSYMs" : false
},
"sdkConfigs" : {} "sdkConfigs" : {}
} }
}, },
+1 -1
View File
@@ -169,7 +169,7 @@ onLoad(async (options) => {
<SModal <SModal
:show="showModal" :show="showModal"
:onClose="() => (showModal = false)" :onClose="() => (showModal = false)"
height="520rpx" height="716rpx"
> >
<view v-if="warnning" class="warnning"> <view v-if="warnning" class="warnning">
{{ warnning }} {{ warnning }}
+51 -12
View File
@@ -1,25 +1,26 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; import {onMounted, ref} from "vue";
import { onShow, onShareAppMessage, onShareTimeline } from "@dcloudio/uni-app"; import {onShareAppMessage, onShareTimeline, onShow} from "@dcloudio/uni-app";
import Container from "@/components/Container.vue"; import Container from "@/components/Container.vue";
import AppFooter from "@/components/AppFooter.vue"; import AppFooter from "@/components/AppFooter.vue";
import AppBackground from "@/components/AppBackground.vue";
import UserHeader from "@/components/UserHeader.vue"; import UserHeader from "@/components/UserHeader.vue";
import Signin from "@/components/Signin.vue"; import Signin from "@/components/Signin.vue";
import BubbleTip from "@/components/BubbleTip.vue"; import BubbleTip from "@/components/BubbleTip.vue";
import { import {
checkUserBindAPI,
getAppConfig, getAppConfig,
getRankListAPI, getDeviceBatteryAPI,
getHomeData, getHomeData,
getMyDevicesAPI, getMyDevicesAPI,
getDeviceBatteryAPI, getRankListAPI,
silentLoginAPI,
} from "@/apis"; } from "@/apis";
import {topThreeColors} from "@/constants"; import {topThreeColors} from "@/constants";
import { canEenter } from "@/util";
import useStore from "@/store"; import useStore from "@/store";
import {storeToRefs} from "pinia"; import {storeToRefs} from "pinia";
const store = useStore(); const store = useStore();
const { const {
updateConfig, updateConfig,
@@ -27,6 +28,7 @@ const {
updateDevice, updateDevice,
updateRank, updateRank,
getLvlName, getLvlName,
getLvlNameByScore,
updateOnline, updateOnline,
} = store; } = store;
const {user, device, rankData, online, game} = storeToRefs(store); const {user, device, rankData, online, game} = storeToRefs(store);
@@ -54,12 +56,36 @@ const toRankListPage = () => {
}; };
onShow(async () => { onShow(async () => {
const token = uni.getStorageSync( const env = uni.getAccountInfoSync().miniProgram.envVersion;
`${uni.getAccountInfoSync().miniProgram.envVersion}_token` const token = uni.getStorageSync(`${env}_token`);
if (!user.value.id && !token) {
try {
const wxResult = await uni.login({provider: "weixin"});
const bindResult = await checkUserBindAPI(wxResult.code);
if (bindResult.binded) {
const newResult = await uni.login({provider: "weixin"});
const silentResult = await silentLoginAPI(newResult.code);
if (silentResult.user) updateUser(silentResult.user);
const devices = await getMyDevicesAPI();
if (devices.bindings && devices.bindings.length) {
updateDevice(
devices.bindings[0].deviceId,
devices.bindings[0].deviceName
); );
const data = await getDeviceBatteryAPI();
updateOnline(data.online);
}
} else {
showModal.value = true;
}
} catch (e) {
console.log("检查绑定状态失败", e);
}
}
const promises = [getRankListAPI()]; const promises = [getRankListAPI()];
if (token) { if (token || user.value.id) {
promises.push(getHomeData()); promises.push(getHomeData());
} }
@@ -221,8 +247,9 @@ onShareTimeline(() => {
<view> <view>
<text>段位</text> <text>段位</text>
<text>{{ <text>{{
user.rankLvl ? getLvlName(user.rankLvl) : "暂无" user.lvlName || "暂无"
}}</text> }}
</text>
</view> </view>
<view> <view>
<text>赛季平均环数</text> <text>赛季平均环数</text>
@@ -234,7 +261,8 @@ onShareTimeline(() => {
user.avg_win user.avg_win
? Number((user.avg_win * 100).toFixed(2)) + "%" ? Number((user.avg_win * 100).toFixed(2)) + "%"
: "暂无" : "暂无"
}}</text> }}
</text>
</view> </view>
</view> </view>
</view> </view>
@@ -363,6 +391,7 @@ onShareTimeline(() => {
width: 32rpx; width: 32rpx;
height: 32rpx; height: 32rpx;
} }
.player-avatar > view:first-child { .player-avatar > view:first-child {
border-radius: 50%; border-radius: 50%;
background: #777777; background: #777777;
@@ -373,6 +402,7 @@ onShareTimeline(() => {
height: 18px; height: 18px;
color: #fff; color: #fff;
} }
.player-avatar > image:last-child { .player-avatar > image:last-child {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -391,18 +421,22 @@ onShareTimeline(() => {
margin-left: 2px; margin-left: 2px;
color: #fff; color: #fff;
} }
.my-data { .my-data {
display: flex; display: flex;
margin-top: 20px; margin-top: 20px;
justify-content: space-between; justify-content: space-between;
} }
.my-data > view:first-child { .my-data > view:first-child {
width: 28%; width: 28%;
} }
.my-data > view:first-child > image { .my-data > view:first-child > image {
width: 100%; width: 100%;
transform: translateX(-8px); transform: translateX(-8px);
} }
.my-data > view:nth-child(2) { .my-data > view:nth-child(2) {
width: 68%; width: 68%;
font-size: 12px; font-size: 12px;
@@ -410,9 +444,11 @@ onShareTimeline(() => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.my-data > view:nth-child(2) > view:nth-child(2) { .my-data > view:nth-child(2) > view:nth-child(2) {
width: 38%; width: 38%;
} }
.my-data > view:nth-child(2) > view { .my-data > view:nth-child(2) > view {
width: 28%; width: 28%;
border-radius: 10px; border-radius: 10px;
@@ -422,11 +458,13 @@ onShareTimeline(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.my-data > view:nth-child(2) > view > text:last-child { .my-data > view:nth-child(2) > view > text:last-child {
color: #fff; color: #fff;
line-height: 25px; line-height: 25px;
font-weight: 500; font-weight: 500;
} }
.top-theme { .top-theme {
position: absolute; position: absolute;
display: flex; display: flex;
@@ -436,6 +474,7 @@ onShareTimeline(() => {
height: 60px; height: 60px;
z-index: -1; z-index: -1;
} }
.top-theme > image { .top-theme > image {
width: 300rpx; width: 300rpx;
transform: translate(-4%, -14%); transform: translate(-4%, -14%);
+10 -8
View File
@@ -3,8 +3,6 @@ import { ref, onMounted, onBeforeUnmount } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app"; import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue"; import Container from "@/components/Container.vue";
import Matching from "@/components/Matching.vue"; import Matching from "@/components/Matching.vue";
import RoundEndTip from "@/components/RoundEndTip.vue";
import TestDistance from "@/components/TestDistance.vue";
import { matchGameAPI } from "@/apis"; import { matchGameAPI } from "@/apis";
import { MESSAGETYPESV2 } from "@/constants"; import { MESSAGETYPESV2 } from "@/constants";
@@ -17,6 +15,13 @@ async function stopMatch() {
uni.$showHint(3); uni.$showHint(3);
} }
async function cancelMatch() {
if (gameType.value && teamSize.value) {
await matchGameAPI(false, gameType.value, teamSize.value);
}
uni.navigateBack()
}
async function onReceiveMessage(msg) { async function onReceiveMessage(msg) {
if (msg.type === MESSAGETYPESV2.MatchSuccess) { if (msg.type === MESSAGETYPESV2.MatchSuccess) {
matchSuccess.value = true; matchSuccess.value = true;
@@ -46,6 +51,7 @@ onMounted(() => {
keepScreenOn: true, keepScreenOn: true,
}); });
uni.$on("socket-inbox", onReceiveMessage); uni.$on("socket-inbox", onReceiveMessage);
uni.$on("cancelMatching", cancelMatch);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -53,9 +59,7 @@ onBeforeUnmount(() => {
keepScreenOn: false, keepScreenOn: false,
}); });
uni.$off("socket-inbox", onReceiveMessage); uni.$off("socket-inbox", onReceiveMessage);
if (gameType.value && teamSize.value && !matchSuccess.value) { uni.$off("cancelMatching", cancelMatch);
matchGameAPI(false, gameType.value, teamSize.value);
}
}); });
onShow(async () => { onShow(async () => {
@@ -65,9 +69,7 @@ onShow(async () => {
}); });
onHide(() => { onHide(() => {
if (gameType.value && teamSize.value && !matchSuccess.value) {
matchGameAPI(false, gameType.value, teamSize.value);
}
}); });
</script> </script>
+13 -11
View File
@@ -8,7 +8,7 @@ import {
bindDeviceAPI, bindDeviceAPI,
getMyDevicesAPI, getMyDevicesAPI,
unbindDeviceAPI, unbindDeviceAPI,
laserAimAPI, laserAimAPI, bindDeviceAPIV2,
} from "@/apis"; } from "@/apis";
import useStore from "@/store"; import useStore from "@/store";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
@@ -20,6 +20,7 @@ const { updateDevice } = store;
const { user, device } = storeToRefs(store); const { user, device } = storeToRefs(store);
const justBind = ref(false); const justBind = ref(false);
const calibration = ref(false); const calibration = ref(false);
const token = ref(null);
// 扫描二维码方法 // 扫描二维码方法
const handleScan = () => { const handleScan = () => {
@@ -30,13 +31,14 @@ const handleScan = () => {
scanType: ["qrCode"], scanType: ["qrCode"],
success: async (res) => { success: async (res) => {
try { try {
const base64Decode = (str) => { // const base64Decode = (str) => {
// 将 base64 转换为 utf8 字符串 // // 将 base64 转换为 utf8 字符串
const bytes = wx.base64ToArrayBuffer(str); // const bytes = wx.base64ToArrayBuffer(str);
return String.fromCharCode.apply(null, new Uint8Array(bytes)); // return String.fromCharCode.apply(null, new Uint8Array(bytes));
}; // };
//
addDevice.value = JSON.parse(base64Decode(res.result)); // addDevice.value = JSON.parse(base64Decode(res.result));
token.value = res.result;
confirmBindTip.value = true; confirmBindTip.value = true;
} catch (err) { } catch (err) {
uni.showToast({ uni.showToast({
@@ -57,8 +59,8 @@ const handleScan = () => {
}; };
const confirmBind = async () => { const confirmBind = async () => {
if (!justBind.value && addDevice.value.id) { if (!justBind.value && token.value) {
const result = await bindDeviceAPI(addDevice.value); const result = await bindDeviceAPIV2(token.value);
confirmBindTip.value = false; confirmBindTip.value = false;
if (result.binded) { if (result.binded) {
return uni.showToast({ return uni.showToast({
@@ -66,7 +68,7 @@ const confirmBind = async () => {
icon: "none", icon: "none",
}); });
} }
updateDevice(addDevice.value.id, addDevice.value.name); updateDevice(result.deviceId, result.name);
justBind.value = true; justBind.value = true;
uni.showToast({ uni.showToast({
title: "绑定成功", title: "绑定成功",
+9 -2
View File
@@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount } from "vue";
import { onShow } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue"; import Container from "@/components/Container.vue";
import ShootProgress from "@/components/ShootProgress.vue"; import ShootProgress from "@/components/ShootProgress.vue";
import BowTarget from "@/components/BowTarget.vue"; import BowTarget from "@/components/BowTarget.vue";
@@ -35,6 +35,13 @@ const practiseResult = ref({});
const practiseId = ref(""); const practiseId = ref("");
const showGuide = ref(false); const showGuide = ref(false);
const tips = ref(""); const tips = ref("");
const targetType = ref(1);
onLoad((options) => {
if (options.target) {
targetType.value = Number(options.target);
}
});
const onReady = async () => { const onReady = async () => {
await startPractiseAPI(); await startPractiseAPI();
@@ -84,7 +91,7 @@ onMounted(async () => {
}); });
uni.$on("socket-inbox", onReceiveMessage); uni.$on("socket-inbox", onReceiveMessage);
uni.$on("share-image", onClickShare); uni.$on("share-image", onClickShare);
const result = await createPractiseAPI(total, 120); const result = await createPractiseAPI(total, 120, targetType.value);
if (result) practiseId.value = result.id; if (result) practiseId.value = result.id;
}); });
+9 -1
View File
@@ -24,6 +24,7 @@ import { MESSAGETYPESV2 } from "@/constants";
import useStore from "@/store"; import useStore from "@/store";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import {onLoad} from "@dcloudio/uni-app";
const store = useStore(); const store = useStore();
const { user } = storeToRefs(store); const { user } = storeToRefs(store);
@@ -33,6 +34,13 @@ const total = 36;
const practiseResult = ref({}); const practiseResult = ref({});
const practiseId = ref(""); const practiseId = ref("");
const showGuide = ref(false); const showGuide = ref(false);
const targetType = ref(1);
onLoad((options) => {
if (options.target) {
targetType.value = Number(options.target);
}
});
const onReady = async () => { const onReady = async () => {
await startPractiseAPI(); await startPractiseAPI();
@@ -97,7 +105,7 @@ onMounted(async () => {
}); });
uni.$on("socket-inbox", onReceiveMessage); uni.$on("socket-inbox", onReceiveMessage);
uni.$on("share-image", onClickShare); uni.$on("share-image", onClickShare);
const result = await createPractiseAPI(total, 360); const result = await createPractiseAPI(total, 360, targetType.value);
if (result) practiseId.value = result.id; if (result) practiseId.value = result.id;
}); });
+16 -2
View File
@@ -4,6 +4,7 @@ import { onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue"; import Container from "@/components/Container.vue";
import Guide from "@/components/Guide.vue"; import Guide from "@/components/Guide.vue";
import Avatar from "@/components/Avatar.vue"; import Avatar from "@/components/Avatar.vue";
import TargetPicker from "@/components/TargetPicker.vue";
import { getPractiseDataAPI } from "@/apis"; import { getPractiseDataAPI } from "@/apis";
import { canEenter } from "@/util"; import { canEenter } from "@/util";
@@ -12,12 +13,20 @@ import { storeToRefs } from "pinia";
const store = useStore(); const store = useStore();
const { user, device, online } = storeToRefs(store); const { user, device, online } = storeToRefs(store);
const data = ref({}); const data = ref({});
const showTargetPicker = ref(false);
const pendingPractiseType = ref("");
const goPractise = async (type) => { const goPractise = async (type) => {
if (!canEenter(user.value, device.value, online.value)) return; if (!canEenter(user.value, device.value, online.value)) return;
// await uni.$checkAudio(); pendingPractiseType.value = type;
showTargetPicker.value = true;
};
const handleTargetConfirm = (target) => {
showTargetPicker.value = false;
const type = pendingPractiseType.value;
uni.navigateTo({ uni.navigateTo({
url: `/pages/practise-${type}`, url: `/pages/practise-${type}?target=${target}`,
}); });
}; };
@@ -88,6 +97,11 @@ onShow(async () => {
/> />
</view> </view>
</view> </view>
<TargetPicker
:show="showTargetPicker"
:onClose="() => (showTargetPicker = false)"
:onConfirm="handleTargetConfirm"
/>
</Container> </Container>
</template> </template>
+32 -8
View File
@@ -22,6 +22,19 @@ const getLvlName = (rankLvl, rankList = []) => {
return lvlName; return lvlName;
}; };
const getLvlNameByScore = (score, rankList = []) => {
if (!rankList) return;
let lvlName = "";
rankList.some((r, index) => {
if (r.upgrade_scores && r.upgrade_scores < score) {
lvlName = rankList[index].name;
return true;
}
return false;
});
return lvlName;
};
const getLvlImage = (rankLvl, rankList = []) => { const getLvlImage = (rankLvl, rankList = []) => {
if (!rankList) return; if (!rankList) return;
let lvlImage = ""; let lvlImage = "";
@@ -35,6 +48,19 @@ const getLvlImage = (rankLvl, rankList = []) => {
return lvlImage; return lvlImage;
}; };
const getLvlImageByScore = (score, rankList = []) => {
if (!rankList) return;
let lvlImage = "";
rankList.some((r, index) => {
if (r.upgrade_scores && r.upgrade_scores < score) {
lvlImage = rankList[index].icoin;
return true;
}
return false;
});
return lvlImage;
};
// 定义游戏相关的 store // 定义游戏相关的 store
export default defineStore("store", { export default defineStore("store", {
// 状态 // 状态
@@ -65,6 +91,9 @@ export default defineStore("store", {
// 方法 // 方法
actions: { actions: {
getLvlNameByScore(score) {
return getLvlNameByScore(score, this.config.randInfos);
},
getLvlName(rankLvl) { getLvlName(rankLvl) {
return getLvlName(rankLvl, this.config.randInfos); return getLvlName(rankLvl, this.config.randInfos);
}, },
@@ -79,14 +108,9 @@ export default defineStore("store", {
}, },
async updateUser(user = {}) { async updateUser(user = {}) {
this.user = { ...defaultUser, ...user }; this.user = { ...defaultUser, ...user };
if (this.user.rankLvl !== undefined) { this.user.lvlName = getLvlNameByScore(this.user.scores, this.config.randInfos)
this.user.lvlName = getLvlName( this.user.lvlImage = getLvlImageByScore(
this.user.rankLvl, this.user.scores,
this.config.randInfos
);
}
this.user.lvlImage = getLvlImage(
this.user.rankLvl,
this.config.randInfos this.config.randInfos
); );
}, },
+2 -2
View File
@@ -14,8 +14,8 @@ function createWebSocket(token, onMessage) {
switch (envVersion) { switch (envVersion) {
case "develop": // 开发版 case "develop": // 开发版
// url = "ws://192.168.1.30:8000/socket"; url = "ws://localhost:8000/socket";
url = "wss://apitest.shelingxingqiu.com/socket"; // url = "wss://apitest.shelingxingqiu.com/socket";
break; break;
case "trial": // 体验版 case "trial": // 体验版
url = "wss://apitest.shelingxingqiu.com/socket"; url = "wss://apitest.shelingxingqiu.com/socket";