import { ref, type Ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { useI18n } from 'vue-i18n' import { type PresetInfo, presetList, presetGoto, presetSet, presetRemove, getPTZCapabilities, ptzControl } from '@/api/camera' import type { LiveStreamDTO, PTZAction } from '@/types' import type { PTZCapabilities, LocalPreset, TimelinePoint } from '../types' const directionToAction: Record = { UP: 'up', DOWN: 'down', LEFT: 'left', RIGHT: 'right', STOP: 'stop' } export function usePTZ( currentMediaStream: Ref, timelinePoints: Ref, callbacks: { sortAndRenumberPoints: () => void saveTimelineConfig: () => void selectedPoint: Ref } ) { const { t } = useI18n({ useScope: 'global' }) const ptzSpeed = ref(50) const zoomValue = ref(0) const ptzPresetList = ref([]) const activePresetId = ref(null) const editingPresetId = ref(null) const editingPresetName = ref('') const cameraCapabilities = ref(null) const capabilitiesLoading = ref(false) const presetsLoading = ref(false) const activePanels = ref(['ptz', 'preset', 'camera']) function hasCameraConnection(): boolean { return !!currentMediaStream.value?.cameraId } async function handlePTZ(direction: string) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先选择直播流')) return } try { const command = directionToAction[direction] || 'stop' const res = await ptzControl({ cameraId, command, speed: ptzSpeed.value }) if (!res.success) { console.error('PTZ 控制失败', res.errMsg) } } catch (error) { console.error('PTZ 控制失败', error) } } async function handlePTZStop() { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) return try { await ptzControl({ cameraId, command: 'stop' }) } catch (error) { console.error('PTZ 停止失败', error) } } function formatZoomTooltip(val: number) { if (val === 0) return t('停止') return val > 0 ? `${t('放大')} ${val}` : `${t('缩小')} ${Math.abs(val)}` } async function handleZoomChange(val: number) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) return if (val === 0) { await ptzControl({ cameraId, command: 'stop' }) return } const command = val > 0 ? 'zoom_in' : 'zoom_out' await ptzControl({ cameraId, command, speed: Math.abs(val) }) } async function handleZoomRelease() { zoomValue.value = 0 const cameraId = currentMediaStream.value?.cameraId if (!cameraId) return await ptzControl({ cameraId, command: 'stop' }) } async function handleZoomIn() { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先选择直播流')) return } try { const res = await ptzControl({ cameraId, command: 'zoom_in', speed: ptzSpeed.value }) if (!res.success) console.error('Zoom in 失败', res.errMsg) } catch (error) { console.error('Zoom in 失败', error) } } async function handleZoomOut() { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先选择直播流')) return } try { const res = await ptzControl({ cameraId, command: 'zoom_out', speed: ptzSpeed.value }) if (!res.success) console.error('Zoom out 失败', res.errMsg) } catch (error) { console.error('Zoom out 失败', error) } } async function loadPTZPresets() { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先选择直播流')) return } presetsLoading.value = true try { const res = await presetList({ cameraId }) if (res.code === 200 && res.data) { ptzPresetList.value = res.data as PresetInfo[] } else { ptzPresetList.value = [] if (!res.success) ElMessage.error(res.errMsg || t('加载预置位失败')) } } catch (error) { console.error('加载 PTZ 预置位失败', error) ptzPresetList.value = [] } finally { presetsLoading.value = false } } async function handleGotoPTZPreset(preset: PresetInfo) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先配置摄像头连接')) return } try { activePresetId.value = preset.id const res = await presetGoto({ cameraId, presetId: parseInt(preset.id) }) if (res.code === 200) { ElMessage.success(`${t('已跳转到预置位')}: ${preset.name || preset.id}`) } else { ElMessage.error(res.errMsg || t('跳转失败')) } } catch (error) { console.error('跳转 PTZ 预置位失败', error) ElMessage.error(t('跳转失败')) } } async function handleEditPreset(preset: { id: string; name: string }) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先配置摄像头连接')) return } try { await ElMessageBox.confirm( `${t('将当前摄像头位置保存到预置位')} "${preset.name || `Preset ${preset.id}`}"?`, t('设置预置位'), { type: 'info' } ) const res = await presetSet({ cameraId, presetId: parseInt(preset.id), presetName: preset.name || `Preset ${preset.id}` }) if (res.code === 200) { ElMessage.success(`${t('预置位设置成功')}: ${preset.name || `Preset ${preset.id}`}`) } else { ElMessage.error(res.errMsg || t('设置失败')) } } catch (error) { if ((error as Error).toString().includes('cancel')) return console.error('设置预置位失败', error) ElMessage.error(t('设置失败')) } } async function handleDeletePreset(preset: { id: string; name: string }) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先配置摄像头连接')) return } try { await ElMessageBox.confirm(`${t('确定删除预置位')} "${preset.name || `Preset ${preset.id}`}"?`, t('删除确认'), { type: 'warning' }) const res = await presetRemove({ cameraId, presetId: parseInt(preset.id) }) if (res.success) { ElMessage.success(t('删除成功')) loadPTZPresets() } else { ElMessage.error(res.errMsg || t('删除失败')) } } catch (error) { if (error !== 'cancel') { console.error('删除预置位失败', error) ElMessage.error(t('删除失败')) } } } // Local preset operations (based on timelinePoints) function startEditPresetName(preset: LocalPreset) { editingPresetId.value = preset.id editingPresetName.value = preset.name } function cancelEditPresetName() { editingPresetId.value = null editingPresetName.value = '' } function savePresetName(preset: LocalPreset) { const newName = editingPresetName.value.trim() if (!newName || newName === preset.name) { cancelEditPresetName() return } const point = timelinePoints.value.find((p) => p.id === preset.pointId) if (!point) { cancelEditPresetName() return } point.presetName = newName callbacks.saveTimelineConfig() cancelEditPresetName() } async function handleGotoLocalPreset(preset: LocalPreset) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先配置摄像头连接')) return } const point = timelinePoints.value.find((p) => p.id === preset.pointId) if (!point?.presetId) { ElMessage.warning(t('未找到对应的预置位')) return } try { activePresetId.value = preset.id const res = await presetGoto({ cameraId, presetId: point.presetId }) if (res.code === 200) { ElMessage.success(`${t('已跳转到')}: ${preset.name}`) } else { ElMessage.error(res.errMsg || t('跳转失败')) } } catch (error) { console.error('跳转预置位失败', error) ElMessage.error(t('跳转失败')) } } async function handleUpdateLocalPreset(preset: LocalPreset) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先配置摄像头连接')) return } const point = timelinePoints.value.find((p) => p.id === preset.pointId) if (!point?.presetId) { ElMessage.warning(t('未找到对应的预置位')) return } try { await ElMessageBox.confirm(`${t('将当前摄像头位置保存到预置位')} "${preset.name}"?`, t('设置预置位'), { type: 'info' }) const res = await presetSet({ cameraId, presetId: point.presetId, presetName: preset.name }) if (res.code === 200) { ElMessage.success(`${t('预置位设置成功')}: ${preset.name}`) } else { ElMessage.error(res.errMsg || t('设置失败')) } } catch (error) { if ((error as Error).toString().includes('cancel')) return console.error('设置预置位失败', error) ElMessage.error(t('设置失败')) } } async function handleDeleteLocalPreset(preset: LocalPreset) { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) { ElMessage.warning(t('请先配置摄像头连接')) return } const point = timelinePoints.value.find((p) => p.id === preset.pointId) if (!point?.presetId) { ElMessage.warning(t('未找到对应的预置位')) return } try { await ElMessageBox.confirm(`${t('确定删除预置位')} "${preset.name}"?`, t('删除确认'), { type: 'warning' }) const res = await presetRemove({ cameraId, presetId: point.presetId }) if (res.success) { const index = timelinePoints.value.findIndex((p) => p.id === preset.pointId) if (index !== -1) { timelinePoints.value.splice(index, 1) callbacks.sortAndRenumberPoints() callbacks.saveTimelineConfig() if (callbacks.selectedPoint.value?.id === preset.pointId) { callbacks.selectedPoint.value = null } } ElMessage.success(t('删除成功')) } else { ElMessage.error(res.errMsg || t('删除失败')) } } catch (error) { if (error !== 'cancel') { console.error('删除预置位失败', error) ElMessage.error(t('删除失败')) } } } async function loadCameraCapabilities() { const cameraId = currentMediaStream.value?.cameraId if (!cameraId) return capabilitiesLoading.value = true try { const res = await getPTZCapabilities({ cameraId }) if (res.success && res.data) { cameraCapabilities.value = res.data as PTZCapabilities } else { cameraCapabilities.value = null } } catch (error) { console.error('加载摄像头能力失败', error) cameraCapabilities.value = null } finally { capabilitiesLoading.value = false } } return { ptzSpeed, zoomValue, ptzPresetList, activePresetId, editingPresetId, editingPresetName, cameraCapabilities, capabilitiesLoading, presetsLoading, activePanels, hasCameraConnection, handlePTZ, handlePTZStop, formatZoomTooltip, handleZoomChange, handleZoomRelease, handleZoomIn, handleZoomOut, loadPTZPresets, handleGotoPTZPreset, handleEditPreset, handleDeletePreset, startEditPresetName, cancelEditPresetName, savePresetName, handleGotoLocalPreset, handleUpdateLocalPreset, handleDeleteLocalPreset, loadCameraCapabilities } }