| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- <template>
- <div class="remote-control_main-container">
- <div class="te-c" style="color: #8C92A7">遥控器模拟器</div>
- <el-row align="middle" class="mar-top-20">
- <el-col :span="3"></el-col>
- <el-col :span="6"><div class="button up" title="单击鼠标右键可切换配置" @click="runLeft" >左脚</div></el-col>
- <el-col :span="6"><div class="button up" @click="run_take_picture">拍照</div></el-col>
- <el-col :span="6"><div class="button up" title="单击鼠标右键可切换配置" @click="runRight">右脚</div></el-col>
- <el-col :span="3"></el-col>
- </el-row>
- <!-- 左脚配置、拍照配置、右脚配置按钮行 -->
- <el-row align="middle">
- <el-col :span="3"></el-col>
- <el-col :span="6"><div class="button up" :class="{ disabled: canStop }" @click="handleLeftRightClick($event, 'left')"><span style="font-size: 12px;">左脚配置</span></div></el-col>
- <!-- ========== 新增:拍照配置按钮 ========== -->
- <el-col :span="6">
- <div class="button up photo-config-btn" :class="{ disabled: canStop, active: showPhotoMenu }" @click="handlePhotoConfigClick">
- <span style="font-size: 12px;">拍照配置</span>
- </div>
- </el-col>
- <el-col :span="6"><div class="button up" :class="{ disabled: canStop }" @click="handleLeftRightClick($event, 'right')"><span style="font-size: 12px;">右脚配置</span></div></el-col>
- <el-col :span="3"></el-col>
- </el-row>
- <el-row align="middle">
- <el-col :span="3"></el-col>
- <el-col :span="6">
- <div class="button up" @click="switchLED(1)" v-log="{ describe: { action: 'LED开启' } }">LED开</div>
- </el-col>
- <el-col :span="6">
- <div class="button up" :class="{ disabled: !canStop }" @click="oneClickStop" v-log="{ describe: { action: '一键停止拍摄' } }">停止</div>
- </el-col>
- <el-col :span="6">
- <div class="button up" @click="switchLED(0)" v-log="{ describe: { action: 'LED关闭' } }">LED关</div>
- </el-col>
- <el-col :span="3"></el-col>
- </el-row>
- <!-- 左脚配置菜单 -->
- <div v-if="showLeftMenu" class="context-menu" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }" @click.stop>
- <div class="menu-title">左脚配置</div>
- <div class="menu-items">
- <div
- v-for="tab in leftTabs"
- :key="tab.id"
- class="menu-item"
- :class="{ active: leftConfigId === tab.id }"
- @click="selectConfig('left', tab.id)"
- >
- {{ leftConfigId === tab.id ? '✓ ' : '' }}{{ tab.mode_name }}
- </div>
- </div>
- </div>
- <!-- 右脚配置菜单 -->
- <div v-if="showRightMenu" class="context-menu" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }" @click.stop>
- <div class="menu-title">右脚配置</div>
- <div class="menu-items">
- <div
- v-for="tab in rightTabs"
- :key="tab.id"
- class="menu-item"
- :class="{ active: rightConfigId === tab.id }"
- @click="selectConfig('right', tab.id)"
- >
- {{ rightConfigId === tab.id ? '✓ ' : '' }}{{ tab.mode_name }}
- </div>
- </div>
- </div>
- <!-- ========== 新增:拍照配置菜单 ========== -->
- <div v-if="showPhotoMenu" class="context-menu photo-menu" :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }" @click.stop>
- <div class="menu-title">拍照配置</div>
- <div class="menu-items">
- <div
- v-for="point in pointList"
- :key="point.key"
- class="menu-item photo-point-item"
- :class="{ active: selectedPoint === point.key }"
- @click="selectPhotoPoint(point.key)"
- >
- <span class="point-name">{{ selectedPoint === point.key ? '✓ ' : '' }}点位 {{ point.key }}</span>
- <el-tag :type="point.connected ? 'success' : 'danger'" size="small">
- {{ point.connected ? '已连接' : '未连接' }}
- </el-tag>
- </div>
- </div>
- </div>
- <!-- 点击其他地方关闭菜单 -->
- <div v-if="showLeftMenu || showRightMenu || showPhotoMenu" class="menu-overlay" @click="closeMenus"></div>
- </div>
- </template>
- <script setup lang="ts">
- import { defineEmits, defineProps, ref, onMounted } from 'vue'
- import socket from "@/stores/modules/socket";
- import { getTopTabs, setLeftRightConfig, getAllUserConfigs } from '@/apis/setting';
- import { ElMessage } from 'element-plus';
- import client from "@/stores/modules/client";
- import icpList from '@/utils/ipc';
- // 初始化 WebSocket 状态管理
- const socketStore = socket()
- const clientStore = client()
- const props = defineProps<{
- canStop: boolean
- }>()
- const emit = defineEmits(['onRemoteControl'])
- // ========== 新增:点位配置 ==========
- const STORAGE_KEY = 'photo_point_name'
- const selectedPoint = ref('A')
- // 相机列表和点位列表
- const cameraList = ref<any[]>([])
- const pointList = ref<{ key: string; connected: boolean; cameraName: string }[]>([])
- // 初始化从 localStorage 读取配置
- onMounted(() => {
- const savedPoint = localStorage.getItem(STORAGE_KEY)
- if (savedPoint && ['A', 'B', 'C'].includes(savedPoint)) {
- selectedPoint.value = savedPoint
- }
- // 获取相机列表
- fetchCameraList()
- })
- // 获取相机列表并更新点位连接状态
- const fetchCameraList = () => {
- clientStore.ipc.invoke(icpList.camera.getCameraList).then(result => {
- if (result && result.CameraLists && Array.isArray(result.CameraLists)) {
- cameraList.value = result.CameraLists
- updatePointConnectionStatus()
- }
- }).catch(err => {
- console.error('获取相机列表失败:', err)
- })
- }
- // 更新点位连接状态(仅显示已绑定相机的点位)
- const updatePointConnectionStatus = async () => {
- let isoConfig: any = {}
- try {
- const result = await getAllUserConfigs({})
- if (result.code == 0 && result.data?.configs?.camera_configs?.iso_config) {
- isoConfig = result.data.configs.camera_configs.iso_config
- }
- } catch (err) {
- console.error('获取点位配置失败:', err)
- }
- // 只保留已绑定相机的点位
- const validPoints = Object.keys(isoConfig).filter(key => {
- const config = isoConfig[key]
- return config && config.CameraKey && config.CameraKey !== ''
- })
- if (validPoints.length === 0) {
- validPoints.push('A')
- }
- pointList.value = validPoints.map(key => {
- const cameraKey = isoConfig[key]?.CameraKey || ''
- let connected = false
- let cameraName = ''
- if (cameraKey) {
- const camera = cameraList.value.find(c => c.CameraKey === cameraKey)
- if (camera) {
- connected = camera.CameraStatus === true
- cameraName = camera.CameraName
- }
- }
- return { key, connected, cameraName }
- })
- // 验证当前选中的点位是否有效
- if (!pointList.value.some(p => p.key === selectedPoint.value)) {
- selectedPoint.value = pointList.value[0]?.key || 'A'
- }
- }
- // 暴露点位给父组件
- defineExpose({
- getSelectedPoint: () => selectedPoint.value
- })
- // ========== 配置切换相关 ==========
- const leftConfigId = ref(0)
- const rightConfigId = ref(0)
- const leftTabs = ref([]) // 左脚配置选项
- const rightTabs = ref([]) // 右脚配置选项
- const showLeftMenu = ref(false) // 显示左脚菜单
- const showRightMenu = ref(false) // 显示右脚菜单
- const showPhotoMenu = ref(false) // 显示拍照配置菜单
- const menuPosition = ref({ x: 0, y: 0 }) // 菜单位置
- const runLeft = async () => {
- emit('onRemoteControl','left')
- }
- const runRight = async () => {
- emit('onRemoteControl','right')
- }
- const run_take_picture = () => {
- emit('onRemoteControl','take_picture')
- }
- //LED
- const switchLED = async (value) => {
- socketStore.sendMessage({
- type: 'control_mcu',
- data: {
- device_name: "laser_position",
- value,
- }
- });
- }
- // 一键停止
- const oneClickStop = () => {
- if (!props.canStop) {
- return
- }
- socketStore.sendMessage({
- type: 'stop_action',
- })
- }
- // 右击左脚按钮
- const handleLeftRightClick = (event, type) => {
- if(props.canStop){
- return
- }
- event.preventDefault()
- menuPosition.value = { x: event.clientX, y: event.clientY }
- if (type === 'left') {
- showLeftMenu.value = true
- showRightMenu.value = false
- loadLeftConfig()
- } else {
- showRightMenu.value = true
- showLeftMenu.value = false
- loadRightConfig()
- }
- }
- // 加载左脚配置
- const loadLeftConfig = async () => {
- try {
- const result = await getTopTabs({ type: 0 })
- if (result.code === 0) {
- leftConfigId.value = result.data.select_configs.left
- leftTabs.value = result.data.tabs
- }
- } catch (error) {
- console.error('加载左脚配置失败:', error)
- }
- }
- // 加载右脚配置
- const loadRightConfig = async () => {
- try {
- const result = await getTopTabs({ type: 1 })
- if (result.code === 0) {
- rightConfigId.value = result.data.select_configs.right
- rightTabs.value = result.data.tabs
- }
- } catch (error) {
- console.error('加载右脚配置失败:', error)
- }
- }
- // 选择配置
- const selectConfig = async (type, configId) => {
- try {
- if(props.canStop){
- ElMessage.error('正在拍摄中,请稍候')
- return
- }
- const result = await setLeftRightConfig({
- type: type,
- id: configId
- })
- if (result.code === 0) {
- if (type === 'left') {
- leftConfigId.value = configId
- showLeftMenu.value = false
- } else {
- rightConfigId.value = configId
- showRightMenu.value = false
- }
- ElMessage.success('配置切换成功')
- }
- } catch (error) {
- console.error('切换配置失败:', error)
- ElMessage.error('配置切换失败')
- }
- }
- // 关闭菜单
- const closeMenus = () => {
- showLeftMenu.value = false
- showRightMenu.value = false
- showPhotoMenu.value = false
- }
- // ========== 新增:拍照配置菜单相关 ==========
- // 点击拍照配置按钮
- const handlePhotoConfigClick = (event: MouseEvent) => {
- if(props.canStop){
- return
- }
- event.preventDefault()
- menuPosition.value = { x: event.clientX, y: event.clientY }
- // 关闭其他菜单
- showLeftMenu.value = false
- showRightMenu.value = false
- // 获取最新的相机列表
- fetchCameraList()
- showPhotoMenu.value = true
- }
- // 选择拍照点位
- const selectPhotoPoint = (pointKey: string) => {
- selectedPoint.value = pointKey
- localStorage.setItem(STORAGE_KEY, pointKey)
- showPhotoMenu.value = false
- ElMessage.success(`已切换到点位 ${pointKey}`)
- }
- </script>
- <style scoped lang="scss">
- .remote-control_main-container {
- background: #EAECED;
- height: 450px;
- width: 300px;
- margin: 0 auto;
- margin-top: 10px;
- padding-top: 30px;
- background: url(@/assets/images/Photography/yk.png) 0px 0px no-repeat;
- background-size: 300px 450px;
- }
- .button {
- width: 60px;
- height: 60px;
- background: #fff;
- border-radius: 60px;
- line-height: 60px;
- color: #474747;
- margin: 0 auto;
- box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2);
- background: url(@/assets/images/Photography/hui.png) 0px 0px no-repeat;
- background-size: 60px 60px;
- }
- .el-row {
- min-height: 100px;
- }
- .button:hover {
- background: url(@/assets/images/Photography/lan.png) 0px 0px no-repeat;
- background-size: 60px 60px;
- cursor: pointer;
- }
- .button.stop {
- background: #ff4c00;
- color: white;
- border-radius: 10px;
- width: 120px;
- height: 40px;
- line-height: 40px;
- font-size: 14px;
- margin: 0 auto;
- }
- .button.stop:hover {
- background: #e64500;
- }
- .button.disabled {
- opacity: 0.5;
- cursor: not-allowed;
- pointer-events: none;
- }
- .context-menu {
- position: fixed;
- background: white;
- border: 1px solid #e4e7ed;
- border-radius: 4px;
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
- z-index: 2000;
- min-width: 120px;
- max-width: 200px;
- }
- .menu-title {
- padding: 8px 12px;
- font-size: 12px;
- font-weight: 600;
- color: #606266;
- border-bottom: 1px solid #ebeef5;
- background-color: #f5f7fa;
- }
- .menu-items {
- max-height: 150px;
- overflow: auto;
- }
- .menu-item {
- padding: 8px 12px;
- font-size: 14px;
- color: #606266;
- cursor: pointer;
- transition: background-color 0.2s;
- &:hover {
- background-color: #f5f7fa;
- }
- &.active {
- color: #2957FF;
- font-weight: 600;
- }
- }
- .menu-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 1999;
- }
- /* 新增:拍照配置样式 */
- .photo-config-btn.active {
- background: url(@/assets/images/Photography/lan.png) 0px 0px no-repeat !important;
- background-size: 60px 60px !important;
- }
- .photo-menu {
- min-width: 180px;
- }
- .photo-point-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- }
- .photo-point-item .point-name {
- flex-shrink: 0;
- }
- </style>
|