111
This commit is contained in:
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user