Merge branch 'feat-shebei' into test

This commit is contained in:
2026-09-23 15:37:10 +08:00
7 changed files with 124 additions and 16 deletions
+6 -2
View File
@@ -14,8 +14,13 @@ const props = defineProps({
type: String, type: String,
default: "normal", default: "normal",
}, },
contentHeight: {
type: String,
default: "",
},
}); });
const getContentHeight = () => { const getContentHeight = () => {
if (props.contentHeight) return props.contentHeight;
if (props.mode === "tall") return "50vw"; if (props.mode === "tall") return "50vw";
if (props.mode === "square") return "74vw"; if (props.mode === "square") return "74vw";
return "36vw"; return "36vw";
@@ -82,8 +87,7 @@ const getContentHeight = () => {
position: absolute; position: absolute;
width: 80vw; width: 80vw;
left: -7%; left: -7%;
bottom: -18vw; bottom: -8rpx;
z-index: -1; z-index: -1;
transform: translateY(-75px);
} }
</style> </style>
@@ -11,8 +11,10 @@ export function useDeviceStatus({
user, user,
device, device,
deviceStatus, deviceStatus,
deviceUsageDuration,
online, online,
updateDevice, updateDevice,
updateDeviceUsageDuration,
clearDevice, clearDevice,
unbindDialogVisible, unbindDialogVisible,
}) { }) {
@@ -41,14 +43,28 @@ export function useDeviceStatus({
const batteryText = computed(() => const batteryText = computed(() =>
battery.value === null ? "暂无数据" : `${battery.value}%` battery.value === null ? "暂无数据" : `${battery.value}%`
); );
const normalizeDuration = (value) => {
if (value == null || String(value).trim() === "") return null;
const seconds = Number(value);
return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
};
const onlineDurationText = computed(() => { const onlineDurationText = computed(() => {
const rawDuration = deviceStatus.value.onlineDuration; const currentDeviceId = String(device.value.deviceId || "");
if (rawDuration == null || String(rawDuration).trim() === "") return "--"; const cachedDuration =
String(deviceUsageDuration.value.deviceId || "") === currentDeviceId
? normalizeDuration(deviceUsageDuration.value.seconds)
: null;
const durations = [
normalizeDuration(deviceStatus.value.onlineDuration),
normalizeDuration(deviceDetails.value.onlineDuration),
cachedDuration,
].filter((value) => value !== null);
const seconds = Number(rawDuration); if (!durations.length) return "--";
if (!Number.isFinite(seconds) || seconds < 0) return "--"; const seconds = Math.max(...durations);
// WS 推送秒数,页面按完整分钟展示累计使用时间 // 累计时长应单调递增,页面按完整分钟展示当前已知最大值
const totalMinutes = Math.floor(seconds / 60); const totalMinutes = Math.floor(seconds / 60);
const hours = Math.floor(totalMinutes / 60); const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60; const minutes = totalMinutes % 60;
@@ -109,6 +125,7 @@ export function useDeviceStatus({
detail.qrCodeUrl ?? deviceDetails.value.qrCodeUrl ?? "" detail.qrCodeUrl ?? deviceDetails.value.qrCodeUrl ?? ""
).trim(), ).trim(),
}; };
updateDeviceUsageDuration(detail.onlineDuration, deviceId);
} catch (error) { } catch (error) {
// 实时刷新失败时保留当前页面数据,等待下次通知或页面重新显示。 // 实时刷新失败时保留当前页面数据,等待下次通知或页面重新显示。
console.log("刷新设备详情失败", error); console.log("刷新设备详情失败", error);
@@ -158,6 +175,7 @@ export function useDeviceStatus({
latestDevice.name || latestDevice.name ||
"我的智能弓" "我的智能弓"
); );
updateDeviceUsageDuration(latestDevice.onlineDuration, latestDevice.deviceId);
return; return;
} }
clearDevice(); clearDevice();
+40 -8
View File
@@ -22,8 +22,8 @@ import {
import { useOtaUpdate } from "@/composables/useOtaUpdate"; import { useOtaUpdate } from "@/composables/useOtaUpdate";
const store = useStore(); const store = useStore();
const { updateDevice, clearDevice } = store; const { updateDevice, updateDeviceUsageDuration, clearDevice } = store;
const { user, device, deviceStatus, online } = storeToRefs(store); const { user, device, deviceStatus, deviceUsageDuration, online } = storeToRefs(store);
const DEVICE_NAME_MAX_LENGTH = 10; const DEVICE_NAME_MAX_LENGTH = 10;
const formatBindingDate = (value) => { const formatBindingDate = (value) => {
@@ -97,8 +97,10 @@ const {
user, user,
device, device,
deviceStatus, deviceStatus,
deviceUsageDuration,
online, online,
updateDevice, updateDevice,
updateDeviceUsageDuration,
clearDevice, clearDevice,
unbindDialogVisible, unbindDialogVisible,
}); });
@@ -112,6 +114,28 @@ const bindingDateText = computed(() =>
formatBindingDate(deviceDetails.value.bindTime) formatBindingDate(deviceDetails.value.bindTime)
); );
// 固件版本统一以大写 V 开头,避免接口返回格式不一致影响弹窗展示。
const formatFirmwareVersion = (value) => {
const version = String(value ?? "").trim();
if (!version) return "";
return /^v/i.test(version) ? `V${version.slice(1)}` : `V${version}`;
};
const firmwareConfirmContent = computed(() => {
const currentVersion = formatFirmwareVersion(deviceStatus.value.version);
const latestVersion = formatFirmwareVersion(
pendingOtaInfo.value?.versionNumber
);
if (currentVersion && latestVersion) {
return `当前固件版本为 ${currentVersion},发现新固件版本 ${latestVersion},是否立即更新固件?`;
}
if (latestVersion) {
return `发现新固件版本 ${latestVersion},是否立即更新固件?`;
}
return "发现新固件版本,是否立即更新固件?";
});
const deviceIdText = computed(() => const deviceIdText = computed(() =>
showDeviceId.value && device.value.deviceName showDeviceId.value && device.value.deviceName
? device.value.deviceName ? device.value.deviceName
@@ -519,7 +543,7 @@ onShow(async () => {
class="device-bow" class="device-bow"
:class="{ 'device-bow--online': isDeviceOnline }" :class="{ 'device-bow--online': isDeviceOnline }"
:src="isDeviceOnline :src="isDeviceOnline
? 'https://static.shelingxingqiu.com/shootmini/static/home-device/device-bow--online.png' ? 'https://static.shelingxingqiu.com/shootmini/static/home-device/device-bow.png'
: 'https://static.shelingxingqiu.com/shootmini/static/home-device/device-bow.png'" : 'https://static.shelingxingqiu.com/shootmini/static/home-device/device-bow.png'"
:mode="isDeviceOnline ? 'aspectFit' : 'widthFix'" :mode="isDeviceOnline ? 'aspectFit' : 'widthFix'"
/> />
@@ -655,7 +679,11 @@ onShow(async () => {
</view> </view>
</ModalDialog> </ModalDialog>
<ScreenHint :show="confirmBindTip" :onClose="closeConfirmBindTip"> <ScreenHint
:show="confirmBindTip"
:onClose="closeConfirmBindTip"
contentHeight="360rpx"
>
<view class="confirm-bind"> <view class="confirm-bind">
<text>智能弓箭和系统账号需一一对应你确定要将当前登录用户账号绑定这把弓箭吗绑定后不可随意更换</text> <text>智能弓箭和系统账号需一一对应你确定要将当前登录用户账号绑定这把弓箭吗绑定后不可随意更换</text>
<view class="confirm-actions"> <view class="confirm-actions">
@@ -693,9 +721,9 @@ onShow(async () => {
<ModalDialog <ModalDialog
:show="firmwareConfirmVisible" :show="firmwareConfirmVisible"
title="固件更新" title="固件更新"
content="是否现在进行固件更新?" :content="firmwareConfirmContent"
cancelText="取消" cancelText="暂不更新"
confirmText="确定" confirmText="立即更新"
:onCancel="closeFirmwareConfirm" :onCancel="closeFirmwareConfirm"
:onConfirm="confirmFirmwareUpdate" :onConfirm="confirmFirmwareUpdate"
></ModalDialog> ></ModalDialog>
@@ -971,7 +999,7 @@ onShow(async () => {
.device-bow { .device-bow {
position: absolute; position: absolute;
top: 0; top: -2rpx;
left: 50%; left: 50%;
z-index: 4; z-index: 4;
width: 562rpx; width: 562rpx;
@@ -1319,6 +1347,10 @@ onShow(async () => {
} }
.confirm-bind { .confirm-bind {
width: 100%;
box-sizing: border-box;
padding-top: 100rpx;
transform: translateY(24rpx);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
font-size: 26rpx; font-size: 26rpx;
line-height: 42rpx; line-height: 42rpx;
+24 -1
View File
@@ -69,6 +69,11 @@ const deviceCardAssets = {
const deviceCardAsset = computed(() => deviceCardAssets[deviceCardState.value]); const deviceCardAsset = computed(() => deviceCardAssets[deviceCardState.value]);
// 二代设备支持充电时保持开机,仅在设备在线且状态明确为充电中时展示充电标识。
const isDeviceCharging = computed(
() => deviceCardState.value === "online" && deviceStatus.value?.charging === true
);
// OTA 相关 // OTA 相关
const otaVisible = ref(false); const otaVisible = ref(false);
const wifiRequiredVisible = ref(false); const wifiRequiredVisible = ref(false);
@@ -525,6 +530,7 @@ onShareTimeline(() => {
<view <view
v-if="user.id && deviceCardState === 'online'" v-if="user.id && deviceCardState === 'online'"
class="device-status-badge device-status-badge--online" class="device-status-badge device-status-badge--online"
:class="{ 'device-status-badge--charging': isDeviceCharging }"
@click.stop="$clickSound(() => toPage('/pages/device/my-device'))" @click.stop="$clickSound(() => toPage('/pages/device/my-device'))"
> >
<image <image
@@ -541,6 +547,12 @@ onShareTimeline(() => {
/> />
<text class="device-battery-text">{{ deviceBattery === null ? "--" : deviceBattery }}</text> <text class="device-battery-text">{{ deviceBattery === null ? "--" : deviceBattery }}</text>
</view> </view>
<image
v-if="isDeviceCharging"
class="device-charging-icon"
src="/static/device-charging.png"
mode="aspectFit"
/>
<image <image
class="device-status-icon device-status-icon--online" class="device-status-icon device-status-icon--online"
src="https://static.shelingxingqiu.com/shootmini/static/home-device/device-state-dot--online.png" src="https://static.shelingxingqiu.com/shootmini/static/home-device/device-state-dot--online.png"
@@ -806,6 +818,10 @@ onShareTimeline(() => {
margin-left: 12rpx; margin-left: 12rpx;
} }
.device-status-badge--charging .device-status-icon--online {
margin-left: 8rpx;
}
.device-status-icon { .device-status-icon {
width: 36rpx; width: 36rpx;
height: 36rpx; height: 36rpx;
@@ -837,11 +853,18 @@ onShareTimeline(() => {
height: 36rpx; height: 36rpx;
} }
.device-charging-icon {
flex-shrink: 0;
width: 15rpx;
height: 20rpx;
margin-left: 4rpx;
}
/* 文字容器只覆盖电池主体(60rpx),避开右侧凸起,使数字在框内水平垂直居中。 */ /* 文字容器只覆盖电池主体(60rpx),避开右侧凸起,使数字在框内水平垂直居中。 */
.device-battery-text { .device-battery-text {
position: absolute; position: absolute;
top: 9rpx; top: 9rpx;
left: 0x; left: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+31
View File
@@ -27,6 +27,11 @@ const getDefaultDeviceStatus = () => ({
receivedAt: 0, receivedAt: 0,
}); });
const getDefaultDeviceUsageDuration = () => ({
deviceId: "",
seconds: null,
});
const getDefaultGame = () => ({ const getDefaultGame = () => ({
roomID: "", roomID: "",
inBattle: false, inBattle: false,
@@ -114,6 +119,8 @@ export default defineStore("store", {
deviceBattery: null, deviceBattery: null,
// WebSocket 实时推送的设备状态,不参与持久化。 // WebSocket 实时推送的设备状态,不参与持久化。
deviceStatus: getDefaultDeviceStatus(), deviceStatus: getDefaultDeviceStatus(),
// 按设备记录本次运行期间已知的最大累计使用时长,离线时保持数值不回退。
deviceUsageDuration: getDefaultDeviceUsageDuration(),
game: { game: {
roomID: "", roomID: "",
inBattle: false, inBattle: false,
@@ -183,6 +190,27 @@ export default defineStore("store", {
this.deviceStatus = nextStatus; this.deviceStatus = nextStatus;
this.online = online; this.online = online;
this.deviceBattery = online ? nextStatus.battery : null; this.deviceBattery = online ? nextStatus.battery : null;
this.updateDeviceUsageDuration(nextStatus.onlineDuration);
},
updateDeviceUsageDuration(value, deviceId = this.device.deviceId) {
if (value === null || value === undefined || String(value).trim() === "") return;
const seconds = Number(value);
const normalizedDeviceId = String(deviceId || "");
if (!normalizedDeviceId || !Number.isFinite(seconds) || seconds < 0) return;
const cachedDeviceId = String(this.deviceUsageDuration.deviceId || "");
const cachedSeconds = Number(this.deviceUsageDuration.seconds);
this.deviceUsageDuration = {
deviceId: normalizedDeviceId,
seconds:
cachedDeviceId === normalizedDeviceId && Number.isFinite(cachedSeconds)
? Math.max(cachedSeconds, seconds)
: seconds,
};
},
clearDeviceUsageDuration() {
this.deviceUsageDuration = getDefaultDeviceUsageDuration();
}, },
setDeviceOnline(online) { setDeviceOnline(online) {
const nextOnline = online === true; const nextOnline = online === true;
@@ -210,6 +238,7 @@ export default defineStore("store", {
updateDevice(deviceId, deviceName) { updateDevice(deviceId, deviceName) {
if (String(this.device.deviceId || "") !== String(deviceId || "")) { if (String(this.device.deviceId || "") !== String(deviceId || "")) {
this.clearDeviceStatus(); this.clearDeviceStatus();
this.clearDeviceUsageDuration();
} }
this.device.deviceId = deviceId; this.device.deviceId = deviceId;
this.device.deviceName = deviceName; this.device.deviceName = deviceName;
@@ -217,6 +246,7 @@ export default defineStore("store", {
clearDevice() { clearDevice() {
this.device = getDefaultDevice(); this.device = getDefaultDevice();
this.clearDeviceStatus(); this.clearDeviceStatus();
this.clearDeviceUsageDuration();
}, },
async updateConfig(config) { async updateConfig(config) {
this.config = config; this.config = config;
@@ -267,6 +297,7 @@ export default defineStore("store", {
online: false, online: false,
deviceBattery: null, deviceBattery: null,
deviceStatus: getDefaultDeviceStatus(), deviceStatus: getDefaultDeviceStatus(),
deviceUsageDuration: getDefaultDeviceUsageDuration(),
game: getDefaultGame(), game: getDefaultGame(),
dailyCount: getDefaultDailyCount(), dailyCount: getDefaultDailyCount(),
deviceChargingDialogVisible: false, deviceChargingDialogVisible: false,