111
This commit is contained in:
@@ -219,6 +219,13 @@ export type HomeAssistantLightTransition = {
|
|||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TcmMusicTransition = {
|
||||||
|
running: boolean
|
||||||
|
hasVolume: boolean
|
||||||
|
volumePercent: number
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
export type HomeAssistantStatus = {
|
export type HomeAssistantStatus = {
|
||||||
connected: boolean
|
connected: boolean
|
||||||
message: string
|
message: string
|
||||||
@@ -285,6 +292,8 @@ export const healthApi = {
|
|||||||
http.get<never, HomeAssistantStatus>('/api/health/home-assistant/status'),
|
http.get<never, HomeAssistantStatus>('/api/health/home-assistant/status'),
|
||||||
lightTransitionStatus: () =>
|
lightTransitionStatus: () =>
|
||||||
http.get<never, HomeAssistantLightTransition>('/api/health/home-assistant/lights/transition'),
|
http.get<never, HomeAssistantLightTransition>('/api/health/home-assistant/lights/transition'),
|
||||||
|
musicTransitionStatus: () =>
|
||||||
|
http.get<never, TcmMusicTransition>('/api/health/home-assistant/music/transition'),
|
||||||
startSleepLight: () =>
|
startSleepLight: () =>
|
||||||
http.post<never, HomeAssistantLightTransition>('/api/health/home-assistant/lights/sleep'),
|
http.post<never, HomeAssistantLightTransition>('/api/health/home-assistant/lights/sleep'),
|
||||||
startWakeLight: () =>
|
startWakeLight: () =>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ async function submit() {
|
|||||||
: await healthApi.register(form)
|
: await healthApi.register(form)
|
||||||
saveTokens(output.accessToken, output.refreshToken)
|
saveTokens(output.accessToken, output.refreshToken)
|
||||||
showToast({ type: 'success', message: mode.value === 'login' ? '登录成功' : '注册成功' })
|
showToast({ type: 'success', message: mode.value === 'login' ? '登录成功' : '注册成功' })
|
||||||
await router.replace(mode.value === 'login' ? '/dashboard' : '/assessment')
|
await router.replace(mode.value === 'login' ? '/dashboard' : '/tcm-assessment')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(error instanceof Error ? error.message : '操作失败')
|
showToast(error instanceof Error ? error.message : '操作失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Button as VanButton, showToast } from 'vant'
|
import { Button as VanButton, showToast } from 'vant'
|
||||||
import AppTabBar from '../components/AppTabBar.vue'
|
import AppTabBar from '../components/AppTabBar.vue'
|
||||||
@@ -15,6 +15,7 @@ const loading = ref(true)
|
|||||||
const sceneRunning = ref<'sleep' | 'all-off'>()
|
const sceneRunning = ref<'sleep' | 'all-off'>()
|
||||||
const dashboard = ref<Dashboard>()
|
const dashboard = ref<Dashboard>()
|
||||||
const homeAssistant = ref<HomeAssistantStatus>()
|
const homeAssistant = ref<HomeAssistantStatus>()
|
||||||
|
const sceneRefreshTimers: ReturnType<typeof setTimeout>[] = []
|
||||||
const greeting = computed(() => {
|
const greeting = computed(() => {
|
||||||
const hour = new Date().getHours()
|
const hour = new Date().getHours()
|
||||||
if (hour < 11) return '早上好'
|
if (hour < 11) return '早上好'
|
||||||
@@ -51,12 +52,20 @@ async function executeScene(scene: 'sleep' | 'all-off') {
|
|||||||
const result = scene === 'sleep'
|
const result = scene === 'sleep'
|
||||||
? await healthApi.executeSleepScene()
|
? await healthApi.executeSleepScene()
|
||||||
: await healthApi.executeAllOffScene()
|
: await healthApi.executeAllOffScene()
|
||||||
showToast({ type: result.success ? 'success' : 'fail', message: result.message })
|
showToast({ type: result.status === 'Succeeded' ? 'success' : 'fail', message: result.message })
|
||||||
homeAssistant.value = await healthApi.homeAssistantStatus()
|
homeAssistant.value = await healthApi.homeAssistantStatus()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(error instanceof Error ? error.message : '场景执行失败')
|
showToast(error instanceof Error ? error.message : '场景执行失败')
|
||||||
} finally {
|
} finally {
|
||||||
sceneRunning.value = undefined
|
sceneRunning.value = undefined
|
||||||
|
for (const timer of sceneRefreshTimers) clearTimeout(timer)
|
||||||
|
sceneRefreshTimers.length = 0
|
||||||
|
for (const delay of [2000, 5000, 10000]) {
|
||||||
|
sceneRefreshTimers.push(setTimeout(async () => {
|
||||||
|
try { homeAssistant.value = await healthApi.homeAssistantStatus() }
|
||||||
|
catch { /* 下次刷新继续读取设备状态。 */ }
|
||||||
|
}, delay))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +75,9 @@ function logout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(load)
|
onMounted(load)
|
||||||
|
onUnmounted(() => {
|
||||||
|
for (const timer of sceneRefreshTimers) clearTimeout(timer)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -134,7 +146,7 @@ onMounted(load)
|
|||||||
</div>
|
</div>
|
||||||
<div class="startup-param-badge">
|
<div class="startup-param-badge">
|
||||||
<span class="lbl">助眠音箱</span>
|
<span class="lbl">助眠音箱</span>
|
||||||
<strong>体质音乐循环播放 · 60% 起每 5 秒降 1% 至停止 · {{ dashboard?.sceneConfig?.enableSpeakerSleepMode ? '开启睡眠模式' : '关闭睡眠模式' }}</strong>
|
<strong>体质音乐循环播放 · 50% 起每 5 秒降 1% 至停止 · {{ dashboard?.sceneConfig?.enableSpeakerSleepMode ? '开启睡眠模式' : '关闭睡眠模式' }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="startup-param-badge">
|
<div class="startup-param-badge">
|
||||||
<span class="lbl">8H 床垫</span>
|
<span class="lbl">8H 床垫</span>
|
||||||
|
|||||||
+124
-34
@@ -15,6 +15,7 @@ import {
|
|||||||
type HomeAssistantLightTransition,
|
type HomeAssistantLightTransition,
|
||||||
type HomeAssistantStatus,
|
type HomeAssistantStatus,
|
||||||
type MattressComfort,
|
type MattressComfort,
|
||||||
|
type TcmMusicTransition,
|
||||||
type TcmAssessmentResult
|
type TcmAssessmentResult
|
||||||
} from '../services/health'
|
} from '../services/health'
|
||||||
|
|
||||||
@@ -29,9 +30,15 @@ const switchingDeviceId = ref<string | null>(null)
|
|||||||
const lastUpdatedAt = ref('')
|
const lastUpdatedAt = ref('')
|
||||||
const actionFeedback = ref<{ type: 'success' | 'fail'; message: string }>()
|
const actionFeedback = ref<{ type: 'success' | 'fail'; message: string }>()
|
||||||
const lightTransition = ref<HomeAssistantLightTransition>()
|
const lightTransition = ref<HomeAssistantLightTransition>()
|
||||||
|
const musicTransition = ref<TcmMusicTransition>()
|
||||||
const lightActionBusy = ref(false)
|
const lightActionBusy = ref(false)
|
||||||
let lightProgressTimer: ReturnType<typeof setInterval> | undefined
|
let lightProgressTimer: ReturnType<typeof setInterval> | undefined
|
||||||
let lightPolling = false
|
let lightPolling = false
|
||||||
|
let musicProgressTimer: ReturnType<typeof setInterval> | undefined
|
||||||
|
let musicPolling = false
|
||||||
|
const sceneRefreshTimers: ReturnType<typeof setTimeout>[] = []
|
||||||
|
const currentUserId = ref<number>()
|
||||||
|
const mattressActiveSide = ref<'left' | 'right' | null>(null)
|
||||||
|
|
||||||
const parameterLabels: Record<string, string> = {
|
const parameterLabels: Record<string, string> = {
|
||||||
humidity: '目标湿度',
|
humidity: '目标湿度',
|
||||||
@@ -55,6 +62,7 @@ const parameterLabels: Record<string, string> = {
|
|||||||
mattress_right_mode: '床垫右侧承托模式'
|
mattress_right_mode: '床垫右侧承托模式'
|
||||||
}
|
}
|
||||||
const mattressFirmnessOptions = ['最软', '很软', '稍软', '稍硬', '很硬', '最硬']
|
const mattressFirmnessOptions = ['最软', '很软', '稍软', '稍硬', '很硬', '最硬']
|
||||||
|
const mattressModeOptions = ['无', '瑜伽助眠', '智能适配', '智能腰托']
|
||||||
const mattressFirmnessZones = [
|
const mattressFirmnessZones = [
|
||||||
{ key: 'left_shoulder', label: '左肩' }, { key: 'right_shoulder', label: '右肩' },
|
{ key: 'left_shoulder', label: '左肩' }, { key: 'right_shoulder', label: '右肩' },
|
||||||
{ key: 'left_back', label: '左背' }, { key: 'right_back', label: '右背' },
|
{ key: 'left_back', label: '左背' }, { key: 'right_back', label: '右背' },
|
||||||
@@ -100,7 +108,7 @@ const localParams = reactive({
|
|||||||
lightColorTemp: 2700,
|
lightColorTemp: 2700,
|
||||||
|
|
||||||
// 音箱
|
// 音箱
|
||||||
speakerVolume: 15,
|
speakerVolume: 50,
|
||||||
speakerSleepMode: true,
|
speakerSleepMode: true,
|
||||||
|
|
||||||
// 香薰机
|
// 香薰机
|
||||||
@@ -135,8 +143,37 @@ function numberValue(value: unknown): number | undefined {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadStatus(notify = false) {
|
function mattressModeStorageKey(): string | undefined {
|
||||||
loading.value = true
|
const deviceId = mattressDevice.value?.deviceId
|
||||||
|
return currentUserId.value && deviceId
|
||||||
|
? `evergreen-mattress-modes-${currentUserId.value}-${deviceId}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function readMattressModePreference(): { left: string; right: string } | undefined {
|
||||||
|
const key = mattressModeStorageKey()
|
||||||
|
if (!key) return undefined
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(localStorage.getItem(key) || 'null')
|
||||||
|
return mattressModeOptions.includes(saved?.left) && mattressModeOptions.includes(saved?.right) ? saved : undefined
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMattressModePreference() {
|
||||||
|
const key = mattressModeStorageKey()
|
||||||
|
if (!key) return
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key, JSON.stringify({
|
||||||
|
left: localParams.mattressLeftMode,
|
||||||
|
right: localParams.mattressRightMode
|
||||||
|
}))
|
||||||
|
} catch { /* 浏览器禁止本地存储时,本次页面内的预选仍可使用。 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStatus(notify = false, background = false) {
|
||||||
|
if (!background) loading.value = true
|
||||||
try {
|
try {
|
||||||
const [status, dashboard] = await Promise.all([
|
const [status, dashboard] = await Promise.all([
|
||||||
healthApi.homeAssistantStatus(),
|
healthApi.homeAssistantStatus(),
|
||||||
@@ -144,6 +181,7 @@ async function loadStatus(notify = false) {
|
|||||||
])
|
])
|
||||||
haStatus.value = status
|
haStatus.value = status
|
||||||
if (dashboard) {
|
if (dashboard) {
|
||||||
|
currentUserId.value = dashboard.user.id
|
||||||
tcmResult.value = dashboard.tcmResult
|
tcmResult.value = dashboard.tcmResult
|
||||||
mattressComfort.value = dashboard.mattressComfort
|
mattressComfort.value = dashboard.mattressComfort
|
||||||
}
|
}
|
||||||
@@ -173,8 +211,16 @@ async function loadStatus(notify = false) {
|
|||||||
|
|
||||||
localParams.mattressLeftHeating = clamp(numberValue(controls.mattress_left_heating?.state) ?? 1, 0, 4)
|
localParams.mattressLeftHeating = clamp(numberValue(controls.mattress_left_heating?.state) ?? 1, 0, 4)
|
||||||
localParams.mattressRightHeating = clamp(numberValue(controls.mattress_right_heating?.state) ?? 1, 0, 4)
|
localParams.mattressRightHeating = clamp(numberValue(controls.mattress_right_heating?.state) ?? 1, 0, 4)
|
||||||
if (controls.mattress_left_mode?.state) localParams.mattressLeftMode = controls.mattress_left_mode.state
|
const actualLeftMode = controls.mattress_left_mode?.state
|
||||||
if (controls.mattress_right_mode?.state) localParams.mattressRightMode = controls.mattress_right_mode.state
|
const actualRightMode = controls.mattress_right_mode?.state
|
||||||
|
const preferredModes = readMattressModePreference()
|
||||||
|
mattressActiveSide.value = actualRightMode && mattressModeOptions.includes(actualRightMode) && actualRightMode !== '无'
|
||||||
|
? 'right'
|
||||||
|
: actualLeftMode && mattressModeOptions.includes(actualLeftMode) && actualLeftMode !== '无' ? 'left' : null
|
||||||
|
localParams.mattressLeftMode = mattressActiveSide.value === 'left'
|
||||||
|
? actualLeftMode! : preferredModes?.left ?? '智能适配'
|
||||||
|
localParams.mattressRightMode = mattressActiveSide.value === 'right'
|
||||||
|
? actualRightMode! : preferredModes?.right ?? '智能适配'
|
||||||
for (const zone of mattressFirmnessZones) {
|
for (const zone of mattressFirmnessZones) {
|
||||||
const state = controls[`mattress_firmness_${zone.key}`]?.state
|
const state = controls[`mattress_firmness_${zone.key}`]?.state
|
||||||
if (state && state !== 'unavailable' && state !== 'unknown')
|
if (state && state !== 'unavailable' && state !== 'unknown')
|
||||||
@@ -184,6 +230,7 @@ async function loadStatus(notify = false) {
|
|||||||
if (controls.mattress_massage_right_part?.state) localParams.mattressMassageRightPart = controls.mattress_massage_right_part.state
|
if (controls.mattress_massage_right_part?.state) localParams.mattressMassageRightPart = controls.mattress_massage_right_part.state
|
||||||
if (controls.mattress_massage_level?.state) localParams.mattressMassageLevel = controls.mattress_massage_level.state
|
if (controls.mattress_massage_level?.state) localParams.mattressMassageLevel = controls.mattress_massage_level.state
|
||||||
await syncLightTransitionStatus()
|
await syncLightTransitionStatus()
|
||||||
|
await syncMusicTransitionStatus().catch(() => undefined)
|
||||||
lastUpdatedAt.value = new Intl.DateTimeFormat('zh-CN', {
|
lastUpdatedAt.value = new Intl.DateTimeFormat('zh-CN', {
|
||||||
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
||||||
}).format(new Date())
|
}).format(new Date())
|
||||||
@@ -193,10 +240,42 @@ async function loadStatus(notify = false) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : '获取设备状态失败'
|
const message = err instanceof Error ? err.message : '获取设备状态失败'
|
||||||
setFeedback('fail', message)
|
if (!background) {
|
||||||
showFailToast(message)
|
setFeedback('fail', message)
|
||||||
|
showFailToast(message)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
if (!background) loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncMusicTransitionStatus() {
|
||||||
|
const status = await healthApi.musicTransitionStatus()
|
||||||
|
musicTransition.value = status
|
||||||
|
if (status.hasVolume) localParams.speakerVolume = clamp(status.volumePercent, 0, 100)
|
||||||
|
if (status.running && !musicProgressTimer)
|
||||||
|
musicProgressTimer = setInterval(pollMusicTransition, 5000)
|
||||||
|
else if (!status.running && musicProgressTimer) {
|
||||||
|
clearInterval(musicProgressTimer)
|
||||||
|
musicProgressTimer = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollMusicTransition() {
|
||||||
|
if (musicPolling) return
|
||||||
|
musicPolling = true
|
||||||
|
try { await syncMusicTransitionStatus() }
|
||||||
|
catch { /* 网络短暂波动时保留最后一次已下发的音量,下个周期继续读取。 */ }
|
||||||
|
finally { musicPolling = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSceneStatusRefresh() {
|
||||||
|
for (const timer of sceneRefreshTimers) clearTimeout(timer)
|
||||||
|
sceneRefreshTimers.length = 0
|
||||||
|
for (const delay of [2000, 5000, 10000]) {
|
||||||
|
sceneRefreshTimers.push(setTimeout(() => {
|
||||||
|
if (!operationBusy.value) void loadStatus(false, true)
|
||||||
|
}, delay))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +451,7 @@ async function controlSpeaker(device: HomeAssistantDiscoveredDevice, play: boole
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 调节参数 API
|
// 调节参数 API
|
||||||
async function applyParam(deviceId: string, paramType: string, val: unknown, entityId?: string) {
|
async function applyParam(deviceId: string, paramType: string, val: unknown, entityId?: string): Promise<boolean> {
|
||||||
const key = `${deviceId}_${paramType}`
|
const key = `${deviceId}_${paramType}`
|
||||||
settingParam.value = key
|
settingParam.value = key
|
||||||
const label = parameterLabels[paramType]
|
const label = parameterLabels[paramType]
|
||||||
@@ -391,14 +470,17 @@ async function applyParam(deviceId: string, paramType: string, val: unknown, ent
|
|||||||
})
|
})
|
||||||
const message = res.message || `${label}设置成功`
|
const message = res.message || `${label}设置成功`
|
||||||
if (!res.success) throw new Error(message)
|
if (!res.success) throw new Error(message)
|
||||||
if (paramType !== 'volume') await loadStatus()
|
if (paramType === 'volume') await syncMusicTransitionStatus().catch(() => undefined)
|
||||||
|
else await loadStatus()
|
||||||
setFeedback('success', message)
|
setFeedback('success', message)
|
||||||
showSuccessToast(message)
|
showSuccessToast(message)
|
||||||
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : `${label}设置失败`
|
const message = err instanceof Error ? err.message : `${label}设置失败`
|
||||||
await loadStatus()
|
await loadStatus()
|
||||||
setFeedback('fail', message)
|
setFeedback('fail', message)
|
||||||
showFailToast(message)
|
showFailToast(message)
|
||||||
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
loadingToast.close()
|
loadingToast.close()
|
||||||
settingParam.value = null
|
settingParam.value = null
|
||||||
@@ -408,17 +490,13 @@ async function applyParam(deviceId: string, paramType: string, val: unknown, ent
|
|||||||
async function setMattressMode(side: 'left' | 'right', mode: string) {
|
async function setMattressMode(side: 'left' | 'right', mode: string) {
|
||||||
const device = mattressDevice.value
|
const device = mattressDevice.value
|
||||||
if (!device) return
|
if (!device) return
|
||||||
|
const succeeded = await applyParam(device.deviceId, `mattress_${side}_mode`, mode)
|
||||||
if (side === 'left') {
|
if (!succeeded) return
|
||||||
localParams.mattressLeftMode = mode
|
if (side === 'left') localParams.mattressLeftMode = mode
|
||||||
if (mode !== '无') localParams.mattressRightMode = '无'
|
else localParams.mattressRightMode = mode
|
||||||
await applyParam(device.deviceId, 'mattress_left_mode', mode)
|
if (mode !== '无') mattressActiveSide.value = side
|
||||||
return
|
else if (mattressActiveSide.value === side) mattressActiveSide.value = null
|
||||||
}
|
saveMattressModePreference()
|
||||||
|
|
||||||
localParams.mattressRightMode = mode
|
|
||||||
if (mode !== '无') localParams.mattressLeftMode = '无'
|
|
||||||
await applyParam(device.deviceId, 'mattress_right_mode', mode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setMattressSelect(parameterType: string, event: Event) {
|
async function setMattressSelect(parameterType: string, event: Event) {
|
||||||
@@ -458,8 +536,9 @@ async function executeScene(scene: 'sleep' | 'all-off') {
|
|||||||
: await healthApi.executeAllOffScene()
|
: await healthApi.executeAllOffScene()
|
||||||
if (!res.success) throw new Error(res.message || '场景执行失败')
|
if (!res.success) throw new Error(res.message || '场景执行失败')
|
||||||
await loadStatus()
|
await loadStatus()
|
||||||
setFeedback('success', res.message)
|
setFeedback(res.status === 'Partial' ? 'fail' : 'success', res.message)
|
||||||
showSuccessToast(res.message)
|
if (res.status === 'Partial') showFailToast(res.message)
|
||||||
|
else showSuccessToast(res.message)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : '场景执行失败'
|
const message = err instanceof Error ? err.message : '场景执行失败'
|
||||||
await loadStatus()
|
await loadStatus()
|
||||||
@@ -468,6 +547,7 @@ async function executeScene(scene: 'sleep' | 'all-off') {
|
|||||||
} finally {
|
} finally {
|
||||||
loadingToast.close()
|
loadingToast.close()
|
||||||
sceneRunning.value = undefined
|
sceneRunning.value = undefined
|
||||||
|
scheduleSceneStatusRefresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,6 +565,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (lightProgressTimer) clearInterval(lightProgressTimer)
|
if (lightProgressTimer) clearInterval(lightProgressTimer)
|
||||||
|
if (musicProgressTimer) clearInterval(musicProgressTimer)
|
||||||
|
for (const timer of sceneRefreshTimers) clearTimeout(timer)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -778,7 +860,7 @@ onUnmounted(() => {
|
|||||||
<!-- 音量滑块 -->
|
<!-- 音量滑块 -->
|
||||||
<div class="param-row">
|
<div class="param-row">
|
||||||
<div class="param-header">
|
<div class="param-header">
|
||||||
<span class="p-title">手动播放音量</span>
|
<span class="p-title">音箱音量(MiAirX 已下发)</span>
|
||||||
<strong class="p-val">{{ localParams.speakerVolume }}%</strong>
|
<strong class="p-val">{{ localParams.speakerVolume }}%</strong>
|
||||||
</div>
|
</div>
|
||||||
<VanSlider
|
<VanSlider
|
||||||
@@ -794,7 +876,7 @@ onUnmounted(() => {
|
|||||||
<span>50% 适中聆听</span>
|
<span>50% 适中聆听</span>
|
||||||
<span>100% 满格音量</span>
|
<span>100% 满格音量</span>
|
||||||
</div>
|
</div>
|
||||||
<small class="p-tip">手动调整音量会停止本次自动渐弱</small>
|
<small class="p-tip">{{ musicTransition?.running ? '自动渐弱中,每 5 秒更新一次显示' : '手动调整音量会停止本次自动渐弱' }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-row inline-row">
|
<div class="param-row inline-row">
|
||||||
@@ -837,6 +919,8 @@ onUnmounted(() => {
|
|||||||
</VanButton>
|
</VanButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<small class="p-tip">上方“释放中/已关闭”只表示香薰主开关;感应夜灯在下方单独显示和控制。</small>
|
||||||
|
|
||||||
<div class="params-body">
|
<div class="params-body">
|
||||||
<div class="param-row">
|
<div class="param-row">
|
||||||
<div class="param-header">
|
<div class="param-header">
|
||||||
@@ -1008,17 +1092,17 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<div class="param-row">
|
<div class="param-row">
|
||||||
<p class="mattress-mode-note">
|
<p class="mattress-mode-note">
|
||||||
左右承托共用气泵,同一时间仅能运行一侧;启动一侧后,另一侧会自动停止。
|
左右可分别预选模式,但共用气泵,同一时间仅最近启动的一侧实际运行;另一侧保留预选,处于待机状态。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-row">
|
<div class="param-row">
|
||||||
<span class="p-title">左侧承托模式</span>
|
<span class="p-title">左侧承托模式 · {{ mattressActiveSide === 'left' ? '运行中' : '预选待机' }}</span>
|
||||||
<div class="btn-group">
|
<div class="btn-group mattress-mode-buttons">
|
||||||
<button
|
<button
|
||||||
v-for="mode in ['无', '瑜伽助眠', '智能适配', '智能腰托']"
|
v-for="mode in mattressModeOptions"
|
||||||
:key="`left-${mode}`"
|
:key="`left-${mode}`"
|
||||||
:class="{ active: localParams.mattressLeftMode === mode }"
|
:class="{ active: localParams.mattressLeftMode === mode, running: mattressActiveSide === 'left' && localParams.mattressLeftMode === mode }"
|
||||||
:disabled="operationBusy"
|
:disabled="operationBusy"
|
||||||
@click="setMattressMode('left', mode)"
|
@click="setMattressMode('left', mode)"
|
||||||
>{{ mode }}</button>
|
>{{ mode }}</button>
|
||||||
@@ -1026,12 +1110,12 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-row">
|
<div class="param-row">
|
||||||
<span class="p-title">右侧承托模式</span>
|
<span class="p-title">右侧承托模式 · {{ mattressActiveSide === 'right' ? '运行中' : '预选待机' }}</span>
|
||||||
<div class="btn-group">
|
<div class="btn-group mattress-mode-buttons">
|
||||||
<button
|
<button
|
||||||
v-for="mode in ['无', '瑜伽助眠', '智能适配', '智能腰托']"
|
v-for="mode in mattressModeOptions"
|
||||||
:key="`right-${mode}`"
|
:key="`right-${mode}`"
|
||||||
:class="{ active: localParams.mattressRightMode === mode }"
|
:class="{ active: localParams.mattressRightMode === mode, running: mattressActiveSide === 'right' && localParams.mattressRightMode === mode }"
|
||||||
:disabled="operationBusy"
|
:disabled="operationBusy"
|
||||||
@click="setMattressMode('right', mode)"
|
@click="setMattressMode('right', mode)"
|
||||||
>{{ mode }}</button>
|
>{{ mode }}</button>
|
||||||
@@ -1598,6 +1682,12 @@ onUnmounted(() => {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mattress-mode-buttons button.active:not(.running) {
|
||||||
|
background: #e4f1ec;
|
||||||
|
color: #0c5045;
|
||||||
|
border-color: #87b9a7;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-group button:disabled,
|
.btn-group button:disabled,
|
||||||
.mini-toggle:disabled {
|
.mini-toggle:disabled {
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ async function submit() {
|
|||||||
<div class="parameter-preview">
|
<div class="parameter-preview">
|
||||||
<span>一键启动预设</span>
|
<span>一键启动预设</span>
|
||||||
<p>湿度 {{ result.sceneConfig.targetHumidity }}% · 灯光 {{ result.sceneConfig.lightColorTemperatureKelvin }}K</p>
|
<p>湿度 {{ result.sceneConfig.targetHumidity }}% · 灯光 {{ result.sceneConfig.lightColorTemperatureKelvin }}K</p>
|
||||||
<p>香氛{{ result.sceneConfig.enableFragrance ? '开启' : '关闭' }} · 体质音乐循环播放,从 60% 每 5 秒降 1% 至停止</p>
|
<p>香氛{{ result.sceneConfig.enableFragrance ? '开启' : '关闭' }} · 体质音乐循环播放,从 50% 每 5 秒降 1% 至停止</p>
|
||||||
<p>床垫加热按重庆冬季天气决定;软硬度按健康档案 BMI 给出可手动修改的演示起点。</p>
|
<p>床垫加热按重庆冬季天气决定;软硬度按健康档案 BMI 给出可手动修改的演示起点。</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user