feat:绑定设备和设备详情

This commit is contained in:
李伊帆
2026-10-10 11:11:44 +08:00
parent 179bd3b66c
commit d4b02745e1
64 changed files with 3526 additions and 221 deletions

View File

@@ -0,0 +1,223 @@
import { onLoad } from '@dcloudio/uni-app'
import { computed, ref } from 'vue'
import { getErrorMessage } from '@/utils/api-error'
import { fetchDeviceList } from '../device.api'
import { fetchDeviceRuntime, updateDeviceRuntime } from './device-detail.api'
import type { AppDeviceRuntimeVo, DeviceDetailRouteQuery, FanLevel, RunMode } from './device-detail.types'
import {
AUTO_FAN_LEVEL_OPTIONS,
formatConnectionText,
formatPowerMode,
formatTvocLevel,
formatTvocValue,
isAutoRunMode,
isBluetoothConnected,
mapRuntimeToView,
RUN_MODE_OPTIONS,
} from './device-detail.utils'
import { useDeviceBluetoothSync } from './use-device-bluetooth-sync'
export function useDeviceDetail() {
const deviceId = ref(0)
const deviceMac = ref<string>()
const deviceName = ref('')
const locationName = ref('客厅')
const deviceStatus = ref<string>()
const bluetoothConnected = ref(false)
const charging = ref(false)
const isOnline = ref(false)
const loading = ref(false)
const updating = ref(false)
const runMode = ref<RunMode>('0')
const fanLevel = ref<FanLevel>('0')
const tvoc = ref<number | null>(null)
const powerMode = ref('0')
const filterLife = ref(0)
const filterRemainText = ref('预计还可使用 -- 天')
const filterReplaced = ref(false)
const filterReplaceDialogVisible = ref(false)
const batteryLevel = ref<number | null>(null)
const syncText = ref('数据实时同步中')
const tvocDisplay = computed(() => formatTvocValue(tvoc.value ?? undefined))
const tvocLevel = computed(() => formatTvocLevel(tvoc.value ?? undefined))
const powerModeText = computed(() => formatPowerMode(powerMode.value))
const batteryDisplay = computed(() =>
batteryLevel.value != null ? `${batteryLevel.value}%` : '--',
)
const connectionText = computed(() =>
formatConnectionText({
status: deviceStatus.value,
bluetoothConnected: bluetoothConnected.value ? '1' : '0',
locationName: locationName.value,
}),
)
const showFanLevelControl = computed(() => isAutoRunMode(runMode.value))
const fanLevelOptions = computed(() =>
showFanLevelControl.value ? AUTO_FAN_LEVEL_OPTIONS : [],
)
function applyRuntime(data: AppDeviceRuntimeVo) {
const view = mapRuntimeToView(data, deviceName.value)
deviceName.value = view.deviceName
runMode.value = view.runMode
fanLevel.value = view.fanLevel
tvoc.value = view.tvoc
powerMode.value = view.powerMode
filterLife.value = view.filterLife
filterRemainText.value = view.filterRemainText
filterReplaced.value = view.filterReplaced
isOnline.value = view.isOnline
if (data.status !== undefined) deviceStatus.value = data.status
syncText.value = '刚刚更新 · 数据实时同步中'
}
async function refreshDeviceMeta() {
if (!deviceId.value) return
try {
const list = await fetchDeviceList()
const device = list?.find((item) => item.deviceId === deviceId.value)
if (!device) return
if (device.mac) deviceMac.value = device.mac
bluetoothConnected.value = isBluetoothConnected(device.bluetoothConnected)
charging.value = device.charging === '1'
if (device.status !== undefined) deviceStatus.value = device.status
isOnline.value = device.status === '1'
} catch {
// 列表刷新失败时保留路由参数状态
}
}
async function loadRuntime() {
if (!deviceId.value) return
loading.value = true
try {
const [runtime] = await Promise.all([
fetchDeviceRuntime({ deviceId: deviceId.value }),
refreshDeviceMeta(),
])
applyRuntime(runtime)
} catch (err) {
uni.showToast({ title: getErrorMessage(err, '加载设备数据失败'), icon: 'none' })
} finally {
loading.value = false
}
}
async function patchRuntime(patch: { runMode?: RunMode; fanLevel?: FanLevel }) {
if (!deviceId.value || updating.value) return
updating.value = true
try {
await updateDeviceRuntime({
deviceId: deviceId.value,
...patch,
})
const data = await fetchDeviceRuntime({ deviceId: deviceId.value })
applyRuntime(data)
await refreshDeviceMeta()
} catch (err) {
uni.showToast({ title: getErrorMessage(err, '设置失败'), icon: 'none' })
} finally {
updating.value = false
}
}
function onSelectRunMode(mode: RunMode) {
if (runMode.value === mode) return
void patchRuntime({ runMode: mode })
}
function onSelectFanLevel(level: FanLevel) {
if (!isAutoRunMode(runMode.value)) return
if (fanLevel.value === level) return
void patchRuntime({ fanLevel: level })
}
function onSettings() {
uni.showToast({ title: '设备设置功能开发中', icon: 'none' })
}
function onFilterReplace() {
if (filterReplaced.value) return
filterReplaceDialogVisible.value = true
}
function onConfirmFilterReplace() {
filterReplaced.value = true
uni.showToast({ title: '操作成功', icon: 'success' })
}
const { startSyncLoop } = useDeviceBluetoothSync({
getDeviceId: () => deviceId.value,
getMac: () => deviceMac.value,
getRuntimePatch: () => ({
runMode: runMode.value,
fanLevel: fanLevel.value,
tvoc: tvoc.value ?? undefined,
powerMode: powerMode.value,
charging: charging.value ? '1' : '0',
}),
onBluetoothConnectedChange: (connected) => {
bluetoothConnected.value = connected
if (connected) {
isOnline.value = true
deviceStatus.value = '1'
}
},
onSyncTextChange: (text) => {
syncText.value = text
},
onSynced: async () => {
await refreshDeviceMeta()
const data = await fetchDeviceRuntime({ deviceId: deviceId.value })
applyRuntime(data)
},
})
onLoad((query?: DeviceDetailRouteQuery) => {
deviceId.value = Number(query?.deviceId) || 0
if (query?.name) deviceName.value = decodeURIComponent(String(query.name))
if (query?.locationName) {
locationName.value = decodeURIComponent(String(query.locationName))
}
if (query?.status) {
deviceStatus.value = String(query.status)
isOnline.value = query.status === '1'
}
if (query?.mac) deviceMac.value = decodeURIComponent(String(query.mac))
bluetoothConnected.value = isBluetoothConnected(query?.bluetoothConnected)
charging.value = query?.charging === '1'
void loadRuntime().then(() => {
startSyncLoop()
})
})
return {
deviceName,
connectionText,
isOnline,
tvocDisplay,
tvocLevel,
syncText,
runMode,
fanLevel,
powerModeText,
filterLife,
filterRemainText,
filterReplaced,
filterReplaceDialogVisible,
batteryDisplay,
batteryLevel,
charging,
loading,
showFanLevelControl,
runModeOptions: RUN_MODE_OPTIONS,
fanLevelOptions,
onSelectRunMode,
onSelectFanLevel,
onSettings,
onFilterReplace,
onConfirmFilterReplace,
}
}