This commit is contained in:
fuenmao
2026-09-28 16:36:47 +08:00
parent 429d8d7e86
commit 65eff42cc8
4 changed files with 65 additions and 28 deletions
+3 -3
View File
@@ -294,9 +294,9 @@ export const healthApi = {
executeSleepScene: () =>
http.post<never, HomeAssistantSceneResult>('/api/health/home-assistant/scenes/sleep', undefined, { timeout: 90000 }),
executeAllOffScene: () =>
http.post<never, HomeAssistantSceneResult>('/api/health/home-assistant/scenes/all-off'),
http.post<never, HomeAssistantSceneResult>('/api/health/home-assistant/scenes/all-off', undefined, { timeout: 90000 }),
switchDevice: (payload: { deviceId: string; turnOn: boolean; entityId?: string }) =>
http.post<never, HomeAssistantDeviceSwitchOutput>('/api/health/home-assistant/devices/switch', payload),
http.post<never, HomeAssistantDeviceSwitchOutput>('/api/health/home-assistant/devices/switch', payload, { timeout: 45000 }),
setParameter: (payload: HomeAssistantSetParameterInput) =>
http.post<never, HomeAssistantSetParameterOutput>('/api/health/home-assistant/devices/set-parameter', payload)
http.post<never, HomeAssistantSetParameterOutput>('/api/health/home-assistant/devices/set-parameter', payload, { timeout: 45000 })
}
+1 -1
View File
@@ -134,7 +134,7 @@ onMounted(load)
</div>
<div class="startup-param-badge">
<span class="lbl">助眠音箱</span>
<strong>{{ dashboard?.sceneConfig?.enableSpeakerSleepMode ? '开启睡眠模式' : '关闭睡眠模式' }} · {{ dashboard?.sceneConfig?.speakerVolumePercent || 15 }}%</strong>
<strong>体质音乐循环播放 · 60% 起每 5 秒降 1% 至停止 · {{ dashboard?.sceneConfig?.enableSpeakerSleepMode ? '开启睡眠模式' : '关闭睡眠模式' }}</strong>
</div>
<div class="startup-param-badge">
<span class="lbl">8H 床垫</span>
+59 -22
View File
@@ -287,11 +287,6 @@ function isDeviceTurnedOn(device?: HomeAssistantDiscoveredDevice): boolean {
if (!device || !device.primaryControlEntityId) return false
const target = device.entities.find(e => e.entityId === device.primaryControlEntityId)
if (!target) return false
if (target.domain === 'media_player') {
const muted = target.attributes?.is_volume_muted
if (typeof muted === 'boolean') return !muted
if (typeof muted === 'string') return muted.toLowerCase() !== 'true'
}
return target.state === 'on' || target.state === 'playing'
}
@@ -314,12 +309,9 @@ async function toggleDevice(device?: HomeAssistantDiscoveredDevice) {
return
}
const nextOn = !isDeviceTurnedOn(device)
const isMediaDevice = device.category === 'Media'
switchingDeviceId.value = device.deviceId
const loadingToast = showLoadingToast({
message: isMediaDevice
? (nextOn ? '正在取消静音…' : '正在静音…')
: (nextOn ? '正在开启设备…' : '正在关闭设备…'),
message: nextOn ? '正在开启设备…' : '正在关闭设备…',
forbidClick: true,
duration: 0
})
@@ -329,9 +321,7 @@ async function toggleDevice(device?: HomeAssistantDiscoveredDevice) {
turnOn: nextOn,
entityId: device.primaryControlEntityId
})
const message = res.message || (isMediaDevice
? (nextOn ? '音箱声音已开启' : '音箱已静音')
: (nextOn ? '设备已开启' : '设备已关闭'))
const message = res.message || (nextOn ? '设备已开启' : '设备已关闭')
if (!res.success) throw new Error(message)
await loadStatus()
// 命令接口已经回读并确认真实状态;覆盖可能仍处于短暂缓存中的状态列表。
@@ -349,6 +339,38 @@ async function toggleDevice(device?: HomeAssistantDiscoveredDevice) {
}
}
async function controlSpeaker(device: HomeAssistantDiscoveredDevice, play: boolean) {
if (!device.primaryControlEntityId) {
const message = '音箱缺少已确认的控制实体'
setFeedback('fail', message)
showFailToast(message)
return
}
switchingDeviceId.value = device.deviceId
const loadingToast = showLoadingToast({
message: play ? '正在发送播放指令…' : '正在发送停止指令…',
forbidClick: true,
duration: 0
})
try {
const res = await healthApi.switchDevice({
deviceId: device.deviceId,
entityId: device.primaryControlEntityId,
turnOn: play
})
if (!res.success) throw new Error(res.message || '音箱控制失败')
setFeedback('success', res.message)
showSuccessToast(res.message)
} catch (err) {
const message = err instanceof Error ? err.message : '音箱控制失败'
setFeedback('fail', message)
showFailToast(message)
} finally {
loadingToast.close()
switchingDeviceId.value = null
}
}
// 调节参数 API
async function applyParam(deviceId: string, paramType: string, val: unknown, entityId?: string) {
const key = `${deviceId}_${paramType}`
@@ -369,7 +391,7 @@ async function applyParam(deviceId: string, paramType: string, val: unknown, ent
})
const message = res.message || `${label}设置成功`
if (!res.success) throw new Error(message)
await loadStatus()
if (paramType !== 'volume') await loadStatus()
setFeedback('success', message)
showSuccessToast(message)
} catch (err) {
@@ -728,27 +750,35 @@ onUnmounted(() => {
<span class="category-tag">睡眠音频</span>
<h3>{{ speakerDevice.name }}</h3>
<span class="status-dot" :class="{ online: speakerDevice.online }">
{{ speakerDevice.online ? '设备在线' : '设备离线' }}
{{ speakerDevice.online ? 'HA 实体在线' : 'HA 实体离线' }}
</span>
</div>
</div>
<div class="speaker-actions">
<VanButton
size="small"
round
:type="isDeviceTurnedOn(speakerDevice) ? 'primary' : 'default'"
:color="isDeviceTurnedOn(speakerDevice) ? '#0c5045' : ''"
type="primary"
color="#0c5045"
:loading="switchingDeviceId === speakerDevice.deviceId"
:disabled="operationBusy"
@click="toggleDevice(speakerDevice)"
>
{{ isDeviceTurnedOn(speakerDevice) ? '声音开启' : '已静音' }}
</VanButton>
@click="controlSpeaker(speakerDevice, true)"
>播放测试音乐</VanButton>
<VanButton
size="small"
round
:disabled="operationBusy"
@click="controlSpeaker(speakerDevice, false)"
>停止播放</VanButton>
</div>
<small class="p-tip">手动按钮播放测试音频;一键开启按体质选曲循环播放,音量自动渐弱至停止</small>
<div class="params-body">
<!-- 音量滑块 -->
<div class="param-row">
<div class="param-header">
<span class="p-title">助眠播放音量</span>
<span class="p-title">手动播放音量</span>
<strong class="p-val">{{ localParams.speakerVolume }}%</strong>
</div>
<VanSlider
@@ -764,12 +794,13 @@ onUnmounted(() => {
<span>50% 适中聆听</span>
<span>100% 满格音量</span>
</div>
<small class="p-tip">手动调整音量会停止本次自动渐弱</small>
</div>
<div class="param-row inline-row">
<div>
<div class="p-title">音箱睡眠模式</div>
<small class="p-tip">使用设备当前音源,仅切换睡眠模式并调整音量</small>
<small class="p-tip">睡眠模式由 Home Assistant 单独控制</small>
</div>
<button
class="mini-toggle"
@@ -1347,6 +1378,12 @@ onUnmounted(() => {
border-bottom: 1px solid rgba(18, 63, 55, 0.06);
}
.speaker-actions {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.category-tag {
font-size: 10px;
color: #0c5045;
+2 -2
View File
@@ -134,11 +134,11 @@ async function submit() {
<div><span>音乐风格建议</span><strong>{{ result.musicRecommendation }}</strong></div>
<div><span>室温建议</span><strong>{{ result.roomTemperatureRecommendation }}</strong></div>
</div>
<p class="capability-note">香型需人工更换,音乐风格暂不能自动选择,室温仅供参考;一键启动只下发当前已支持的设备参数。</p>
<p class="capability-note">香型需人工更换,体质音乐可由音箱自动播放,室温仅供参考;一键启动只下发当前已支持的设备参数。</p>
<div class="parameter-preview">
<span>一键启动预设</span>
<p>湿度 {{ result.sceneConfig.targetHumidity }}% · 灯光 {{ result.sceneConfig.lightColorTemperatureKelvin }}K</p>
<p>香氛{{ result.sceneConfig.enableFragrance ? '开启' : '关闭' }} · 音箱睡眠模式 {{ result.sceneConfig.speakerVolumePercent }}% 音量</p>
<p>香氛{{ result.sceneConfig.enableFragrance ? '开启' : '关闭' }} · 体质音乐循环播放,从 60% 每 5 秒降 1% 至停止</p>
<p>床垫加热按重庆冬季天气决定;软硬度按健康档案 BMI 给出可手动修改的演示起点。</p>
</div>
</section>