Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88febb92e5 |
@@ -28,7 +28,6 @@
|
|||||||
const {
|
const {
|
||||||
updateUser,
|
updateUser,
|
||||||
updateOnline,
|
updateOnline,
|
||||||
updateDeviceBattery,
|
|
||||||
showDeviceChargingDialog,
|
showDeviceChargingDialog,
|
||||||
clearSessionState,
|
clearSessionState,
|
||||||
clearDevice
|
clearDevice
|
||||||
@@ -75,7 +74,6 @@
|
|||||||
const wasOnline = Boolean(online.value);
|
const wasOnline = Boolean(online.value);
|
||||||
const nextOnline = Boolean(data.online);
|
const nextOnline = Boolean(data.online);
|
||||||
updateOnline(nextOnline);
|
updateOnline(nextOnline);
|
||||||
updateDeviceBattery(nextOnline ? data?.battery ?? data?.power : null);
|
|
||||||
if (!device.value.deviceId || wasOnline === nextOnline) return;
|
if (!device.value.deviceId || wasOnline === nextOnline) return;
|
||||||
audioManager.play(nextOnline ? "设备已连接" : "设备连接已断开");
|
audioManager.play(nextOnline ? "设备已连接" : "设备连接已断开");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,10 +281,6 @@ export const getMyDevicesAPI = () => {
|
|||||||
return request("GET", "/user/device/getBindings");
|
return request("GET", "/user/device/getBindings");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDeviceDetailAPI = (deviceId) => {
|
|
||||||
return request("GET", `/user/device/getDetail?deviceId=${encodeURIComponent(deviceId)}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createPractiseAPI = (arrows, time, target) => {
|
export const createPractiseAPI = (arrows, time, target) => {
|
||||||
return request("POST", "/user/practice/create", {
|
return request("POST", "/user/practice/create", {
|
||||||
shootNumber: arrows,
|
shootNumber: arrows,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export const AUDIO_INTERRUPTION_BEGIN_EVENT = "audio-interruption-begin";
|
|||||||
export const AUDIO_INTERRUPTION_END_EVENT = "audio-interruption-end";
|
export const AUDIO_INTERRUPTION_END_EVENT = "audio-interruption-end";
|
||||||
export const RHYTHM_SHOOT_WINDOW_AUDIO_KEY = "请射箭";
|
export const RHYTHM_SHOOT_WINDOW_AUDIO_KEY = "请射箭";
|
||||||
export const STABILITY_START_AUDIO_KEY =
|
export const STABILITY_START_AUDIO_KEY =
|
||||||
"请在计时结束时能量条达到标记区域";
|
"请在计时结束前能量条达到百分百";
|
||||||
export const STABILITY_ENERGY_50_AUDIO_KEY = "加油!到达50%啦";
|
export const STABILITY_ENERGY_50_AUDIO_KEY = "加油!到达50%啦";
|
||||||
export const STABILITY_ENERGY_70_AUDIO_KEY =
|
export const STABILITY_ENERGY_70_AUDIO_KEY =
|
||||||
"70%啦!还差一点,胜利就在眼前";
|
"70%啦!还差一点,胜利就在眼前";
|
||||||
@@ -12,7 +12,7 @@ const TRAINING_START_AUDIO_KEY_MAP = Object.freeze({
|
|||||||
endurance: "请于计时前完成累计环数和箭量",
|
endurance: "请于计时前完成累计环数和箭量",
|
||||||
precision: "请射箭命中高亮区域",
|
precision: "请射箭命中高亮区域",
|
||||||
rhythm: "请在读条推进到标记区间内射箭",
|
rhythm: "请在读条推进到标记区间内射箭",
|
||||||
stability: STABILITY_START_AUDIO_KEY,
|
stability: "请在计时结束前能量条达到百分百",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getTrainingStartAudioKey = (trainingType, fallback = "") =>
|
export const getTrainingStartAudioKey = (trainingType, fallback = "") =>
|
||||||
@@ -176,7 +176,7 @@ export const audioFils = {
|
|||||||
练习开始:
|
练习开始:
|
||||||
"https://static.shelingxingqiu.com/attachment/2025-11-14/de88w0lmmt43nnfmoi.mp3",
|
"https://static.shelingxingqiu.com/attachment/2025-11-14/de88w0lmmt43nnfmoi.mp3",
|
||||||
[STABILITY_START_AUDIO_KEY]:
|
[STABILITY_START_AUDIO_KEY]:
|
||||||
"https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E5%9C%A8%E8%AE%A1%E6%97%B6%E7%BB%93%E6%9D%9F%E6%97%B6%E8%83%BD%E9%87%8F%E6%9D%A1%E8%BE%BE%E5%88%B0%E6%A0%87%E8%AE%B0%E5%8C%BA%E5%9F%9F.MP3",
|
"https://static.shelingxingqiu.com/shootaudio/%E8%AF%B7%E5%9C%A8%E8%AE%A1%E6%97%B6%E7%BB%93%E6%9D%9F%E5%89%8D%E8%83%BD%E9%87%8F%E6%9D%A1%E8%BE%BE%E5%88%B0%E7%99%BE%E5%88%86%E7%99%BE.MP3",
|
||||||
[STABILITY_ENERGY_50_AUDIO_KEY]:
|
[STABILITY_ENERGY_50_AUDIO_KEY]:
|
||||||
"https://static.shelingxingqiu.com/shootmini/static/audio0820/%E5%8A%A0%E6%B2%B9%EF%BC%81%E5%88%B0%E8%BE%BE50%25%E5%95%A6.MP3",
|
"https://static.shelingxingqiu.com/shootmini/static/audio0820/%E5%8A%A0%E6%B2%B9%EF%BC%81%E5%88%B0%E8%BE%BE50%25%E5%95%A6.MP3",
|
||||||
[STABILITY_ENERGY_70_AUDIO_KEY]:
|
[STABILITY_ENERGY_70_AUDIO_KEY]:
|
||||||
|
|||||||
@@ -81,13 +81,6 @@ const props = defineProps({
|
|||||||
src="https://static.shelingxingqiu.com/shootmini/static/app-bg9.png"
|
src="https://static.shelingxingqiu.com/shootmini/static/app-bg9.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<!-- 我的设备未绑定/绑定成功页面背景 -->
|
|
||||||
<image
|
|
||||||
class="bg-image"
|
|
||||||
v-if="type === 12"
|
|
||||||
src="../static/device-assets/my-device-unbound-background.png"
|
|
||||||
mode="widthFix"
|
|
||||||
/>
|
|
||||||
<image
|
<image
|
||||||
class="bg-image"
|
class="bg-image"
|
||||||
v-if="type === 10"
|
v-if="type === 10"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function handleTabClick(index) {
|
|||||||
}
|
}
|
||||||
if (index === 2) {
|
if (index === 2) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/device/device-intro",
|
url: "/pages/device-intro",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ const cancelMatching = async () => {
|
|||||||
const goCalibration = async () => {
|
const goCalibration = async () => {
|
||||||
await laserAimAPI();
|
await laserAimAPI();
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/device/calibration",
|
url: "/pages/calibration",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/audio-test"
|
"path": "pages/audio-test"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/calibration"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/about-us"
|
"path": "pages/about-us"
|
||||||
},
|
},
|
||||||
@@ -57,6 +60,12 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/match-page"
|
"path": "pages/match-page"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/my-device"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/device-intro"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/user"
|
"path": "pages/user"
|
||||||
},
|
},
|
||||||
@@ -98,6 +107,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/mine-bow-data"
|
"path": "pages/mine-bow-data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/ota-wifi",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
@@ -144,27 +159,6 @@
|
|||||||
{
|
{
|
||||||
"root": "pages/device",
|
"root": "pages/device",
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
|
||||||
"path": "my-device"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "device-bind-success"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "device-bind-failure"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "device-intro"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "ota-wifi",
|
|
||||||
"style": {
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "calibration"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "unbind-device"
|
"path": "unbind-device"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const onScrollView = (e) => {
|
|||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<navigator open-type="navigateBack">
|
<navigator open-type="navigateBack">
|
||||||
<image class="header-back" src="../../static/back.png" mode="widthFix" />
|
<image class="header-back" src="../static/back.png" mode="widthFix" />
|
||||||
</navigator>
|
</navigator>
|
||||||
<text
|
<text
|
||||||
:style="{ opacity: addBg ? 1 : 0, color: '#fff', fontWeight: 'bold' }"
|
:style="{ opacity: addBg ? 1 : 0, color: '#fff', fontWeight: 'bold' }"
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { bindDeviceAPIV2 } from "@/apis";
|
|
||||||
|
|
||||||
export function useDeviceBinding({
|
|
||||||
token,
|
|
||||||
confirmBindTip,
|
|
||||||
binding,
|
|
||||||
updateDevice,
|
|
||||||
deviceDetails,
|
|
||||||
refreshDeviceStatus,
|
|
||||||
}) {
|
|
||||||
const showBindFailurePage = () => {
|
|
||||||
uni.hideToast();
|
|
||||||
uni.navigateTo({
|
|
||||||
url: "/pages/device/device-bind-failure",
|
|
||||||
fail: (error) => {
|
|
||||||
console.error("打开绑定失败页失败", error);
|
|
||||||
uni.showToast({ title: "二维码不正确,请重新扫码", icon: "none" });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleScan = () => {
|
|
||||||
uni.scanCode({
|
|
||||||
onlyFromCamera: true,
|
|
||||||
scanType: ["qrCode"],
|
|
||||||
success: (result) => {
|
|
||||||
if (!result?.result) {
|
|
||||||
showBindFailurePage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
token.value = result.result;
|
|
||||||
confirmBindTip.value = true;
|
|
||||||
},
|
|
||||||
fail: (error) => {
|
|
||||||
const message = String(error?.errMsg || error?.message || "");
|
|
||||||
if (/cancel|取消/i.test(message)) return;
|
|
||||||
showBindFailurePage();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmBind = async () => {
|
|
||||||
if (!token.value || binding.value) return;
|
|
||||||
binding.value = true;
|
|
||||||
try {
|
|
||||||
const result = await bindDeviceAPIV2(token.value);
|
|
||||||
confirmBindTip.value = false;
|
|
||||||
if (result?.binded) {
|
|
||||||
uni.showToast({
|
|
||||||
title: "设备已绑定其他账号,请解绑后再绑定",
|
|
||||||
icon: "none",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const deviceId = String(result?.deviceId || "").trim();
|
|
||||||
const deviceName = String(result?.name || result?.deviceName || "").trim();
|
|
||||||
if (!deviceId || !deviceName) {
|
|
||||||
confirmBindTip.value = false;
|
|
||||||
token.value = "";
|
|
||||||
showBindFailurePage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const applyBoundDevice = () => {
|
|
||||||
updateDevice(deviceId, deviceName);
|
|
||||||
deviceDetails.value = result || {};
|
|
||||||
void refreshDeviceStatus();
|
|
||||||
};
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/device/device-bind-success?deviceId=${encodeURIComponent(deviceId)}`,
|
|
||||||
success: applyBoundDevice,
|
|
||||||
fail: (navigationError) => {
|
|
||||||
applyBoundDevice();
|
|
||||||
console.error("打开绑定成功页失败", navigationError);
|
|
||||||
uni.showToast({ title: "绑定成功,请返回查看设备", icon: "none" });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("绑定设备失败", error);
|
|
||||||
confirmBindTip.value = false;
|
|
||||||
token.value = "";
|
|
||||||
showBindFailurePage();
|
|
||||||
} finally {
|
|
||||||
binding.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { confirmBind, handleScan, showBindFailurePage };
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { computed, ref } from "vue";
|
|
||||||
import { getDeviceBatteryAPI, getMyDevicesAPI, unbindDeviceAPI } from "@/apis";
|
|
||||||
|
|
||||||
export const DEVICE_NAME_STORAGE_KEY = "device_name_overrides";
|
|
||||||
|
|
||||||
export function useDeviceStatus({
|
|
||||||
user,
|
|
||||||
device,
|
|
||||||
online,
|
|
||||||
updateDevice,
|
|
||||||
updateOnline,
|
|
||||||
clearDevice,
|
|
||||||
unbindDialogVisible,
|
|
||||||
}) {
|
|
||||||
const deviceStatus = ref({});
|
|
||||||
const deviceDetails = ref({});
|
|
||||||
|
|
||||||
const isDeviceOnline = computed(
|
|
||||||
() => deviceStatus.value.online === true || online.value === true
|
|
||||||
);
|
|
||||||
const statusText = computed(() => (isDeviceOnline.value ? "已连接" : "未连接"));
|
|
||||||
const statusClass = computed(() =>
|
|
||||||
isDeviceOnline.value ? "device-status--online" : "device-status--offline"
|
|
||||||
);
|
|
||||||
const battery = computed(() => {
|
|
||||||
const value = Number(
|
|
||||||
deviceStatus.value.battery ?? deviceStatus.value.power ?? 0
|
|
||||||
);
|
|
||||||
return Number.isFinite(value) && value > 0 ? Math.min(100, value) : 0;
|
|
||||||
});
|
|
||||||
const batteryText = computed(() =>
|
|
||||||
battery.value ? `${battery.value}%` : "暂无数据"
|
|
||||||
);
|
|
||||||
const networkText = computed(() => {
|
|
||||||
const netType = String(deviceStatus.value.netType || "").toLowerCase();
|
|
||||||
if (netType === "wifi") return "WiFi";
|
|
||||||
if (netType === "4g") return "4G";
|
|
||||||
return isDeviceOnline.value ? "在线" : "未连接";
|
|
||||||
});
|
|
||||||
const maskedDeviceId = computed(() => {
|
|
||||||
const id = String(device.value.deviceId || "");
|
|
||||||
if (!id) return "暂无设备编号";
|
|
||||||
if (id.length <= 3) return id;
|
|
||||||
return `${"*".repeat(Math.min(5, id.length - 3))}${id.slice(-3)}`;
|
|
||||||
});
|
|
||||||
const deviceRows = computed(() => [
|
|
||||||
{ label: "设备型号", value: deviceDetails.value.model || "射灵智能弓" },
|
|
||||||
{ label: "当前电量", value: batteryText.value },
|
|
||||||
{ label: "连接方式", value: networkText.value },
|
|
||||||
{ label: "设备编号", value: maskedDeviceId.value },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getDeviceNameOverrides = () => {
|
|
||||||
const value = uni.getStorageSync(DEVICE_NAME_STORAGE_KEY);
|
|
||||||
return value && typeof value === "object" ? value : {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshDeviceStatus = async () => {
|
|
||||||
if (!device.value.deviceId) return;
|
|
||||||
try {
|
|
||||||
const result = await getDeviceBatteryAPI();
|
|
||||||
deviceStatus.value = result || {};
|
|
||||||
updateOnline(result?.online === true);
|
|
||||||
} catch (error) {
|
|
||||||
deviceStatus.value = {};
|
|
||||||
console.log("获取设备状态失败", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const syncDeviceBinding = async () => {
|
|
||||||
if (!user.value.id) return;
|
|
||||||
try {
|
|
||||||
const devices = await getMyDevicesAPI();
|
|
||||||
if (Array.isArray(devices?.bindings) && devices.bindings.length > 0) {
|
|
||||||
const currentDevice = devices.bindings[0];
|
|
||||||
const nameOverrides = getDeviceNameOverrides();
|
|
||||||
deviceDetails.value = currentDevice;
|
|
||||||
updateDevice(
|
|
||||||
currentDevice.deviceId,
|
|
||||||
nameOverrides[currentDevice.deviceId] ||
|
|
||||||
currentDevice.deviceName ||
|
|
||||||
currentDevice.name ||
|
|
||||||
"我的智能弓"
|
|
||||||
);
|
|
||||||
await refreshDeviceStatus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
clearDevice();
|
|
||||||
deviceStatus.value = {};
|
|
||||||
deviceDetails.value = {};
|
|
||||||
} catch (error) {
|
|
||||||
console.log("同步设备绑定失败", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const unbindDevice = async () => {
|
|
||||||
if (!device.value.deviceId) return;
|
|
||||||
try {
|
|
||||||
await unbindDeviceAPI(device.value.deviceId);
|
|
||||||
uni.setStorageSync("calibration", false);
|
|
||||||
clearDevice();
|
|
||||||
deviceStatus.value = {};
|
|
||||||
deviceDetails.value = {};
|
|
||||||
unbindDialogVisible.value = false;
|
|
||||||
uni.showToast({ title: "解绑成功", icon: "success" });
|
|
||||||
} catch (error) {
|
|
||||||
console.error("解绑设备失败", error);
|
|
||||||
if (error?.type === "DEVICE_BIND_INVALID") {
|
|
||||||
clearDevice();
|
|
||||||
unbindDialogVisible.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
batteryText,
|
|
||||||
deviceDetails,
|
|
||||||
deviceRows,
|
|
||||||
getDeviceNameOverrides,
|
|
||||||
isDeviceOnline,
|
|
||||||
maskedDeviceId,
|
|
||||||
networkText,
|
|
||||||
refreshDeviceStatus,
|
|
||||||
statusClass,
|
|
||||||
statusText,
|
|
||||||
syncDeviceBinding,
|
|
||||||
unbindDevice,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
|
|
||||||
// 失败页由设备页进入,返回时优先回到上一页;页面栈异常时兜底回到设备页。
|
|
||||||
const goBackToDevicePage = () => {
|
|
||||||
const pages = getCurrentPages();
|
|
||||||
if (pages.length > 1) {
|
|
||||||
uni.navigateBack({ delta: 1 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.redirectTo({ url: "/pages/device/my-device" });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 返回设备页后沿用原有扫码绑定流程,不在结果页重复处理二维码数据。
|
|
||||||
const retryScan = () => {
|
|
||||||
const pages = getCurrentPages();
|
|
||||||
if (pages.length > 1) {
|
|
||||||
uni.navigateBack({
|
|
||||||
delta: 1,
|
|
||||||
success: () => uni.$emit("device-bind-retry-scan"),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.redirectTo({ url: "/pages/device/my-device?retryScan=1" });
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="device-bind-failure-page">
|
|
||||||
<Container
|
|
||||||
:bgType="12"
|
|
||||||
bgColor="transparent"
|
|
||||||
:onBack="goBackToDevicePage"
|
|
||||||
headerClass="bind-failure-header"
|
|
||||||
:scroll="false"
|
|
||||||
:usePageScroll="true"
|
|
||||||
>
|
|
||||||
<view class="bind-failure-page">
|
|
||||||
<view class="bind-failure-scene">
|
|
||||||
<view class="bind-failure-hero-wrap">
|
|
||||||
<view class="bind-failure-hero-shadow"></view>
|
|
||||||
<image
|
|
||||||
class="bind-failure-hero"
|
|
||||||
src="../../static/device-assets/device-bind-failure-hero.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<view class="bind-failure-message">
|
|
||||||
<text>二维码不正确,</text>
|
|
||||||
<text>仅支持扫描射灵智能弓箭的设备二维码。</text>
|
|
||||||
</view>
|
|
||||||
<view class="bind-failure-retry" @click="$clickSound(retryScan)">
|
|
||||||
<text>重新扫码</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.device-bind-failure-page {
|
|
||||||
position: relative;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-header {
|
|
||||||
position: relative;
|
|
||||||
z-index: 20;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 与绑定成功页共用固定画布,页面内的视觉尺寸全部按 375 宽设计稿换算为 rpx。 */
|
|
||||||
.bind-failure-page {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-scene {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-hero-wrap,
|
|
||||||
.bind-failure-message,
|
|
||||||
.bind-failure-retry {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-hero-wrap {
|
|
||||||
top: 568rpx;
|
|
||||||
left: 244rpx;
|
|
||||||
width: 260rpx;
|
|
||||||
height: 222rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-hero-shadow {
|
|
||||||
position: absolute;
|
|
||||||
left: 22rpx;
|
|
||||||
bottom: 0;
|
|
||||||
width: 238rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-hero {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 260rpx;
|
|
||||||
height: 222rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-message {
|
|
||||||
top: 824rpx;
|
|
||||||
left: 122rpx;
|
|
||||||
display: flex;
|
|
||||||
width: 504rpx;
|
|
||||||
min-height: 80rpx;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
color: #ffffff;
|
|
||||||
font-family: PingFang SC-Regular;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 40rpx;
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-failure-retry {
|
|
||||||
top: 960rpx;
|
|
||||||
left: 195rpx;
|
|
||||||
display: flex;
|
|
||||||
width: 360rpx;
|
|
||||||
height: 70rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 2rpx solid #ffd947;
|
|
||||||
border-radius: 78rpx;
|
|
||||||
color: #ffd947;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 26rpx;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed, ref } from "vue";
|
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import { getDeviceDetailAPI, getHomeData } from "@/apis";
|
|
||||||
|
|
||||||
const bindResult = ref({
|
|
||||||
isFirstBind: false,
|
|
||||||
expireDate: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const bindSuccessHero = computed(() =>
|
|
||||||
bindResult.value.isFirstBind
|
|
||||||
? "../../static/device-assets/device-bind-success-hero-first.png"
|
|
||||||
: "../../static/device-assets/device-bind-success-hero-nonfirst.png"
|
|
||||||
);
|
|
||||||
const bindRewardExpireDate = computed(() => bindResult.value.expireDate);
|
|
||||||
|
|
||||||
// 与会员页使用相同的到期时间格式,设备详情接口本身不提供会员有效期。
|
|
||||||
const formatVipDate = (value) => {
|
|
||||||
if (!value) return "";
|
|
||||||
const numericValue = Number(value);
|
|
||||||
const timestamp = Number.isNaN(numericValue)
|
|
||||||
? new Date(value).getTime()
|
|
||||||
: numericValue < 1000000000000
|
|
||||||
? numericValue * 1000
|
|
||||||
: numericValue;
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
if (Number.isNaN(date.getTime())) return "";
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
||||||
const day = String(date.getDate()).padStart(2, "0");
|
|
||||||
return `${year}-${month}-${day}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadBindResult = async (deviceId) => {
|
|
||||||
if (!deviceId) return;
|
|
||||||
try {
|
|
||||||
const data = await getDeviceDetailAPI(deviceId);
|
|
||||||
if (data?.detail?.isFirstBind !== true) return;
|
|
||||||
bindResult.value.isFirstBind = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const homeData = await getHomeData();
|
|
||||||
bindResult.value.expireDate = formatVipDate(
|
|
||||||
homeData?.user?.normalVipExpiredAt
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("获取赠送会员有效期失败", error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("获取设备详情失败", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toFirstTryPage = () => {
|
|
||||||
uni.navigateTo({ url: "/pages/first-try" });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 成功页由设备页 navigateTo 进入,优先返回上一页;页面栈异常时兜底回到设备页。
|
|
||||||
const goBackToDevicePage = () => {
|
|
||||||
const pages = getCurrentPages();
|
|
||||||
if (pages.length > 1) {
|
|
||||||
uni.navigateBack({ delta: 1 });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.redirectTo({ url: "/pages/device/my-device" });
|
|
||||||
};
|
|
||||||
|
|
||||||
onLoad((options = {}) => {
|
|
||||||
bindResult.value = {
|
|
||||||
isFirstBind: false,
|
|
||||||
expireDate: "",
|
|
||||||
};
|
|
||||||
void loadBindResult(options.deviceId);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="device-bind-success-page">
|
|
||||||
<Container
|
|
||||||
:bgType="12"
|
|
||||||
bgColor="transparent"
|
|
||||||
:onBack="goBackToDevicePage"
|
|
||||||
headerClass="bind-success-header"
|
|
||||||
:scroll="false"
|
|
||||||
:usePageScroll="true"
|
|
||||||
>
|
|
||||||
<view class="bind-success-page">
|
|
||||||
<view
|
|
||||||
class="bind-success-scene"
|
|
||||||
:class="{ 'bind-success-scene--first': bindResult.isFirstBind }"
|
|
||||||
>
|
|
||||||
<image
|
|
||||||
class="bind-success-confetti"
|
|
||||||
src="../../static/device-assets/device-bind-success-confetti.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
<image class="bind-success-hero" :src="bindSuccessHero" mode="aspectFit" />
|
|
||||||
<image
|
|
||||||
class="bind-success-title-image"
|
|
||||||
src="../../static/device-assets/device-bind-success-title.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
<view v-if="bindResult.isFirstBind" class="bind-reward-card">
|
|
||||||
<image
|
|
||||||
class="bind-reward-gift"
|
|
||||||
src="../../static/device-assets/device-bind-success-reward-gift.png"
|
|
||||||
mode="aspectFit"
|
|
||||||
/>
|
|
||||||
<text class="bind-reward-text">
|
|
||||||
新设备首次绑定礼包:
|
|
||||||
<text class="bind-reward-highlight">6个月射灵会员</text>
|
|
||||||
已自动发放至本账号<text v-if="bindRewardExpireDate">,有效期{{ bindRewardExpireDate }}</text>。
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="bind-success-tutorial" @click="$clickSound(toFirstTryPage)">
|
|
||||||
<text>立即查看新手教程</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.device-bind-success-page {
|
|
||||||
position: relative;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-header {
|
|
||||||
position: relative;
|
|
||||||
z-index: 20;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 绑定结果页使用固定画布,背景和前景素材按蓝湖 375 x 812 画布定位。 */
|
|
||||||
.bind-success-page {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-scene {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-confetti,
|
|
||||||
.bind-success-hero,
|
|
||||||
.bind-success-title-image,
|
|
||||||
.bind-reward-card,
|
|
||||||
.bind-success-tutorial {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-confetti {
|
|
||||||
top: 488rpx;
|
|
||||||
left: 78rpx;
|
|
||||||
width: 588rpx;
|
|
||||||
height: 508rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-hero {
|
|
||||||
top: 568rpx;
|
|
||||||
left: 244rpx;
|
|
||||||
width: 260rpx;
|
|
||||||
height: 222rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-scene--first .bind-success-confetti {
|
|
||||||
top: 314rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-scene--first .bind-success-hero {
|
|
||||||
top: 400rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-title-image {
|
|
||||||
top: 828rpx;
|
|
||||||
left: 216rpx;
|
|
||||||
width: 316rpx;
|
|
||||||
height: 70rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-scene--first .bind-success-title-image {
|
|
||||||
top: 660rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-reward-card {
|
|
||||||
top: 768rpx;
|
|
||||||
left: 170rpx;
|
|
||||||
width: 508rpx;
|
|
||||||
height: 152rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 16rpx 16rpx 16rpx 90rpx;
|
|
||||||
border-radius: 16rpx 64rpx 16rpx 64rpx;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-reward-gift {
|
|
||||||
position: absolute;
|
|
||||||
top: -18rpx;
|
|
||||||
left: -100rpx;
|
|
||||||
width: 178rpx;
|
|
||||||
height: 176rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-reward-text {
|
|
||||||
display: block;
|
|
||||||
width: 380rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-family: PingFang SC-Regular;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-reward-highlight {
|
|
||||||
color: #ffd947;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bind-success-tutorial {
|
|
||||||
top: 960rpx;
|
|
||||||
left: 195rpx;
|
|
||||||
display: flex;
|
|
||||||
width: 360rpx;
|
|
||||||
height: 70rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 2rpx solid #ffd947;
|
|
||||||
border-radius: 78rpx;
|
|
||||||
color: #ffd947;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 26rpx;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,961 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
|
||||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
|
||||||
import Container from "@/components/Container.vue";
|
|
||||||
import ScreenHint from "@/components/ScreenHint.vue";
|
|
||||||
import ModalDialog from "@/components/ModalDialog.vue";
|
|
||||||
import { laserAimAPI } from "@/apis";
|
|
||||||
import useStore from "@/store";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { useDeviceBinding } from "./composables/useDeviceBinding";
|
|
||||||
import {
|
|
||||||
DEVICE_NAME_STORAGE_KEY,
|
|
||||||
useDeviceStatus,
|
|
||||||
} from "./composables/useDeviceStatus";
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
const { updateDevice, updateOnline, clearDevice } = store;
|
|
||||||
const { user, device, online } = storeToRefs(store);
|
|
||||||
|
|
||||||
const showTip = ref(false);
|
|
||||||
const confirmBindTip = ref(false);
|
|
||||||
const unbindDialogVisible = ref(false);
|
|
||||||
const nameEditorVisible = ref(false);
|
|
||||||
const qrVisible = ref(false);
|
|
||||||
const qrSaved = ref(false);
|
|
||||||
const editingName = ref("");
|
|
||||||
const token = ref("");
|
|
||||||
const binding = ref(false);
|
|
||||||
const retryScanOnShow = ref(false);
|
|
||||||
const calibration = ref(false);
|
|
||||||
|
|
||||||
const {
|
|
||||||
deviceDetails,
|
|
||||||
deviceRows,
|
|
||||||
getDeviceNameOverrides,
|
|
||||||
isDeviceOnline,
|
|
||||||
maskedDeviceId,
|
|
||||||
refreshDeviceStatus,
|
|
||||||
statusClass,
|
|
||||||
statusText,
|
|
||||||
syncDeviceBinding,
|
|
||||||
unbindDevice,
|
|
||||||
} = useDeviceStatus({
|
|
||||||
user,
|
|
||||||
device,
|
|
||||||
online,
|
|
||||||
updateDevice,
|
|
||||||
updateOnline,
|
|
||||||
clearDevice,
|
|
||||||
unbindDialogVisible,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { confirmBind, handleScan } = useDeviceBinding({
|
|
||||||
token,
|
|
||||||
confirmBindTip,
|
|
||||||
binding,
|
|
||||||
updateDevice,
|
|
||||||
deviceDetails,
|
|
||||||
refreshDeviceStatus,
|
|
||||||
});
|
|
||||||
|
|
||||||
const qrImageUrl = computed(
|
|
||||||
() =>
|
|
||||||
deviceDetails.value.qrCode ||
|
|
||||||
deviceDetails.value.qrcode ||
|
|
||||||
deviceDetails.value.qrUrl ||
|
|
||||||
"../../static/device-assets/my-device-qrcode.png"
|
|
||||||
);
|
|
||||||
const isScanPage = computed(() => !device.value.deviceId && !qrVisible.value);
|
|
||||||
const containerBgType = computed(() =>
|
|
||||||
isScanPage.value ? 12 : 0
|
|
||||||
);
|
|
||||||
const containerBgColor = computed(() =>
|
|
||||||
containerBgType.value === 12 || containerBgType.value === -1
|
|
||||||
? "transparent"
|
|
||||||
: "#050b19"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 解绑前展示统一确认弹窗,避免误触解除绑定。
|
|
||||||
const openUnbindDialog = () => {
|
|
||||||
unbindDialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeUnbindDialog = () => {
|
|
||||||
unbindDialogVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openNameEditor = () => {
|
|
||||||
editingName.value = device.value.deviceName || "我的智能弓";
|
|
||||||
nameEditorVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeNameEditor = () => {
|
|
||||||
nameEditorVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 当前接口列表没有设备改名接口,先更新页面和本地缓存,后端接口接入时可替换为请求。
|
|
||||||
const confirmName = () => {
|
|
||||||
const name = String(editingName.value || "").trim();
|
|
||||||
if (!name) {
|
|
||||||
uni.showToast({ title: "请输入设备名", icon: "none" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (name.length > 10 || !/^[\u4e00-\u9fa5A-Za-z0-9_-]+$/.test(name)) {
|
|
||||||
uni.showToast({ title: "设备名格式不正确", icon: "none" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
updateDevice(device.value.deviceId, name);
|
|
||||||
deviceDetails.value = { ...deviceDetails.value, deviceName: name };
|
|
||||||
const nameOverrides = getDeviceNameOverrides();
|
|
||||||
nameOverrides[device.value.deviceId] = name;
|
|
||||||
uni.setStorageSync(DEVICE_NAME_STORAGE_KEY, nameOverrides);
|
|
||||||
nameEditorVisible.value = false;
|
|
||||||
uni.showToast({ title: "设备名已更新", icon: "success" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const toDeviceIntroPage = () => {
|
|
||||||
uni.navigateTo({ url: "/pages/device/device-intro" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const joinWifi = () => {
|
|
||||||
uni.navigateTo({ url: "/pages/device/ota-wifi" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const goFirmwareUpdate = () => {
|
|
||||||
if (!isDeviceOnline.value) {
|
|
||||||
uni.showToast({ title: "请先开启智能弓", icon: "none" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uni.navigateTo({ url: "/pages/device/ota-wifi" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const goCalibration = async () => {
|
|
||||||
try {
|
|
||||||
await laserAimAPI();
|
|
||||||
uni.navigateTo({ url: "/pages/device/calibration" });
|
|
||||||
} catch (error) {
|
|
||||||
uni.showToast({ title: "设备未连接,暂时无法调瞄", icon: "none" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyEmail = () => {
|
|
||||||
uni.setClipboardData({
|
|
||||||
data: "shelingxingqiu@163.com",
|
|
||||||
success: () => uni.showToast({ title: "邮箱已复制", icon: "success" }),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openQr = () => {
|
|
||||||
qrSaved.value = false;
|
|
||||||
qrVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeTip = () => {
|
|
||||||
showTip.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeConfirmBindTip = () => {
|
|
||||||
confirmBindTip.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeQr = () => {
|
|
||||||
qrVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 保存二维码到相册;远程二维码先下载到临时目录,失败时保留长按保存提示。
|
|
||||||
const saveQrCode = async () => {
|
|
||||||
let filePath = qrImageUrl.value;
|
|
||||||
try {
|
|
||||||
if (/^https?:\/\//.test(filePath)) {
|
|
||||||
filePath = await new Promise((resolve, reject) => {
|
|
||||||
uni.downloadFile({
|
|
||||||
url: filePath,
|
|
||||||
success: (result) =>
|
|
||||||
result.statusCode === 200
|
|
||||||
? resolve(result.tempFilePath)
|
|
||||||
: reject(new Error("二维码下载失败")),
|
|
||||||
fail: reject,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
uni.saveImageToPhotosAlbum({ success: resolve, fail: reject, filePath });
|
|
||||||
});
|
|
||||||
qrSaved.value = true;
|
|
||||||
uni.showToast({ title: "已保存至相册", icon: "success" });
|
|
||||||
} catch (error) {
|
|
||||||
uni.showToast({ title: "请长按二维码保存", icon: "none" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onLoad((options = {}) => {
|
|
||||||
retryScanOnShow.value = options.retryScan === "1";
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
uni.$on("device-bind-retry-scan", handleScan);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
uni.$off("device-bind-retry-scan", handleScan);
|
|
||||||
});
|
|
||||||
|
|
||||||
onShow(async () => {
|
|
||||||
calibration.value = uni.getStorageSync("calibration");
|
|
||||||
await syncDeviceBinding();
|
|
||||||
if (retryScanOnShow.value) {
|
|
||||||
retryScanOnShow.value = false;
|
|
||||||
handleScan();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="my-device-page" :class="{ 'my-device-page--scan': isScanPage }">
|
|
||||||
<Container
|
|
||||||
:bgType="containerBgType"
|
|
||||||
:bgColor="containerBgColor"
|
|
||||||
:scroll="!isScanPage"
|
|
||||||
:usePageScroll="false"
|
|
||||||
>
|
|
||||||
<view v-if="qrVisible" class="qr-page" @click="closeQr">
|
|
||||||
<view class="qr-canvas" @click.stop>
|
|
||||||
<view class="qr-corner qr-corner--top"></view>
|
|
||||||
<view class="qr-corner qr-corner--bottom"></view>
|
|
||||||
<text class="qr-title">射灵智能弓箭二维码</text>
|
|
||||||
<image class="qr-image" :src="qrImageUrl" mode="aspectFit" show-menu-by-longpress />
|
|
||||||
<text v-if="qrSaved" class="qr-device-id">设备ID:{{ maskedDeviceId }}</text>
|
|
||||||
<view v-else class="qr-save-button" @click="saveQrCode">
|
|
||||||
<text>保存至相册</text>
|
|
||||||
</view>
|
|
||||||
<text class="qr-description">
|
|
||||||
该二维码为当前绑定弓箭的二维码,你可以截图保存到相册,以便当二维码丢失或不在身边时,可以扫描二维码进行设备绑定。
|
|
||||||
</text>
|
|
||||||
<text class="qr-note">注:解除绑定后将无法查看该二维码。</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-else-if="!device.deviceId" class="scan-code">
|
|
||||||
<view class="unbound-content">
|
|
||||||
<button class="scan-entry" hover-class="none" @click="$clickSound(handleScan)">
|
|
||||||
<image src="../../static/device-assets/my-device-unbound-scan.png" mode="aspectFit" />
|
|
||||||
</button>
|
|
||||||
<view class="scan-title">
|
|
||||||
<text>请扫描</text>
|
|
||||||
<text class="highlight-text">射灵智能弓箭</text>
|
|
||||||
<text>设备上的二维码</text>
|
|
||||||
</view>
|
|
||||||
<view class="benefit-copy">
|
|
||||||
<text>新设备首次绑定账号可获赠</text>
|
|
||||||
<text class="highlight-text">6个月射灵会员礼包</text>
|
|
||||||
<text>,</text>
|
|
||||||
<text>该礼包仅可使用一次,请确保当前登录账号为您本人账号。</text>
|
|
||||||
</view>
|
|
||||||
<button class="help-link" hover-class="none" @click="showTip = true">
|
|
||||||
<text>没找到二维码或遇到问题></text>
|
|
||||||
</button>
|
|
||||||
<button class="product-link" hover-class="none" @click="toDeviceIntroPage">
|
|
||||||
<text>还没有射灵智能弓箭?点我获取></text>
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-else class="device-page">
|
|
||||||
|
|
||||||
<view class="device-card">
|
|
||||||
<view class="device-card-header">
|
|
||||||
<view class="device-status" :class="statusClass">
|
|
||||||
<view class="status-dot"></view>
|
|
||||||
<text>{{ statusText }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="edit-name" @click="openNameEditor">修改设备名</view>
|
|
||||||
</view>
|
|
||||||
<view class="device-identity">
|
|
||||||
<image class="device-avatar" src="../../static/device-assets/my-device-avatar.png" mode="aspectFill" />
|
|
||||||
<view class="device-identity-text">
|
|
||||||
<text class="device-name">{{ device.deviceName || "我的智能弓" }}</text>
|
|
||||||
<text class="device-id">{{ maskedDeviceId }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="device-stats">
|
|
||||||
<view v-for="item in deviceRows" :key="item.label" class="device-stat">
|
|
||||||
<text class="device-stat-label">{{ item.label }}</text>
|
|
||||||
<text class="device-stat-value">{{ item.value }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="device-actions">
|
|
||||||
<view class="action-item" @click="toDeviceIntroPage">
|
|
||||||
<text class="action-icon">⌁</text>
|
|
||||||
<text>设备介绍</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-item" @click="goCalibration">
|
|
||||||
<text class="action-icon">⌖</text>
|
|
||||||
<text>弓箭调瞄</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-item" @click="goFirmwareUpdate">
|
|
||||||
<text class="action-icon">↻</text>
|
|
||||||
<text>固件更新</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-item" @click="joinWifi">
|
|
||||||
<text class="action-icon">◌</text>
|
|
||||||
<text>WIFI设置</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-item" @click="openQr">
|
|
||||||
<text class="action-icon">▦</text>
|
|
||||||
<text>设备二维码</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-item action-item--danger" @click="openUnbindDialog">
|
|
||||||
<text class="action-icon">⊗</text>
|
|
||||||
<text>解除绑定</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="calibration" class="calibration-tip">
|
|
||||||
<text>如有场地或距离变化,请重新校准以保证智能弓射箭精准度</text>
|
|
||||||
<view @click="goCalibration">重新校准</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<ScreenHint mode="square" :show="showTip" :onClose="closeTip">
|
|
||||||
<view class="scan-tips">
|
|
||||||
<text class="scan-tips-title">扫码绑定射灵弓箭</text>
|
|
||||||
<text class="scan-tips-subtitle">设备底部二维码样例</text>
|
|
||||||
<image
|
|
||||||
class="scan-tips-qr"
|
|
||||||
src="../../static/device-assets/my-device-unbound-qr-sample.png"
|
|
||||||
mode="widthFix"
|
|
||||||
/>
|
|
||||||
<text class="scan-tips-note">【注】已被绑定的弓箭无法再次绑定。</text>
|
|
||||||
<view class="scan-tips-contact">
|
|
||||||
<text>如有任何疑问,请随时联系:</text>
|
|
||||||
<button hover-class="none" @click="copyEmail">shelingxingqiu@163.com</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</ScreenHint>
|
|
||||||
|
|
||||||
<ScreenHint :show="confirmBindTip" :onClose="closeConfirmBindTip">
|
|
||||||
<view class="confirm-bind">
|
|
||||||
<text>智能弓箭和系统账号需一一对应,你确定要将当前登录用户账号绑定这把弓箭吗?绑定后不可随意更换。</text>
|
|
||||||
<view class="confirm-actions">
|
|
||||||
<view
|
|
||||||
class="primary-action"
|
|
||||||
:class="{ 'primary-action--disabled': binding }"
|
|
||||||
@click="confirmBind"
|
|
||||||
>
|
|
||||||
{{ binding ? "绑定中..." : "确认绑定" }}
|
|
||||||
</view>
|
|
||||||
<view class="secondary-action" @click="confirmBindTip = false">取消</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</ScreenHint>
|
|
||||||
|
|
||||||
<ModalDialog
|
|
||||||
:show="unbindDialogVisible"
|
|
||||||
title="解除设备绑定"
|
|
||||||
content="解除绑定后,将无法使用智能弓箭进行对战,确定继续吗?"
|
|
||||||
cancelText="取消"
|
|
||||||
confirmText="解除绑定"
|
|
||||||
:onCancel="closeUnbindDialog"
|
|
||||||
:onConfirm="unbindDevice"
|
|
||||||
></ModalDialog>
|
|
||||||
|
|
||||||
<view v-if="nameEditorVisible" class="name-mask" @click="closeNameEditor">
|
|
||||||
<view class="name-sheet" @click.stop>
|
|
||||||
<view class="name-input-row">
|
|
||||||
<input
|
|
||||||
v-model="editingName"
|
|
||||||
class="name-input"
|
|
||||||
focus
|
|
||||||
maxlength="10"
|
|
||||||
confirm-type="done"
|
|
||||||
placeholder="请输入设备名(最长10个汉字)"
|
|
||||||
placeholder-class="name-placeholder"
|
|
||||||
@confirm="confirmName"
|
|
||||||
/>
|
|
||||||
<view class="name-confirm" @click="confirmName">确定</view>
|
|
||||||
</view>
|
|
||||||
<text class="name-helper">仅支持中文、英文、数字、下划线、减号</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</Container>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.my-device-page {
|
|
||||||
position: relative;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-device-page--scan {
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-code,
|
|
||||||
.device-page {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: calc(100vh - 100rpx);
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-code {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding-top: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unbound-content {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: calc(100vh - 100rpx);
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 170rpx 32rpx 160rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-entry,
|
|
||||||
.help-link,
|
|
||||||
.product-link {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-entry::after,
|
|
||||||
.help-link::after,
|
|
||||||
.product-link::after {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-entry {
|
|
||||||
display: flex;
|
|
||||||
width: 276rpx;
|
|
||||||
height: 276rpx;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-entry image {
|
|
||||||
width: 276rpx;
|
|
||||||
height: 276rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 48rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 40rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.benefit-copy {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 20rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.74);
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 44rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-link {
|
|
||||||
margin-top: 72rpx;
|
|
||||||
color: #ffd947;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-link {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 104rpx;
|
|
||||||
bottom: calc(env(safe-area-inset-bottom) + 104rpx);
|
|
||||||
left: 0;
|
|
||||||
z-index: 2;
|
|
||||||
width: 100%;
|
|
||||||
color: #ffd947;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight-text {
|
|
||||||
color: #fed847;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-page {
|
|
||||||
padding: 36rpx 32rpx 80rpx;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-card {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 24rpx;
|
|
||||||
background: linear-gradient(145deg, rgba(45, 54, 76, 0.96), rgba(19, 25, 43, 0.98));
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-card {
|
|
||||||
padding: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-card-header,
|
|
||||||
.device-identity,
|
|
||||||
.device-stats,
|
|
||||||
.confirm-actions,
|
|
||||||
.name-input-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-card-header {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-dot {
|
|
||||||
width: 14rpx;
|
|
||||||
height: 14rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status--online {
|
|
||||||
color: #62e4a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status--online .status-dot {
|
|
||||||
background: #62e4a1;
|
|
||||||
box-shadow: 0 0 16rpx rgba(98, 228, 161, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status--offline {
|
|
||||||
color: #ff6e6e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status--offline .status-dot {
|
|
||||||
background: #ff6e6e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-name {
|
|
||||||
color: #fed847;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-identity {
|
|
||||||
margin-top: 42rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-avatar {
|
|
||||||
width: 144rpx;
|
|
||||||
height: 144rpx;
|
|
||||||
margin-right: 26rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-identity-text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-name {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-id {
|
|
||||||
margin-top: 14rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.52);
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-stats {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 40rpx;
|
|
||||||
padding-top: 28rpx;
|
|
||||||
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-stat {
|
|
||||||
width: 48%;
|
|
||||||
margin-bottom: 22rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-stat-label,
|
|
||||||
.device-stat-value {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-stat-label {
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
font-size: 22rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-stat-value {
|
|
||||||
margin-top: 8rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-actions {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 28rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
background: rgba(30, 38, 58, 0.88);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
display: flex;
|
|
||||||
width: 33.3333%;
|
|
||||||
height: 142rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-item--danger {
|
|
||||||
color: #ff9898;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-icon {
|
|
||||||
display: flex;
|
|
||||||
width: 56rpx;
|
|
||||||
height: 56rpx;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
border: 1rpx solid currentColor;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calibration-tip {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
background: rgba(40, 127, 255, 0.12);
|
|
||||||
color: rgba(255, 255, 255, 0.65);
|
|
||||||
font-size: 22rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calibration-tip > text {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calibration-tip > view {
|
|
||||||
margin-left: 18rpx;
|
|
||||||
color: #5bbdff;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-action,
|
|
||||||
.secondary-action {
|
|
||||||
display: flex;
|
|
||||||
min-width: 150rpx;
|
|
||||||
height: 68rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 34rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-action {
|
|
||||||
color: #111111;
|
|
||||||
background: #fed847;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-action--disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-action {
|
|
||||||
color: #ffffff;
|
|
||||||
background: rgba(255, 255, 255, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips {
|
|
||||||
display: flex;
|
|
||||||
width: 90%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-top: 20%;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips-title {
|
|
||||||
color: #fed847;
|
|
||||||
font-size: 32rpx;
|
|
||||||
line-height: 42rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips-subtitle {
|
|
||||||
margin-top: 14rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.72);
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips-qr {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 12rpx;
|
|
||||||
border-radius: 14rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips-note {
|
|
||||||
margin-top: 16rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips-contact {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-top: 12rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tips button {
|
|
||||||
padding: 0;
|
|
||||||
color: #39a8ff;
|
|
||||||
background: transparent;
|
|
||||||
font-size: 30rpx;
|
|
||||||
line-height: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-bind {
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 42rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-actions {
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-actions > view {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-page {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
min-height: calc(100vh - 100rpx);
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
background: radial-gradient(circle at 50% 46%, rgba(95, 78, 58, 0.9), rgba(8, 10, 25, 1) 70%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-page::before,
|
|
||||||
.qr-page::after {
|
|
||||||
position: absolute;
|
|
||||||
width: 620rpx;
|
|
||||||
height: 1rpx;
|
|
||||||
content: "";
|
|
||||||
background: rgba(255, 255, 255, 0.07);
|
|
||||||
box-shadow: 0 180rpx rgba(255, 255, 255, 0.05), 0 360rpx rgba(255, 255, 255, 0.04), 0 540rpx rgba(255, 255, 255, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-page::before {
|
|
||||||
top: 380rpx;
|
|
||||||
left: -160rpx;
|
|
||||||
transform: rotate(48deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-page::after {
|
|
||||||
top: 420rpx;
|
|
||||||
right: -160rpx;
|
|
||||||
transform: rotate(-48deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-corner {
|
|
||||||
position: absolute;
|
|
||||||
width: 300rpx;
|
|
||||||
height: 300rpx;
|
|
||||||
background: #ffeb00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-corner--top {
|
|
||||||
top: -220rpx;
|
|
||||||
right: -170rpx;
|
|
||||||
transform: rotate(42deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-corner--bottom {
|
|
||||||
bottom: -220rpx;
|
|
||||||
left: -170rpx;
|
|
||||||
transform: rotate(42deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-canvas {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
min-height: calc(100vh - 100rpx);
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 260rpx 62rpx 70rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-title {
|
|
||||||
color: #ffe846;
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-image {
|
|
||||||
width: 432rpx;
|
|
||||||
height: 432rpx;
|
|
||||||
margin-top: 48rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-device-id {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.62);
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-save-button {
|
|
||||||
display: flex;
|
|
||||||
width: 360rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 34rpx;
|
|
||||||
border: 1rpx solid #e8c840;
|
|
||||||
border-radius: 36rpx;
|
|
||||||
color: #ffe846;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-description,
|
|
||||||
.qr-note {
|
|
||||||
width: 100%;
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
font-size: 22rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-description {
|
|
||||||
margin-top: 42rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-note {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
color: rgba(255, 232, 70, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
display: flex;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: flex-end;
|
|
||||||
background: rgba(0, 0, 0, 0.68);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-sheet {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 34rpx 32rpx 48rpx;
|
|
||||||
border-top: 1rpx solid rgba(255, 226, 153, 0.4);
|
|
||||||
border-radius: 28rpx 28rpx 0 0;
|
|
||||||
background: linear-gradient(180deg, #57462b, #2c241b);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input-row {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-input {
|
|
||||||
flex: 1;
|
|
||||||
height: 76rpx;
|
|
||||||
padding: 0 22rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 14rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
background: rgba(0, 0, 0, 0.22);
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-confirm {
|
|
||||||
display: flex;
|
|
||||||
width: 132rpx;
|
|
||||||
height: 76rpx;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-left: 18rpx;
|
|
||||||
border-radius: 14rpx;
|
|
||||||
color: #1b160d;
|
|
||||||
background: #fed847;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name-helper {
|
|
||||||
display: block;
|
|
||||||
margin-top: 18rpx;
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
font-size: 22rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onMounted, onUnmounted, ref, watch} from "vue";
|
import {onMounted, onUnmounted, ref, watch} from "vue";
|
||||||
import {onHide, onShareAppMessage, onShareTimeline, onShow} 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 UserHeader from "@/components/UserHeader.vue";
|
import UserHeader from "@/components/UserHeader.vue";
|
||||||
@@ -34,123 +34,12 @@ const {
|
|||||||
getLvlName,
|
getLvlName,
|
||||||
getLvlNameByScore,
|
getLvlNameByScore,
|
||||||
updateOnline,
|
updateOnline,
|
||||||
updateDeviceBattery,
|
|
||||||
} = store;
|
} = store;
|
||||||
const {user, device, online, deviceBattery, game} = storeToRefs(store);
|
const {user, device, online, game} = storeToRefs(store);
|
||||||
|
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
const showGuide = ref(false);
|
const showGuide = ref(false);
|
||||||
const scoreRankList = ref([]);
|
const scoreRankList = ref([]);
|
||||||
const HOME_DEVICE_STATUS_POLL_INTERVAL = 10000;
|
|
||||||
let isHomePageVisible = false;
|
|
||||||
let homeDeviceStatusTimer = null;
|
|
||||||
let isHomeDeviceStatusRequesting = false;
|
|
||||||
|
|
||||||
// 设备状态接口可能返回布尔值、数字或字符串,统一成 true / false / null。
|
|
||||||
// null 表示接口没有给出可判断的状态,调用方应保留已有状态。
|
|
||||||
const normalizeDeviceOnline = (value) => {
|
|
||||||
if (typeof value === "boolean") return value;
|
|
||||||
if (typeof value === "number") return value === 1;
|
|
||||||
|
|
||||||
const normalized = String(value ?? "").trim().toLowerCase();
|
|
||||||
if (["true", "1", "online", "connected"].includes(normalized)) return true;
|
|
||||||
if (["false", "0", "offline", "disconnected"].includes(normalized)) return false;
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 将设备状态接口响应统一同步到首页 Store,离线时清空无效电量。
|
|
||||||
const applyHomeDeviceStatus = (data) => {
|
|
||||||
const normalizedOnline = normalizeDeviceOnline(data?.online);
|
|
||||||
if (normalizedOnline !== null) {
|
|
||||||
updateOnline(normalizedOnline);
|
|
||||||
}
|
|
||||||
if (normalizedOnline === false) {
|
|
||||||
updateDeviceBattery(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const battery = data?.battery ?? data?.power;
|
|
||||||
if (battery !== undefined && battery !== null) {
|
|
||||||
updateDeviceBattery(battery);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 刷新首页设备在线状态和电量;上一轮未结束时跳过,避免请求堆积。
|
|
||||||
const refreshHomeDeviceStatus = async () => {
|
|
||||||
if (
|
|
||||||
!isHomePageVisible ||
|
|
||||||
!user.value?.id ||
|
|
||||||
!device.value?.deviceId ||
|
|
||||||
isHomeDeviceStatusRequesting
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isHomeDeviceStatusRequesting = true;
|
|
||||||
try {
|
|
||||||
const data = await getDeviceBatteryAPI();
|
|
||||||
if (isHomePageVisible) {
|
|
||||||
applyHomeDeviceStatus(data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 请求失败时保留上一次有效状态,等待下一轮自动恢复。
|
|
||||||
console.log("刷新首页设备状态失败", error);
|
|
||||||
} finally {
|
|
||||||
isHomeDeviceStatusRequesting = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopHomeDeviceStatusPolling = () => {
|
|
||||||
clearInterval(homeDeviceStatusTimer);
|
|
||||||
homeDeviceStatusTimer = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 首页可见且用户已绑定设备时,每 10 秒刷新一次设备状态。
|
|
||||||
const startHomeDeviceStatusPolling = () => {
|
|
||||||
stopHomeDeviceStatusPolling();
|
|
||||||
if (!isHomePageVisible || !user.value?.id || !device.value?.deviceId) return;
|
|
||||||
|
|
||||||
homeDeviceStatusTimer = setInterval(() => {
|
|
||||||
void refreshHomeDeviceStatus();
|
|
||||||
}, HOME_DEVICE_STATUS_POLL_INTERVAL);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 首页停留期间登录或绑定状态变化时,及时启停设备状态轮询。
|
|
||||||
watch(
|
|
||||||
[() => user.value?.id, () => device.value?.deviceId],
|
|
||||||
() => {
|
|
||||||
if (isHomePageVisible) {
|
|
||||||
startHomeDeviceStatusPolling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// 首页设备卡片按“未绑定 / 已绑定未连接 / 已绑定已连接”三态展示。
|
|
||||||
const deviceCardState = computed(() => {
|
|
||||||
// 未登录时始终展示绑定入口,避免本地残留设备状态误显示为已绑定。
|
|
||||||
if (!user.value?.id || !device.value?.deviceId) return "unbound";
|
|
||||||
return normalizeDeviceOnline(online.value) === true ? "online" : "offline";
|
|
||||||
});
|
|
||||||
|
|
||||||
const deviceCardAssets = {
|
|
||||||
unbound: {
|
|
||||||
bow: "../static/home-device/device-bow--unbound.png",
|
|
||||||
background: "../static/home-device/device-card-bg--unbound.png",
|
|
||||||
actionBackground: "../static/home-device/device-action-bg--bind.png",
|
|
||||||
},
|
|
||||||
offline: {
|
|
||||||
bow: "../static/home-device/device-bow--offline.png",
|
|
||||||
background: "../static/home-device/device-card-bg--bound.png",
|
|
||||||
actionBackground: "../static/home-device/device-action-bg--device.png",
|
|
||||||
},
|
|
||||||
online: {
|
|
||||||
bow: "../static/home-device/device-bow--online.png",
|
|
||||||
background: "../static/home-device/device-card-bg--bound.png",
|
|
||||||
actionBackground: "../static/home-device/device-action-bg--device.png",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const deviceCardAsset = computed(() => deviceCardAssets[deviceCardState.value]);
|
|
||||||
|
|
||||||
// OTA 相关
|
// OTA 相关
|
||||||
const otaVisible = ref(false);
|
const otaVisible = ref(false);
|
||||||
@@ -161,7 +50,6 @@ const otaInfo = ref({
|
|||||||
versionInfo: "",
|
versionInfo: "",
|
||||||
resourceUrl: "",
|
resourceUrl: "",
|
||||||
forceUpdate: false,
|
forceUpdate: false,
|
||||||
needUpdate: false,
|
|
||||||
});
|
});
|
||||||
const isStartingOta = ref(false);
|
const isStartingOta = ref(false);
|
||||||
let isCheckingOta = false;
|
let isCheckingOta = false;
|
||||||
@@ -219,8 +107,6 @@ const applyOtaVersionInfo = (versionInfo) => {
|
|||||||
versionInfo: versionInfo?.versionInfo || "",
|
versionInfo: versionInfo?.versionInfo || "",
|
||||||
resourceUrl: versionInfo?.resourceUrl || "",
|
resourceUrl: versionInfo?.resourceUrl || "",
|
||||||
forceUpdate: Number(versionInfo?.forceUpdate) === 1,
|
forceUpdate: Number(versionInfo?.forceUpdate) === 1,
|
||||||
needUpdate:
|
|
||||||
versionInfo?.needUpdate === true || Number(versionInfo?.needUpdate) === 1,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -236,7 +122,7 @@ const checkOtaUpdate = async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (normalizeDeviceOnline(deviceStatus?.online) !== true || otaVisible.value) return;
|
if (deviceStatus?.online !== true || otaVisible.value) return;
|
||||||
|
|
||||||
let versionInfo;
|
let versionInfo;
|
||||||
try {
|
try {
|
||||||
@@ -244,9 +130,8 @@ const checkOtaUpdate = async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (otaVisible.value) return;
|
if (!versionInfo?.needUpdate || otaVisible.value) return;
|
||||||
applyOtaVersionInfo(versionInfo);
|
applyOtaVersionInfo(versionInfo);
|
||||||
if (!otaInfo.value.needUpdate) return;
|
|
||||||
|
|
||||||
const dismissedAt = uni.getStorageSync("ota_dismissed_at");
|
const dismissedAt = uni.getStorageSync("ota_dismissed_at");
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -280,7 +165,7 @@ const getOtaWifiUrl = () => {
|
|||||||
`versionNumber=${encodeURIComponent(versionNumber)}`,
|
`versionNumber=${encodeURIComponent(versionNumber)}`,
|
||||||
`resourceUrl=${encodeURIComponent(resourceUrl)}`,
|
`resourceUrl=${encodeURIComponent(resourceUrl)}`,
|
||||||
].join("&");
|
].join("&");
|
||||||
return `/pages/device/ota-wifi?${query}`;
|
return `/pages/ota-wifi?${query}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理 OTA 弹窗暂不更新,强制更新时不允许关闭。
|
// 处理 OTA 弹窗暂不更新,强制更新时不允许关闭。
|
||||||
@@ -304,7 +189,6 @@ const completeHomeOtaUpdate = (runId) => {
|
|||||||
if (!isOtaUpdateRunActive(runId)) return;
|
if (!isOtaUpdateRunActive(runId)) return;
|
||||||
invalidateOtaUpdateRun();
|
invalidateOtaUpdateRun();
|
||||||
isStartingOta.value = false;
|
isStartingOta.value = false;
|
||||||
otaInfo.value = {...otaInfo.value, needUpdate: false};
|
|
||||||
otaProgress.value = 100;
|
otaProgress.value = 100;
|
||||||
const completedRunId = otaUpdateRunId;
|
const completedRunId = otaUpdateRunId;
|
||||||
otaResultTimer = setTimeout(() => {
|
otaResultTimer = setTimeout(() => {
|
||||||
@@ -400,7 +284,7 @@ const handleOtaUpdate = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizeDeviceOnline(deviceStatus?.online) !== true) {
|
if (deviceStatus?.online !== true) {
|
||||||
isStartingOta.value = false;
|
isStartingOta.value = false;
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: "请先开启智能弓",
|
title: "请先开启智能弓",
|
||||||
@@ -452,72 +336,13 @@ const toRankListPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清理首页设备相关状态;clearDevice 会同步清空 deviceId 和在线状态。
|
|
||||||
const clearHomeDeviceState = () => {
|
|
||||||
clearDevice();
|
|
||||||
uni.setStorageSync("calibration", false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 从后端同步首页设备绑定关系和连接状态,绑定关系与在线状态共同决定卡片三态。
|
|
||||||
const syncHomeDevice = async () => {
|
|
||||||
let devices;
|
|
||||||
try {
|
|
||||||
devices = await getMyDevicesAPI();
|
|
||||||
} catch (error) {
|
|
||||||
// 绑定接口失败时保留当前设备,避免网络抖动被误判为未绑定。
|
|
||||||
console.log("同步首页设备绑定失败", error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bindings = devices?.bindings;
|
|
||||||
|
|
||||||
// 服务端用 null 或空数组表示当前账号没有绑定设备。
|
|
||||||
if (bindings === null || (Array.isArray(bindings) && bindings.length === 0)) {
|
|
||||||
clearHomeDeviceState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bindings 缺失或类型错误才属于异常响应,保留已有设备状态。
|
|
||||||
if (!Array.isArray(bindings)) {
|
|
||||||
console.log("首页设备绑定响应格式异常", devices);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDevice = bindings.find((item) => item?.deviceId);
|
|
||||||
if (!currentDevice) {
|
|
||||||
clearHomeDeviceState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousDeviceId = String(device.value?.deviceId || "");
|
|
||||||
const deviceId = String(currentDevice.deviceId || "");
|
|
||||||
updateDevice(
|
|
||||||
deviceId,
|
|
||||||
currentDevice.deviceName || currentDevice.name || ""
|
|
||||||
);
|
|
||||||
|
|
||||||
// 切换到新设备时先以离线态初始化,避免沿用上一台设备的在线状态。
|
|
||||||
if (previousDeviceId !== deviceId) {
|
|
||||||
updateOnline(false);
|
|
||||||
updateDeviceBattery(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
await refreshHomeDeviceStatus();
|
|
||||||
};
|
|
||||||
|
|
||||||
onShow(async (options) => {
|
onShow(async (options) => {
|
||||||
isHomePageVisible = true;
|
|
||||||
startHomeDeviceStatusPolling();
|
|
||||||
|
|
||||||
const env = uni.getAccountInfoSync().miniProgram.envVersion;
|
const env = uni.getAccountInfoSync().miniProgram.envVersion;
|
||||||
const token = uni.getStorageSync(`${env}_token`);
|
const token = uni.getStorageSync(`${env}_token`);
|
||||||
|
|
||||||
// 检查是否从 OTA 更新页面返回
|
// 检查是否从 OTA 更新页面返回
|
||||||
if (options && options.updateResult) {
|
if (options && options.updateResult) {
|
||||||
otaState.value = options.updateResult;
|
otaState.value = options.updateResult;
|
||||||
if (options.updateResult === "update_success") {
|
|
||||||
otaInfo.value = {...otaInfo.value, needUpdate: false};
|
|
||||||
}
|
|
||||||
otaVisible.value = true;
|
otaVisible.value = true;
|
||||||
} else if (token || user.value.id) {
|
} else if (token || user.value.id) {
|
||||||
await checkOtaUpdate();
|
await checkOtaUpdate();
|
||||||
@@ -575,17 +400,19 @@ onShow(async (options) => {
|
|||||||
showGuide.value = false;
|
showGuide.value = false;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
await syncHomeDevice();
|
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 {
|
||||||
|
clearDevice();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录态或绑定设备可能在本次 onShow 中发生变化,按最新状态重建轮询。
|
|
||||||
startHomeDeviceStatusPolling();
|
|
||||||
});
|
|
||||||
|
|
||||||
onHide(() => {
|
|
||||||
isHomePageVisible = false;
|
|
||||||
stopHomeDeviceStatusPolling();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -595,8 +422,6 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
isHomePageVisible = false;
|
|
||||||
stopHomeDeviceStatusPolling();
|
|
||||||
invalidateOtaUpdateRun();
|
invalidateOtaUpdateRun();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -643,90 +468,30 @@ onShareTimeline(() => {
|
|||||||
/> -->
|
/> -->
|
||||||
</view>
|
</view>
|
||||||
<UserHeader showRank :onSignin="() => (showModal = true)"/>
|
<UserHeader showRank :onSignin="() => (showModal = true)"/>
|
||||||
<view :style="{ padding: '24rpx 20rpx' }">
|
<view :style="{ padding: '12px 10px' }">
|
||||||
<view class="feature-grid">
|
<view class="feature-grid">
|
||||||
<view class="bow-card">
|
<view class="bow-card">
|
||||||
<view
|
|
||||||
class="device-visual"
|
|
||||||
@click="$clickSound(() => toPage('/pages/device/my-device'))"
|
|
||||||
>
|
|
||||||
<image
|
|
||||||
class="device-visual-background"
|
|
||||||
:src="deviceCardAsset.background"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
class="device-visual-bow"
|
|
||||||
:src="deviceCardAsset.bow"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<view
|
|
||||||
v-if="user.id && deviceCardState === 'offline'"
|
|
||||||
class="device-status-badge device-status-badge--offline"
|
|
||||||
@click.stop="$clickSound(() => toPage('/pages/device/my-device'))"
|
|
||||||
>
|
|
||||||
<image
|
|
||||||
class="device-status-icon"
|
|
||||||
src="../static/home-device/device-state-dot--offline.png"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<text>未连接</text>
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
v-if="user.id && deviceCardState === 'online'"
|
|
||||||
class="device-status-badge device-status-badge--online"
|
|
||||||
@click.stop="$clickSound(() => toPage('/pages/device/my-device'))"
|
|
||||||
>
|
|
||||||
<image
|
|
||||||
v-if="otaInfo.needUpdate"
|
|
||||||
class="device-status-refresh"
|
|
||||||
src="../static/home-device/device-state-refresh.png"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<view class="device-battery-value">
|
|
||||||
<image
|
|
||||||
class="device-battery-frame"
|
|
||||||
src="../static/home-device/device-state-battery.png"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<text class="device-battery-text">{{ deviceBattery === null ? "--" : deviceBattery }}</text>
|
|
||||||
</view>
|
|
||||||
<image
|
|
||||||
class="device-status-icon device-status-icon--online"
|
|
||||||
src="../static/home-device/device-state-dot--online.png"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
class="device-action-badge"
|
|
||||||
:class="`device-action-badge--${deviceCardState}`"
|
|
||||||
@click.stop="$clickSound(() => toPage('/pages/device/my-device'))"
|
|
||||||
>
|
|
||||||
<image
|
|
||||||
class="device-action-background"
|
|
||||||
:src="deviceCardAsset.actionBackground"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
v-if="deviceCardState === 'unbound'"
|
|
||||||
class="device-action-bind-icon"
|
|
||||||
src="../static/home-device/device-action-bind-icon.png"
|
|
||||||
mode="scaleToFill"
|
|
||||||
/>
|
|
||||||
<text
|
|
||||||
:class="{
|
|
||||||
'device-action-text--bound': deviceCardState !== 'unbound'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ deviceCardState === "unbound" ? "绑定智能弓箭" : "我的设备" }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<image
|
<image
|
||||||
class="device-tutorial"
|
v-if="online"
|
||||||
|
src="https://static.shelingxingqiu.com/attachment/2025-08-07/dbvt1o6dvhr2rop3kn.webp"
|
||||||
|
mode="widthFix"
|
||||||
|
@click="() => toPage('/pages/my-device')"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
v-else
|
||||||
|
src="https://static.shelingxingqiu.com/attachment/2026-01-04/dffohwtk1gwh0xfa6h.png"
|
||||||
|
mode="widthFix"
|
||||||
|
@click="$clickSound(() => toPage('/pages/my-device'))"
|
||||||
|
/>
|
||||||
|
<block v-if="user.id">
|
||||||
|
<text v-if="!device.deviceId">绑定我的智能弓</text>
|
||||||
|
<text v-else-if="!online">设备离线</text>
|
||||||
|
<text v-else-if="online">设备在线</text>
|
||||||
|
</block>
|
||||||
|
<image
|
||||||
src="https://static.shelingxingqiu.com/shootmini/static/first-try.png"
|
src="https://static.shelingxingqiu.com/shootmini/static/first-try.png"
|
||||||
mode="scaleToFill"
|
mode="widthFix"
|
||||||
@click="$clickSound(() => toPage('/pages/first-try'))"
|
@click="() => toPage('/pages/first-try')"
|
||||||
/>
|
/>
|
||||||
<BubbleTip v-if="showGuide" :location="{ top: '60%', left: '47%' }">
|
<BubbleTip v-if="showGuide" :location="{ top: '60%', left: '47%' }">
|
||||||
<text>新人必刷!</text>
|
<text>新人必刷!</text>
|
||||||
@@ -834,7 +599,7 @@ onShareTimeline(() => {
|
|||||||
.feature-grid {
|
.feature-grid {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 10rpx;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-grid > view {
|
.feature-grid > view {
|
||||||
@@ -849,168 +614,22 @@ onShareTimeline(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-visual {
|
.feature-grid > view > image {
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 390rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #16161e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-visual-background,
|
.bow-card > text {
|
||||||
.device-visual-bow {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
top: 66%;
|
||||||
}
|
|
||||||
|
|
||||||
.device-visual-background {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-visual-bow {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 388rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-badge,
|
|
||||||
.device-action-badge {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-badge {
|
|
||||||
position: absolute;
|
|
||||||
top: 10rpx;
|
|
||||||
right: 14rpx;
|
|
||||||
z-index: 2;
|
|
||||||
height: 34rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-family: Inter-Regular, sans-serif;
|
|
||||||
font-size: 18rpx;
|
|
||||||
line-height: 18rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-badge--online {
|
|
||||||
top: 9rpx;
|
|
||||||
right: 10rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-badge--offline .device-status-icon {
|
|
||||||
margin-right: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-badge--online .device-status-icon--online {
|
|
||||||
margin-left: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-icon {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-icon--online {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status-refresh {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 三个状态项使用等大的方形槽位,电池框在槽位内垂直居中。 */
|
|
||||||
.device-battery-value {
|
|
||||||
position: relative;
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
margin-left: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-battery-frame {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 38rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文字容器只覆盖电池主体(60rpx),避开右侧凸起,使数字在框内水平垂直居中。 */
|
|
||||||
.device-battery-text {
|
|
||||||
position: absolute;
|
|
||||||
top: 9rpx;
|
|
||||||
left: 0x;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 36rpx;
|
|
||||||
height: 20rpx;
|
|
||||||
font-size: 14rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-action-badge {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 28rpx;
|
|
||||||
left: 50%;
|
left: 50%;
|
||||||
z-index: 2;
|
transform: translate(-50%, -50%);
|
||||||
width: 218rpx;
|
|
||||||
height: 56rpx;
|
|
||||||
justify-content: center;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 22rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #b3b3b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-action-badge--offline,
|
.bow-card > image:nth-child(3) {
|
||||||
.device-action-badge--online {
|
transform: translateY(-1px);
|
||||||
width: 166rpx;
|
|
||||||
height: 44rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-action-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-action-badge > text,
|
|
||||||
.device-action-bind-icon {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-action-text--bound {
|
|
||||||
background: linear-gradient(90deg, #fcfeef 0%, #fdeaa1 100%);
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-action-bind-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-tutorial {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 114rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-card {
|
.play-card {
|
||||||
@@ -1023,8 +642,8 @@ onShareTimeline(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ranking-section {
|
.ranking-section {
|
||||||
border-radius: 30rpx;
|
border-radius: 15px;
|
||||||
padding: 30rpx;
|
padding: 15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,23 +657,23 @@ onShareTimeline(() => {
|
|||||||
|
|
||||||
.into-btn {
|
.into-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 80rpx;
|
top: 40px;
|
||||||
left: calc(50% - 200rpx);
|
left: calc(50% - 100px);
|
||||||
width: 400rpx;
|
width: 200px;
|
||||||
height: 200rpx;
|
height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-players {
|
.ranking-players {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 20px;
|
||||||
margin-top: 42%;
|
margin-top: 42%;
|
||||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-players > image:first-child {
|
.ranking-players > image:first-child {
|
||||||
width: 28%;
|
width: 28%;
|
||||||
transform: translateX(-20rpx) translateY(-16rpx);
|
transform: translateX(-10px) translateY(-8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-avatars {
|
.player-avatars {
|
||||||
@@ -1063,10 +682,10 @@ onShareTimeline(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.divide-line {
|
.divide-line {
|
||||||
width: 2rpx;
|
width: 1px;
|
||||||
height: 70rpx;
|
height: 35px;
|
||||||
background-color: #80808033;
|
background-color: #80808033;
|
||||||
margin-right: 16rpx;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-avatar,
|
.player-avatar,
|
||||||
@@ -1093,10 +712,10 @@ onShareTimeline(() => {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #777777;
|
background: #777777;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20rpx;
|
font-size: 10px;
|
||||||
line-height: 36rpx;
|
line-height: 18px;
|
||||||
width: 36rpx;
|
width: 18px;
|
||||||
height: 36rpx;
|
height: 18px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1108,20 +727,20 @@ onShareTimeline(() => {
|
|||||||
|
|
||||||
.more-players {
|
.more-players {
|
||||||
background: #3c445a;
|
background: #3c445a;
|
||||||
font-size: 18rpx;
|
font-size: 9px;
|
||||||
line-height: 80rpx;
|
line-height: 80rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-players > text {
|
.more-players > text {
|
||||||
margin-left: 4rpx;
|
margin-left: 2px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-data {
|
.my-data {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: 40rpx;
|
margin-top: 20px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1131,7 +750,7 @@ onShareTimeline(() => {
|
|||||||
|
|
||||||
.my-data > view:first-child > image {
|
.my-data > view:first-child > image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transform: translateX(-16rpx);
|
transform: translateX(-8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-data > view:nth-child(2) {
|
.my-data > view:nth-child(2) {
|
||||||
@@ -1148,7 +767,7 @@ onShareTimeline(() => {
|
|||||||
|
|
||||||
.my-data > view:nth-child(2) > view {
|
.my-data > view:nth-child(2) > view {
|
||||||
width: 28%;
|
width: 28%;
|
||||||
border-radius: 20rpx;
|
border-radius: 10px;
|
||||||
background: linear-gradient(180deg, #303b4c 30%, #2c384a 100%);
|
background: linear-gradient(180deg, #303b4c 30%, #2c384a 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1158,7 +777,7 @@ onShareTimeline(() => {
|
|||||||
|
|
||||||
.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: 50rpx;
|
line-height: 25px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,7 +787,7 @@ onShareTimeline(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 120rpx;
|
height: 60px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,526 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
|
import Container from "@/components/Container.vue";
|
||||||
|
import ScreenHint from "@/components/ScreenHint.vue";
|
||||||
|
import SButton from "@/components/SButton.vue";
|
||||||
|
import {
|
||||||
|
bindDeviceAPI,
|
||||||
|
getMyDevicesAPI,
|
||||||
|
unbindDeviceAPI,
|
||||||
|
laserAimAPI, bindDeviceAPIV2,
|
||||||
|
} from "@/apis";
|
||||||
|
import useStore from "@/store";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
const showTip = ref(false);
|
||||||
|
const confirmBindTip = ref(false);
|
||||||
|
const addDevice = ref();
|
||||||
|
const store = useStore();
|
||||||
|
const { updateDevice, clearDevice } = store;
|
||||||
|
const { user, device } = storeToRefs(store);
|
||||||
|
const justBind = ref(false);
|
||||||
|
const calibration = ref(false);
|
||||||
|
const token = ref(null);
|
||||||
|
const isSVip = computed(() => user.value.sVip === true);
|
||||||
|
const isVip = computed(() => user.value.vip === true && !isSVip.value);
|
||||||
|
|
||||||
|
// 扫描二维码方法
|
||||||
|
const handleScan = () => {
|
||||||
|
// 调用扫码API
|
||||||
|
uni.scanCode({
|
||||||
|
// 只支持扫码二维码
|
||||||
|
onlyFromCamera: true,
|
||||||
|
scanType: ["qrCode"],
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
// const base64Decode = (str) => {
|
||||||
|
// // 将 base64 转换为 utf8 字符串
|
||||||
|
// const bytes = wx.base64ToArrayBuffer(str);
|
||||||
|
// return String.fromCharCode.apply(null, new Uint8Array(bytes));
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// addDevice.value = JSON.parse(base64Decode(res.result));
|
||||||
|
token.value = res.result;
|
||||||
|
confirmBindTip.value = true;
|
||||||
|
} catch (err) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "无效二维码",
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error("扫码失败:", err);
|
||||||
|
uni.showToast({
|
||||||
|
title: "扫码失败",
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmBind = async () => {
|
||||||
|
if (!justBind.value && token.value) {
|
||||||
|
const result = await bindDeviceAPIV2(token.value);
|
||||||
|
confirmBindTip.value = false;
|
||||||
|
if (result.binded) {
|
||||||
|
return uni.showToast({
|
||||||
|
title: "设备已绑定其他账号,请解绑后再绑定",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateDevice(result.deviceId, result.name);
|
||||||
|
justBind.value = true;
|
||||||
|
uni.showToast({
|
||||||
|
title: "绑定成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toFristTryPage = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/first-try",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const unbindDevice = async () => {
|
||||||
|
try {
|
||||||
|
await unbindDeviceAPI(device.value.deviceId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.type === "DEVICE_BIND_INVALID") {
|
||||||
|
uni.setStorageSync("calibration", false);
|
||||||
|
clearDevice();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.setStorageSync("calibration", false);
|
||||||
|
uni.showToast({
|
||||||
|
title: "解绑成功",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
clearDevice();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 连接wifi跳转到wifi列表页面 */
|
||||||
|
const joinWifi = () => {
|
||||||
|
uni.navigateTo({ url: "/pages/ota-wifi" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const toDeviceIntroPage = () => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/device-intro",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const backToHome = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyEmail = () => {
|
||||||
|
uni.setClipboardData({
|
||||||
|
data: "shelingxingqiu@163.com",
|
||||||
|
success: () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: "邮箱已复制",
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const goCalibration = async () => {
|
||||||
|
await laserAimAPI();
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/calibration",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncDeviceBinding = async () => {
|
||||||
|
if (!user.value.id) return;
|
||||||
|
try {
|
||||||
|
const devices = await getMyDevicesAPI();
|
||||||
|
if (devices.bindings && devices.bindings.length) {
|
||||||
|
updateDevice(devices.bindings[0].deviceId, devices.bindings[0].deviceName);
|
||||||
|
} else {
|
||||||
|
clearDevice();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("sync device binding error", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onShow(async () => {
|
||||||
|
calibration.value = uni.getStorageSync("calibration");
|
||||||
|
await syncDeviceBinding();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Container title="弓箭绑定">
|
||||||
|
<view v-if="!device.deviceId" class="scan-code">
|
||||||
|
<button hover-class="none" @click="$clickSound(handleScan)">
|
||||||
|
<image src="https://static.shelingxingqiu.com/shootmini/static/scan.png" mode="widthFix" />
|
||||||
|
</button>
|
||||||
|
<button hover-class="none" @click="showTip = true">
|
||||||
|
<text>扫</text>
|
||||||
|
<text :style="{ color: '#fed847' }">射灵弓箭</text>
|
||||||
|
<text>上的二维码</text>
|
||||||
|
<image src="../static/s-question-mark-white.png" mode="widthFix" />
|
||||||
|
</button>
|
||||||
|
<text>射灵智能弓箭,三模传感系统与独创靶环算法,</text>
|
||||||
|
<text>毫秒级在线实时对战,让你拥有全球约战的乐趣!</text>
|
||||||
|
<button hover-class="none" @click="toDeviceIntroPage">
|
||||||
|
<image src="https://static.shelingxingqiu.com/shootmini/static/have-no-device.png" mode="widthFix" />
|
||||||
|
</button>
|
||||||
|
<ScreenHint
|
||||||
|
mode="square"
|
||||||
|
:show="showTip"
|
||||||
|
:onClose="() => (showTip = false)"
|
||||||
|
>
|
||||||
|
<view class="scan-tips">
|
||||||
|
<text>扫码绑定设灵弓箭</text>
|
||||||
|
<image
|
||||||
|
src="https://static.shelingxingqiu.com/attachment/2025-08-05/dbuacrelri7jr3axiy.png"
|
||||||
|
mode="widthFix"
|
||||||
|
/>
|
||||||
|
<text>【注】已被绑定的弓箭无法再次绑定。</text>
|
||||||
|
<view>
|
||||||
|
<text>如有任何疑问,请随时联系:</text>
|
||||||
|
<button hover-class="none" @click="copyEmail">
|
||||||
|
shelingxingqiu@163.com
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</ScreenHint>
|
||||||
|
<ScreenHint
|
||||||
|
:show="confirmBindTip"
|
||||||
|
:onClose="() => (confirmBindTip = false)"
|
||||||
|
>
|
||||||
|
<view class="confirm-bind">
|
||||||
|
<text
|
||||||
|
>智能弓箭和系统账号需一一对应,你确定要将<text
|
||||||
|
:style="{ color: '#fed847' }"
|
||||||
|
>当前登录用户账号</text
|
||||||
|
>绑定<text :style="{ color: '#fed847' }">这把弓箭</text>吗?
|
||||||
|
绑定后不可随意更换。</text
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view @click="confirmBind">确认绑定</view>
|
||||||
|
<view @click="() => (confirmBindTip = false)">取消</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</ScreenHint>
|
||||||
|
</view>
|
||||||
|
<view v-if="justBind" class="just-bind">
|
||||||
|
<view
|
||||||
|
class="device-binded"
|
||||||
|
:style="{ marginBottom: calibration ? '250rpx' : '100rpx' }"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<image src="https://static.shelingxingqiu.com/shootmini/static/device-icon.png" mode="widthFix" />
|
||||||
|
<text>{{ device.deviceName }}</text>
|
||||||
|
<view class="calibration" v-if="calibration">
|
||||||
|
<button hover-class="none" @click="goCalibration">
|
||||||
|
<text>重新校准</text>
|
||||||
|
<image src="../static/enter-arrow-blue.png" mode="widthFix" />
|
||||||
|
</button>
|
||||||
|
<view>
|
||||||
|
<image src="../static/calibration-tip.png" mode="widthFix" />
|
||||||
|
<text>如有场地/距离变化,需重新校准以保证智能弓射箭精准度</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image src="../static/bind-success.png" mode="widthFix" />
|
||||||
|
<view>
|
||||||
|
<image
|
||||||
|
:src="user.avatar || '../static/user-icon.png'"
|
||||||
|
mode="widthFix"
|
||||||
|
:style="{ borderRadius: '50%' }"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- <block v-if="calibration"> -->
|
||||||
|
<view>
|
||||||
|
<text>恭喜,你的弓箭和账号已成功绑定!</text>
|
||||||
|
<text :style="{ color: '#fed847' }">已赠送6个月射灵世界会员</text>
|
||||||
|
</view>
|
||||||
|
<!-- <SButton :onClick="goCalibration" width="60vw" :rounded="40">
|
||||||
|
开启智能弓进行校准
|
||||||
|
</SButton>
|
||||||
|
<text :style="{ marginTop: '20rpx', fontSize: '24rpx', color: '#fff9' }"
|
||||||
|
>校准时弓箭激光将开启,请勿直视激光</text
|
||||||
|
> -->
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<SButton
|
||||||
|
:onClick="backToHome"
|
||||||
|
backgroundColor="#fff3"
|
||||||
|
color="#fff"
|
||||||
|
width="60vw"
|
||||||
|
:rounded="40"
|
||||||
|
>返回首页</SButton
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view :style="{ marginTop: '15px' }">
|
||||||
|
<SButton :onClick="toFristTryPage" width="60vw" :rounded="40">进入新手试炼</SButton>
|
||||||
|
</view>
|
||||||
|
<!-- </block> -->
|
||||||
|
<!-- <block v-else>
|
||||||
|
|
||||||
|
</block> -->
|
||||||
|
</view>
|
||||||
|
<view v-if="device.deviceId && !justBind" class="has-device">
|
||||||
|
<view class="device-binded">
|
||||||
|
<view>
|
||||||
|
<image src="https://static.shelingxingqiu.com/shootmini/static/device-icon.png" mode="widthFix" />
|
||||||
|
<text>{{ device.deviceName }}</text>
|
||||||
|
<view class="calibration">
|
||||||
|
<button hover-class="none" @click="goCalibration">
|
||||||
|
<text>去校准</text>
|
||||||
|
<image src="../static/enter-arrow-blue.png" mode="widthFix" />
|
||||||
|
</button>
|
||||||
|
<view>
|
||||||
|
<image src="../static/calibration-tip.png" mode="widthFix" />
|
||||||
|
<text
|
||||||
|
>首次绑定智能弓或场地/距离变化时,应进行校准以确保射箭精度</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<image src="../static/bind.png" mode="widthFix" />
|
||||||
|
<view>
|
||||||
|
<image
|
||||||
|
:src="user.avatar || '../static/user-icon.png'"
|
||||||
|
mode="widthFix"
|
||||||
|
:style="{ borderRadius: '50%' }"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view :style="{ marginTop: '240rpx' }">
|
||||||
|
<SButton :onClick="() => $clickSound(unbindDevice)" width="80vw" :rounded="40"
|
||||||
|
>解绑</SButton
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view :style="{ marginTop: '20rpx' }">
|
||||||
|
<SButton :onClick="() => $clickSound(joinWifi)" width="80vw" :rounded="40"
|
||||||
|
>设备连接WIFI</SButton
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</Container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.scan-code,
|
||||||
|
.just-bind,
|
||||||
|
.has-device {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.scan-code {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.scan-code > button:first-child {
|
||||||
|
margin-top: 22%;
|
||||||
|
}
|
||||||
|
.scan-code > button:first-child > image {
|
||||||
|
width: 300rpx;
|
||||||
|
}
|
||||||
|
.scan-code > button:nth-child(2) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
margin: 50rpx;
|
||||||
|
}
|
||||||
|
.scan-code > button:nth-child(2) > image {
|
||||||
|
width: 28rpx;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
.scan-code > text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff9;
|
||||||
|
}
|
||||||
|
.scan-code > button:nth-child(5) {
|
||||||
|
margin-top: 25%;
|
||||||
|
}
|
||||||
|
.scan-code > button:nth-child(5) > image {
|
||||||
|
width: 380rpx;
|
||||||
|
}
|
||||||
|
.scan-tips {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 90%;
|
||||||
|
margin-top: 20%;
|
||||||
|
}
|
||||||
|
.scan-tips > text {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
.scan-tips > text:first-child {
|
||||||
|
color: #fed847;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
.scan-tips > view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.scan-tips > view:last-child {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
.scan-tips > view:last-child > button {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #39a8ff;
|
||||||
|
}
|
||||||
|
.scan-tips > image {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.confirm-bind {
|
||||||
|
color: #fff9;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.confirm-bind > view:last-child {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.confirm-bind > view:last-child > view {
|
||||||
|
width: 48%;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #fed847;
|
||||||
|
color: #000;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.confirm-bind > view:last-child > view:nth-child(2) {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #fff3;
|
||||||
|
}
|
||||||
|
.device-binded {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 200rpx;
|
||||||
|
}
|
||||||
|
.device-binded > view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
.device-binded > view > image {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.device-binded > view > text {
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.device-binded .member-nickname {
|
||||||
|
justify-content: center;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
.device-binded .member-nickname__text,
|
||||||
|
.device-binded .member-nickname__shine {
|
||||||
|
font-size: 26rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.device-binded > image {
|
||||||
|
width: 100rpx;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
.has-device,
|
||||||
|
.just-bind {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.has-device > view:nth-child(2),
|
||||||
|
.just-bind > view:nth-child(2) {
|
||||||
|
color: #fff9;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
margin-bottom: 100rpx;
|
||||||
|
}
|
||||||
|
.has-device > view:nth-child(2) > text,
|
||||||
|
.just-bind > view:nth-child(2) > text {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.calibration {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -145rpx;
|
||||||
|
left: 20rpx;
|
||||||
|
}
|
||||||
|
.calibration > button {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #287fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 15rpx;
|
||||||
|
padding-left: 50rpx;
|
||||||
|
}
|
||||||
|
.calibration > button > image {
|
||||||
|
width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
}
|
||||||
|
.calibration > view {
|
||||||
|
position: relative;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #fff9;
|
||||||
|
padding-top: 34rpx;
|
||||||
|
padding-left: 35rpx;
|
||||||
|
width: 322rpx;
|
||||||
|
}
|
||||||
|
.calibration > view > image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 370rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||||
import Container from "@/components/Container.vue";
|
import Container from "@/components/Container.vue";
|
||||||
@@ -650,16 +650,16 @@ onUnmounted(() => {
|
|||||||
</view>
|
</view>
|
||||||
<view v-if="currentState === 'CONNECTED'" class="wifi-list-card connected-wifi-card">
|
<view v-if="currentState === 'CONNECTED'" class="wifi-list-card connected-wifi-card">
|
||||||
<view class="wifi-item connected-wifi-item">
|
<view class="wifi-item connected-wifi-item">
|
||||||
<image class="check-icon" src="../../static/sicon/check.png" mode="aspectFit" />
|
<image class="check-icon" src="../static/sicon/check.png" mode="aspectFit" />
|
||||||
<text class="wifi-ssid connected-wifi-ssid">{{ connectedWifi?.SSID }}</text>
|
<text class="wifi-ssid connected-wifi-ssid">{{ connectedWifi?.SSID }}</text>
|
||||||
<view class="wifi-icons connected-wifi-icons">
|
<view class="wifi-icons connected-wifi-icons">
|
||||||
<image
|
<image
|
||||||
v-if="connectedWifi?.secure"
|
v-if="connectedWifi?.secure"
|
||||||
class="security-icon"
|
class="security-icon"
|
||||||
src="../../static/sicon/pwd.png"
|
src="../static/sicon/pwd.png"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
/>
|
/>
|
||||||
<image class="signal-icon" src="../../static/sicon/wifi.png" mode="aspectFit" />
|
<image class="signal-icon" src="../static/sicon/wifi.png" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -668,7 +668,7 @@ onUnmounted(() => {
|
|||||||
<view class="section-label-row">
|
<view class="section-label-row">
|
||||||
<text class="section-label">网络</text>
|
<text class="section-label">网络</text>
|
||||||
<image
|
<image
|
||||||
src="../../static/sicon/refresh.png"
|
src="../static/sicon/refresh.png"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
:style="{ width: '34rpx', height: '34rpx', marginLeft: '8rpx', opacity: isRefreshing ? 0.3 : 0.7 }"
|
:style="{ width: '34rpx', height: '34rpx', marginLeft: '8rpx', opacity: isRefreshing ? 0.3 : 0.7 }"
|
||||||
@click="startScanning"
|
@click="startScanning"
|
||||||
@@ -699,10 +699,10 @@ onUnmounted(() => {
|
|||||||
<image
|
<image
|
||||||
v-if="wifi.secure"
|
v-if="wifi.secure"
|
||||||
class="security-icon"
|
class="security-icon"
|
||||||
src="../../static/sicon/pwd.png"
|
src="../static/sicon/pwd.png"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
/>
|
/>
|
||||||
<image class="signal-icon" src="../../static/sicon/wifi.png" mode="aspectFit" />
|
<image class="signal-icon" src="../static/sicon/wifi.png" mode="aspectFit" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="wifi-item" @click="selectOther">
|
<view class="wifi-item" @click="selectOther">
|
||||||
@@ -763,7 +763,7 @@ onUnmounted(() => {
|
|||||||
<block v-if="connectMode === 'secure'">
|
<block v-if="connectMode === 'secure'">
|
||||||
<view class="sheet-header">
|
<view class="sheet-header">
|
||||||
<view class="sheet-nav-btn" @click="closeConnectSheet">
|
<view class="sheet-nav-btn" @click="closeConnectSheet">
|
||||||
<image src="../../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
|
<image src="../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
|
||||||
</view>
|
</view>
|
||||||
<text class="sheet-title">加入"{{ connectInput.ssid }}"</text>
|
<text class="sheet-title">加入"{{ connectInput.ssid }}"</text>
|
||||||
<view
|
<view
|
||||||
@@ -771,7 +771,7 @@ onUnmounted(() => {
|
|||||||
:class="{ 'nav-disabled': joinDisabled }"
|
:class="{ 'nav-disabled': joinDisabled }"
|
||||||
@click="joinNetwork"
|
@click="joinNetwork"
|
||||||
>
|
>
|
||||||
<image src="../../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
|
<image src="../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="input-row-card">
|
<view class="input-row-card">
|
||||||
@@ -789,7 +789,7 @@ onUnmounted(() => {
|
|||||||
<!-- 密码显示/隐藏切换按钮 -->
|
<!-- 密码显示/隐藏切换按钮 -->
|
||||||
<view class="pwd-eye-btn" @click="togglePasswordVisibility">
|
<view class="pwd-eye-btn" @click="togglePasswordVisibility">
|
||||||
<image
|
<image
|
||||||
:src="showPassword ? '../../static/sicon/eye-on.png' : '../../static/sicon/eye-off.png'"
|
:src="showPassword ? '../static/sicon/eye-on.png' : '../static/sicon/eye-off.png'"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
style="width: 40rpx; height: 40rpx;"
|
style="width: 40rpx; height: 40rpx;"
|
||||||
/>
|
/>
|
||||||
@@ -803,11 +803,11 @@ onUnmounted(() => {
|
|||||||
<block v-else-if="connectMode === 'open'">
|
<block v-else-if="connectMode === 'open'">
|
||||||
<view class="sheet-header">
|
<view class="sheet-header">
|
||||||
<view class="sheet-nav-btn" @click="closeConnectSheet">
|
<view class="sheet-nav-btn" @click="closeConnectSheet">
|
||||||
<image src="../../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
|
<image src="../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
|
||||||
</view>
|
</view>
|
||||||
<text class="sheet-title">加入"{{ connectInput.ssid }}"</text>
|
<text class="sheet-title">加入"{{ connectInput.ssid }}"</text>
|
||||||
<view class="sheet-nav-btn" @click="joinNetwork">
|
<view class="sheet-nav-btn" @click="joinNetwork">
|
||||||
<image src="../../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
|
<image src="../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text v-if="connectStatusText" class="connect-status">{{ connectStatusText }}</text>
|
<text v-if="connectStatusText" class="connect-status">{{ connectStatusText }}</text>
|
||||||
@@ -819,7 +819,7 @@ onUnmounted(() => {
|
|||||||
<block v-else-if="connectMode === 'manual'">
|
<block v-else-if="connectMode === 'manual'">
|
||||||
<view class="sheet-header">
|
<view class="sheet-header">
|
||||||
<view class="sheet-nav-btn" @click="closeConnectSheet">
|
<view class="sheet-nav-btn" @click="closeConnectSheet">
|
||||||
<image src="../../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
|
<image src="../static/sicon/arrow-left.png" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
|
||||||
</view>
|
</view>
|
||||||
<text class="sheet-title">加入无线网络</text>
|
<text class="sheet-title">加入无线网络</text>
|
||||||
<view
|
<view
|
||||||
@@ -827,7 +827,7 @@ onUnmounted(() => {
|
|||||||
:class="{ 'nav-disabled': joinDisabled }"
|
:class="{ 'nav-disabled': joinDisabled }"
|
||||||
@click="joinNetwork"
|
@click="joinNetwork"
|
||||||
>
|
>
|
||||||
<image src="../../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
|
<image src="../static/sicon/check.png" mode="aspectFit" style="width: 28rpx; height: 24rpx;" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="input-row-card">
|
<view class="input-row-card">
|
||||||
@@ -854,7 +854,7 @@ onUnmounted(() => {
|
|||||||
<!-- 密码显示/隐藏切换按钮 -->
|
<!-- 密码显示/隐藏切换按钮 -->
|
||||||
<view class="pwd-eye-btn" @click="togglePasswordVisibility">
|
<view class="pwd-eye-btn" @click="togglePasswordVisibility">
|
||||||
<image
|
<image
|
||||||
:src="showPassword ? '../../static/sicon/eye-on.png' : '../../static/sicon/eye-off.png'"
|
:src="showPassword ? '../static/sicon/eye-on.png' : '../static/sicon/eye-off.png'"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
style="width: 40rpx; height: 40rpx;"
|
style="width: 40rpx; height: 40rpx;"
|
||||||
/>
|
/>
|
||||||
@@ -111,7 +111,7 @@ const cancelMatching = async () => {
|
|||||||
const goCalibration = async () => {
|
const goCalibration = async () => {
|
||||||
await laserAimAPI();
|
await laserAimAPI();
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/device/calibration",
|
url: "/pages/calibration",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 519 B |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 630 KiB |
|
Before Width: | Height: | Size: 519 B |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 813 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 944 B |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 681 B |
|
Before Width: | Height: | Size: 895 B |
|
Before Width: | Height: | Size: 972 B |
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -100,8 +100,6 @@ export default defineStore("store", {
|
|||||||
ringRank: [],
|
ringRank: [],
|
||||||
},
|
},
|
||||||
online: false,
|
online: false,
|
||||||
// 设备电量属于运行时状态,null 表示当前没有有效数据。
|
|
||||||
deviceBattery: null,
|
|
||||||
game: {
|
game: {
|
||||||
roomID: "",
|
roomID: "",
|
||||||
inBattle: false,
|
inBattle: false,
|
||||||
@@ -138,17 +136,6 @@ export default defineStore("store", {
|
|||||||
updateOnline(online) {
|
updateOnline(online) {
|
||||||
this.online = online;
|
this.online = online;
|
||||||
},
|
},
|
||||||
updateDeviceBattery(value) {
|
|
||||||
if (value === null || value === undefined || value === "") {
|
|
||||||
this.deviceBattery = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const battery = Number(value);
|
|
||||||
this.deviceBattery = Number.isFinite(battery)
|
|
||||||
? Math.min(100, Math.max(0, battery))
|
|
||||||
: null;
|
|
||||||
},
|
|
||||||
async updateUser(user = {}) {
|
async updateUser(user = {}) {
|
||||||
this.user = { ...getDefaultUser(), ...user };
|
this.user = { ...getDefaultUser(), ...user };
|
||||||
this.user.lvlName = getLvlNameByScore(this.user.scores, this.config.randInfos)
|
this.user.lvlName = getLvlNameByScore(this.user.scores, this.config.randInfos)
|
||||||
@@ -164,7 +151,6 @@ export default defineStore("store", {
|
|||||||
clearDevice() {
|
clearDevice() {
|
||||||
this.device = getDefaultDevice();
|
this.device = getDefaultDevice();
|
||||||
this.online = false;
|
this.online = false;
|
||||||
this.deviceBattery = null;
|
|
||||||
},
|
},
|
||||||
async updateConfig(config) {
|
async updateConfig(config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@@ -213,7 +199,6 @@ export default defineStore("store", {
|
|||||||
user: getDefaultUser(),
|
user: getDefaultUser(),
|
||||||
device: getDefaultDevice(),
|
device: getDefaultDevice(),
|
||||||
online: false,
|
online: false,
|
||||||
deviceBattery: null,
|
|
||||||
game: getDefaultGame(),
|
game: getDefaultGame(),
|
||||||
dailyCount: getDefaultDailyCount(),
|
dailyCount: getDefaultDailyCount(),
|
||||||
deviceChargingDialogVisible: false,
|
deviceChargingDialogVisible: false,
|
||||||
|
|||||||