on-site-robots-screen/src/views/home/components/control-deck-new.vue

1248 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 机器人操控面板 -->
<div class="control-deck child-container">
<!-- 第一行 -->
<n-grid :cols="24">
<n-grid-item :span="9">
<div class="row-1-item">摄像头</div>
</n-grid-item>
<n-grid-item :span="6">
<!-- <div class="row-1-item">升降杆</div> -->
<div
class="row-1-item"
style="display: flex; align-items: center; justify-content: center"
>
<n-icon size="26">
<!-- 绿色 -->
<BatteryFullSharp
color="#16E607"
v-if="robotBaseInfo.dl >= 80 && robotBaseInfo.type != '充电中'"
/>
<BatteryHalfSharp
color="#E69807"
v-else-if="robotBaseInfo.dl >= 50 && robotBaseInfo.type != '充电中'"
/>
<BatteryDeadSharp
color="#F90303"
v-else-if="robotBaseInfo.type != '充电中'"
/>
<BatteryChargingSharp
color="#16E607"
v-if="robotBaseInfo.type == '充电中'"
/>
</n-icon>
<span style="margin-left: 4px; font-size: 13px"> {{ robotBaseInfo.dl }}%</span>
</div>
</n-grid-item>
<n-grid-item :span="9">
<div class="row-1-item">底盘</div>
</n-grid-item>
</n-grid>
<!-- 第二行 -->
<n-grid :cols="24">
<n-grid-item :span="9">
<div class="row-2-item">
<!-- <div>速度</div>
<div>30</div> -->
<!-- <n-button
type="primary"
size="small"
@click="handleStopAndOverRouter('15')"
v-if="robotBaseInfo.type.includes('巡航')"
>
暂停巡航
</n-button> -->
</div>
</n-grid-item>
<n-grid-item :span="6">
<div class="row-2-item">
<!-- <div>高度</div>
<div>
{{ currentHeight }}
</div> -->
</div>
<div class="row-2-item">
<div></div>
<div style="width: 70px">
{{ robotBaseInfo.type }}
</div>
</div>
</n-grid-item>
<n-grid-item :span="9">
<div class="row-2-item">
<!-- <div>速度</div>
<div>30</div> -->
<!-- <n-button
type="primary"
size="small"
@click="handleStopAndOverRouter('14')"
v-if="robotBaseInfo.value.type.includes('巡航')"
>
结束巡航
</n-button> -->
</div>
</n-grid-item>
</n-grid>
<!-- 第三行 -->
<n-grid :cols="10">
<n-grid-item :span="5">
<div class="row-3-item-1">
<!-- 上下左右控制按钮 -->
<img
class="arrow-top hand-direction"
@mousedown="handleChangeCamera('up')"
@mouseup="handleChangeCamera('stop')"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<img
class="arrow-right hand-direction"
@mousedown="handleChangeCamera('right')"
@mouseup="handleChangeCamera('stop')"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<img
class="arrow-bottom hand-direction"
@mousedown="handleChangeCamera('down')"
@mouseup="handleChangeCamera('stop')"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<img
class="arrow-left hand-direction"
@mousedown="handleChangeCamera('left')"
@mouseup="handleChangeCamera('stop')"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<!-- 中间的按钮 -->
<div class="row-3-item-1-center">
<img
class="center-icon"
@click="handleChangeCamera('stop')"
:src="isStopLeft ? stopImg : startImg"
/>
</div>
<!-- 描述文字 -->
<div class="text-top description-text">正前</div>
<div class="text-bottom description-text">正后</div>
<div class="text-left description-text">正左</div>
<div class="text-right description-text">正右</div>
<!-- 增加减少按钮 -->
<div class="add-reduce-btn">
<span>缩放</span>
<img
style="margin-bottom: 2px"
@mousedown="handleChangeZoomCamera('ZoomIn')"
@mouseup="handleStopDeviceCameraZoom()"
src="@/assets/home-imgs/control-2-add.png"
/>
<img
@mousedown="handleChangeZoomCamera('ZoomOut')"
@mouseup="handleStopDeviceCameraZoom()"
src="@/assets/home-imgs/control-2-reduce.png"
/>
</div>
</div>
</n-grid-item>
<n-grid-item :span="3" v-if="false">
<div class="row-3-item-2" ref="container">
<!-- 上下滑动调节 -->
<span
ref="draggable"
class="up-down-box"
@mousedown="startDrag"
:style="{ top: currentTop + 'px' }"
/>
</div>
</n-grid-item>
<n-grid-item :span="5">
<div class="row-3-item-3">
<!-- 上下左右控制按钮 -->
<img
class="arrow-top"
@mousedown="handleChangeRobot('5')"
@mouseup="handleChangeRobot(9)"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<img
class="arrow-right"
@mousedown="handleChangeRobot('8')"
@mouseup="handleChangeRobot(9)"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<img
class="arrow-bottom"
@mousedown="handleChangeRobot('6')"
@mouseup="handleChangeRobot(9)"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<img
class="arrow-left"
@mousedown="handleChangeRobot('7')"
@mouseup="handleChangeRobot(9)"
src="@/assets/home-imgs/control-2-arrow.png"
/>
<!-- 描述文字 -->
<div class="text-top description-text">前进</div>
<div class="text-bottom description-text">后退</div>
<div class="text-left description-text">左转</div>
<div class="text-right description-text">右转</div>
<div class="row-3-item-1-center">
<img
class="center-icon"
@click="handleChangeRobot(9)"
:src="isStopRight ? startImg : stopImg"
/>
</div>
</div>
</n-grid-item>
</n-grid>
<!-- 第四行 -->
<n-grid :cols="12" class="row-4">
<n-grid-item :span="3">
<div>警灯</div>
</n-grid-item>
<n-grid-item :span="3">
<div>
<n-radio
value="关闭"
name="basic-demo"
@change="handleChange1"
:checked="checkedValue === '关闭'"
>
关闭
</n-radio>
</div>
</n-grid-item>
<n-grid-item :span="3">
<div>
<n-radio
value="开启"
name="basic-demo"
@change="handleChange2"
:checked="checkedValue === '开启'"
>
开启
</n-radio>
</div>
</n-grid-item>
<n-grid-item :span="3">
<div class="row-4-item">
<n-radio
value="自动"
name="basic-demo"
@change="handleChange3"
:checked="checkedValue === '自动'"
>
自动
</n-radio>
</div>
</n-grid-item>
</n-grid>
<!-- 第五行 -->
<n-grid :cols="12" class="row-5">
<n-grid-item :span="3">
<div>录音播放</div>
</n-grid-item>
<n-grid-item :span="3">
<div>
<div>
<!-- <n-select
size="tiny"
v-model:value="selectValue"
:options="selectOptions"
/> -->
<!-- <n-select
size="small"
v-model:value="selectValue"
:options="selectOptions"
/> -->
<!-- <n-select
:options="selectOptions"
size="small"
:dropdown-style="{
borderRadius: '4px',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
color: '#fff',
}"
/> -->
<!-- <n-button type="info" size="small">请上传</n-button> -->
<n-upload
:accept="audioMimeTypes"
:show-file-list="false"
:default-upload="false"
@before-upload="beforeUpload"
@change="handleChangeUpload"
v-if="!audioInfo"
>
<n-button type="info" size="small">请上传</n-button>
</n-upload>
<n-popselect
trigger="click"
v-model:value="audioValue"
:options="audioOptions"
@update:value="handleChangeAudio"
v-else
>
<n-button type="info" size="small">
{{ audioInfo?.fileName }}
</n-button>
</n-popselect>
</div>
<!-- <div style="margin-top: 16px">
<n-radio
:checked="checkedValue === '循环播放'"
value="循环播放"
name="basic-demo"
@change="handleChange4"
>
循环播放
</n-radio>
</div> -->
</div>
</n-grid-item>
<n-grid-item :span="6">
<div class="row-5-item">
<div class="row-5-item-1">
<img src="@/assets/home-imgs/control-3-voice.png" alt="" />
<div style="width: 60%">
<n-slider
:step="10"
:default-value="50"
:format-tooltip="formatTooltip"
@update:value="handleChangeVolume"
/>
</div>
</div>
<!-- <div class="row-5-item-1" style="margin-top: 16px">
<img src="@/assets/home-imgs/control-3-video.png" alt="" />
<div style="width: 60%">
<n-slider
:default-value="50"
:step="10"
:format-tooltip="formatTooltip"
/>
</div>
</div> -->
</div>
</n-grid-item>
</n-grid>
<!-- 第六行 -->
<n-grid :cols="10" class="row-6">
<n-grid-item :span="4">
<div class="row-6-item" @click="handleChangeRobot(10)">回桩充电</div>
</n-grid-item>
<!-- <n-grid-item :span="3">
<div class="row-6-item-1">
<img
alt=""
v-if="!isOpenMK"
@click="onHandleOpenOrCloseMK(1)"
src="@/assets/home-imgs/control-3-mk.png"
/>
<span v-else style="cursor: pointer" @click="onHandleOpenOrCloseMK(2)"
>结束喊话</span
>
</div>
</n-grid-item> -->
</n-grid>
</div>
<n-modal v-model:show="selectPlayType">
<n-card
size="huge"
role="dialog"
aria-modal="true"
:bordered="false"
class="modal-container"
>
<n-form
ref="formRef"
size="small"
label-placement="left"
style="margin-top: 10px"
:model="playParams"
>
<n-form-item label="播放类型:">
<n-radio-group v-model:value="playParams.playType" name="radiogroup">
<n-space>
<n-radio :value="1"> 次数播放 </n-radio>
<n-radio :value="0"> 循环播放 </n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="播放次数:" v-if="playParams.playType === 1">
<n-input-number
v-model:value="playParams.playCount"
placeholder="输入播放次数"
:show-button="false"
/>
</n-form-item>
<n-button type="info" @click="handlePlayAudio"> 确定</n-button>
<n-button color="#6E90A9" style="margin-left: 8px" @click="selectPlayType = false">
取消
</n-button>
</n-form>
</n-card>
</n-modal>
</template>
<script setup>
import { ref, onMounted, h } from 'vue'
import {
stopDeviceCameraApi,
changeDeviceCameraApi,
stopDeviceCameraZoomApi,
changeDeviceCameraZoomInApi,
changeDeviceCameraZoomOutApi,
} from '@/utils/initLogin'
import { handleRobotActionApi, uploadAudioApi, getAudioInfoApi, deleteAudioApi } from '@/api/home'
import { useRobotDataStore } from '@/store/robot'
import { getRobotTokenFn, getRobotDeviceListFn } from '@/utils/getRobotInfo.js'
import {
BatteryHalfSharp,
BatteryDeadSharp,
BatteryFullSharp,
BatteryChargingSharp,
} from '@vicons/ionicons5'
import { openDeviceMKApi, stopDeviceMKApi } from '@/utils/initLogin'
import { useMessage, useDialog, NRadio, NRadioGroup, NInputNumber, NSpace } from 'naive-ui'
import { debounce } from 'lodash'
import stopImg from '@/assets/home-imgs/control-2-stop.png'
import startImg from '@/assets/home-imgs/control-2-start.png'
const dialog = useDialog()
const message = useMessage()
const robotData = useRobotDataStore()
const isStopLeft = ref(true)
const isStopRight = ref(false)
const upDownHeight = ref(30)
const container = ref(null)
const draggable = ref(null)
const isDragging = ref(false)
const currentTop = ref(0)
const startY = ref(0)
const startTop = ref(0)
const checkedValue = ref('关闭')
const selectValue = ref('')
const deviceToken = ref('')
const deviceInfo = ref({})
const isZoom = ref(false)
const robotBaseInfo = ref({})
const selectPlayType = ref(false)
const audioValue = ref('')
const audioInfo = ref(null)
const playParams = ref({
playType: 1,
playCount: 1,
})
const audioMimeTypes = ref(
'.pcm,.wav,.aac,audio/wav,audio/x-wav,audio/x-aiff,audio/aac,audio/x-aac',
)
// 音频操作选项
const audioOptions = ref([
{
label: '播放',
value: '播放',
},
{
label: '删除',
value: '删除',
},
{
label: '暂停',
value: '暂停',
},
])
const beforeUpload = ({ file }) => {
const allowedExtensions = ['.pcm', '.wav', '.aac']
const fileName = file.name.toLowerCase()
const isValid = allowedExtensions.some((ext) => fileName.endsWith(ext))
if (!isValid) {
message.error('只能上传 PCM、WAV、AAC 格式的音频文件')
return false
}
return true
}
// 暂停和停止巡航
const handleStopAndOverRouter = async (type) => {
return new Promise((resolve) => {
dialog.warning({
title: '提示',
content: `当前机器人正在巡检中,确定${type == 15 ? '暂停' : '结束'}巡检吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
const { data: res } = await handleRobotActionApi({
puId: robotData.robotInfo?.puId,
type: type,
})
if (res.code == 200) {
message.success(`${type == 15 ? '暂停' : '结束'}巡检`)
getRobotBaseInfo()
resolve(false) // 已暂停巡检返回false表示不处于巡检状态
} else {
message.error(`${type == 15 ? '暂停' : '结束'}巡检失败` + res.message)
resolve(true) // 暂停失败,仍然处于巡检状态
}
} catch (error) {
message.error(`${type == 15 ? '暂停' : '结束'}巡检出错`)
resolve(true) // 出错时,假设仍然处于巡检状态
}
},
onNegativeClick: () => {
resolve(true) // 用户取消,仍然处于巡检状态
},
})
})
}
// 判断当前机器人是否在 巡检中 如果在巡检中 则不可进行任何操作 可弹框提示客户暂停巡检 然后重新获取机器人状态再进行操作
const handleInspection = async () => {
if (robotBaseInfo.value.type.includes('巡航')) {
return new Promise((resolve) => {
dialog.warning({
title: '提示',
content: '当前机器人正在巡检中,可停止巡检后再进行操作',
positiveText: '停止巡检',
negativeText: '取消',
onPositiveClick: async () => {
try {
const { data: res } = await handleRobotActionApi({
puId: robotData.robotInfo?.puId,
type: '14',
})
if (res.code == 200) {
message.success('已停止巡检')
getRobotBaseInfo()
resolve(false) // 已暂停巡检返回false表示不处于巡检状态
} else {
message.error('停止巡检失败' + res.message)
resolve(true) // 暂停失败,仍然处于巡检状态
}
} catch (error) {
message.error('停止巡检出错')
resolve(true) // 出错时,假设仍然处于巡检状态
}
},
onNegativeClick: () => {
resolve(true) // 用户取消,仍然处于巡检状态
},
})
})
}
return false // 不包含"巡检"返回false表示不处于巡检状态
}
// 上传音频
const handleChangeUpload = async (e) => {
const file = e.file
const formData = new FormData()
formData.append('file', file.file)
formData.append('puid', deviceInfo.value?.puId)
const { data: res } = await uploadAudioApi(formData)
if (res.code == 200) {
message.success('上传音频文件成功')
getAudioInfo()
} else {
message.error('上传音频文件失败')
}
}
// 获取音频信息
const getAudioInfo = async () => {
const { data: res } = await getAudioInfoApi({
puid: deviceInfo.value?.puId,
})
if (res.code == 200) {
audioInfo.value = res?.data
}
}
const handleChangeAudio = async (e) => {
// console.log(e, '选择音频操作')
if (e === '播放') {
// showPlayTypeDialog()
selectPlayType.value = true
} else if (e === '删除') {
const { data: res } = await deleteAudioApi({
puid: robotData.robotInfo?.puId,
id: audioInfo.value?.id,
})
if (res.code == 200) {
message.success('删除音频成功')
getAudioInfo()
} else {
message.error('删除音频失败')
}
} else {
const { data: res } = await handleRobotActionApi({
puId: robotData.robotInfo?.puId,
type: '17',
enable: '0',
videoId: audioInfo.value?.id,
})
if (res.code == 200) {
message.success('已暂停')
} else {
message.error('暂停音频失败')
}
}
}
// 计算当前高度百分比(0-100)
const currentHeight = computed(() => {
if (!container.value) return 0
const containerHeight = container.value.offsetHeight
const draggableHeight = draggable.value?.offsetHeight || 0
const maxTop = containerHeight - draggableHeight - 20 // 20是底部初始偏移
// 计算百分比 (0-100)
return Math.round(100 - (currentTop.value / maxTop) * 100)
})
const startDrag = (e) => {
isDragging.value = true
startY.value = e.clientY
startTop.value = currentTop.value
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', stopDrag)
}
const onDrag = (e) => {
if (!isDragging.value) return
const deltaY = e.clientY - startY.value
let newTop = startTop.value + deltaY
const containerHeight = container.value.offsetHeight
const draggableHeight = draggable.value.offsetHeight
const maxTop = containerHeight - draggableHeight - 20
newTop = Math.max(0, Math.min(newTop, maxTop))
currentTop.value = newTop
}
const stopDrag = () => {
isDragging.value = false
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
// 拖动结束时可以在这里使用currentHeight.value
console.log('当前高度:', currentHeight.value)
}
onMounted(() => {
// const containerHeight = container.value.offsetHeight
// const draggableHeight = draggable.value.offsetHeight
// const maxTop = containerHeight - draggableHeight - 20
// currentTop.value = maxTop * (1 - upDownHeight.value / 100)
})
const handleChange1 = (e) => {
checkedValue.value = e.target.value
handleChangeLight(checkedValue.value)
}
const handleChange2 = (e) => {
checkedValue.value = e.target.value
handleChangeLight(checkedValue.value)
}
const handleChange3 = (e) => {
checkedValue.value = e.target.value
handleChangeLight(checkedValue.value)
}
// 警灯闪烁
const handleChangeLight = async (value) => {
const lightMap = {
关闭: 'OFF',
开启: 'ON',
自动: 'AUTO',
}
const { data: res } = await handleRobotActionApi({
puId: robotData.robotInfo?.puId,
type: '19',
value: lightMap[value],
})
if (res.code == 200) {
message.success('操作成功')
} else {
message.error('操作失败')
checkedValue.value = '关闭'
}
}
const handleChange4 = (e) => {
checkedValue.value = e.target.value
}
const formatTooltip = (value) => {
return `${value}%`
}
// 停止缩放
const handleStopDeviceCameraZoom = async () => {
isZoom.value = false
const res = await stopDeviceCameraZoomApi({
idx: 0,
token: deviceToken.value,
puid: deviceInfo.value?.puId,
})
isStopLeft.value = true
console.log(res, '停止缩放')
}
// 操控机器人上方的摄像头
const handleChangeCamera = debounce(async (motion) => {
const isInspection = await handleInspection()
if (isInspection) {
return
}
if (motion === 'stop' && isZoom.value) {
isStopLeft.value = true
handleStopDeviceCameraZoom()
return
}
if (motion === 'stop') {
isStopLeft.value = true
} else {
isStopLeft.value = false
}
if (!deviceInfo.value?.puId) {
message.error('当前机器人未连接', {
duration: 1000,
})
return
}
const res = await changeDeviceCameraApi({
token: deviceToken.value,
puid: deviceInfo.value?.puId,
idx: 0,
motion,
})
}, 0)
// 球机缩放
const handleChangeZoomCamera = async (motion) => {
const isInspection = await handleInspection()
if (isInspection) {
return
}
isStopLeft.value = false
isZoom.value = true
if (motion === 'ZoomIn') {
const res = await changeDeviceCameraZoomInApi({
token: deviceToken.value,
puid: deviceInfo.value?.puId,
idx: 0,
})
} else {
const res = await changeDeviceCameraZoomOutApi({
token: deviceToken.value,
puid: deviceInfo.value?.puId,
idx: 0,
})
}
}
// 操控机器人
const handleChangeRobot = debounce(async (type) => {
const isInspection = await handleInspection()
if (isInspection) {
return
}
// type 5 前进 6 后退 7 左转 8 右转
const typeMap = {
5: '前进', // 前进
6: '后退', // 后退
7: '左转', // 左转
8: '右转', // 右转
9: '停止', // 停止
}
if (!robotData.robotInfo?.puId) {
message.error('当前机器人未连接', {
duration: 1000,
})
return
}
const params = {
puId: robotData.robotInfo?.puId,
type,
}
console.log(params, '机器人操控参数' + typeMap[type])
const { data: res } = await handleRobotActionApi(params)
console.log(res, '机器人操控结果')
if (res.code == 200 && type != 9) {
isStopRight.value = true
}
if (res.code == 200 && type == 9) {
isStopRight.value = false
}
}, 0)
// 停止机器人
const handleStopRobot = async () => {
console.log('停止机器人')
}
// 获取机器人的基础信息
const getRobotBaseInfo = async () => {
const { data: res } = await handleRobotActionApi({
puId: deviceInfo.value?.puId,
type: '1',
})
robotBaseInfo.value = res.data
}
// 确认播放
const handlePlayAudio = async () => {
const loop = playParams.value.playType === 0 ? 0 : playParams.value.playCount
const { data: res } = await handleRobotActionApi({
puId: robotData.robotInfo?.puId,
type: '17',
loop,
enable: '1',
videoId: audioInfo.value?.id,
})
if (res.code == 200) {
message.success('播放音频成功')
selectPlayType.value = false
} else {
message.error('播放音频失败')
}
}
// 音量选择
const handleChangeVolume = debounce(async (value) => {
const { data: res } = await handleRobotActionApi({
puId: robotData.robotInfo?.puId,
type: '18',
sound: value,
videoId: audioInfo.value?.id,
})
if (res.code == 200) {
message.success('音量调整成功')
} else {
message.error('音量调整失败')
}
}, 0)
const isOpenMK = ref(false)
const onHandleOpenOrCloseMK = async (type) => {
if (type === 1) {
// 先判断当前设备有没有开启MK
try {
// 请求麦克风权限并检查状态
// const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
// // 检查麦克风是否真的在接收声音(可选)
// const audioTracks = stream.getAudioTracks()
// if (audioTracks.length > 0) {
// audioTracks.forEach((track) => track.stop())
// // 调用麦克风开启接口
// const { data: res } = await openDeviceMKApi({
// token: deviceToken.value,
// puId: robotData.robotInfo?.puId,
// idx: 0,
// })
// if (res.code == 200) {
// message.success('麦克风开启成功')
// isOpenMK.value = true
// } else {
// message.error('麦克风开启失败')
// }
// } else {
// message.error('未检测到音频输入设备')
// }
// 调用麦克风开启接口
const { data: res } = await openDeviceMKApi({
token: deviceToken.value,
puid: robotData.robotInfo?.puId,
idx: 0,
})
if (res.code == 200) {
message.success('麦克风开启成功')
isOpenMK.value = true
} else {
message.error('麦克风开启失败')
}
} catch (error) {
message.error('麦克风访问被拒绝或不可用,请检查麦克风权限')
}
} else {
// 调用麦克风开启接口
const { data: res } = await stopDeviceMKApi({
token: deviceToken.value,
puid: deviceInfo.value?.puId,
idx: 0,
})
if (res.code == 200) {
message.success('麦克风已关闭')
isOpenMK.value = false
} else {
message.error('麦克风关闭失败')
}
}
}
onMounted(async () => {
const token = await getRobotTokenFn() // 获取设备token
const device = await getRobotDeviceListFn() // 获取设备信息
deviceToken.value = token
deviceInfo.value = device
getRobotBaseInfo()
getAudioInfo()
})
</script>
<style lang="scss" scoped>
.control-deck {
justify-content: space-between;
}
.row-1-item,
.row-2-item {
width: 90px;
margin: 0 auto;
}
.row-1-item {
height: 24px;
background: url('@/assets/home-imgs/control-1-bg.png') no-repeat center center;
background-size: 100% 100%;
line-height: 24px;
text-align: center;
}
.row-2-item {
// margin-top: 6px;
display: flex;
align-items: center;
justify-content: space-around;
& div:last-child {
// width: 32px;
height: 16px;
background-color: #005899;
border-radius: 6px;
font-size: 12px;
text-align: center;
line-height: 16px;
padding: 2px 10px;
}
}
.row-3-item-1,
.row-3-item-3 {
width: 68px;
height: 68px;
margin: 20px auto 0;
position: relative;
background: url('@/assets/home-imgs/control-2-round.png') no-repeat center center;
background-size: 100% 100%;
> img {
width: 16px;
height: 18px;
cursor: pointer;
object-fit: contain;
}
// // 增加一个移入时的放大平滑动画效果
// .hand-direction:hover {
// transform: scale(1.1);
// transition: transform 0.3s ease-in-out;
// }
.arrow-top {
position: absolute;
top: 1px;
left: 50%;
transform: translateX(-50%) rotate(-90deg);
}
.arrow-right {
position: absolute;
top: 50%;
right: 1px;
transform: translateY(-50%);
}
.arrow-bottom {
position: absolute;
bottom: 1px;
left: 50%;
transform: translateX(-50%) rotate(90deg);
}
.arrow-left {
position: absolute;
top: 50%;
left: 1px;
transform: translateY(-50%) rotate(-180deg);
}
.row-3-item-1-center {
width: 16px;
height: 16px;
position: absolute;
top: 50%;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
transform: translate(-50%, -50%) rotate(45deg);
border: 2px solid #7fc0ff;
border-radius: 4px;
background-color: #2f78bf;
cursor: pointer;
> img {
width: 9px;
height: 9px;
object-fit: contain;
transform: rotate(-45deg);
}
}
// 描述文字
.description-text {
position: absolute;
font-size: 12px;
}
.text-top {
left: 50%;
top: -28px;
transform: translateX(-50%);
}
.text-bottom {
left: 50%;
bottom: -28px;
transform: translateX(-50%);
}
.text-top,
.text-bottom {
width: 36px;
height: 22px;
border: 1px solid #7fc0ff;
border-radius: 4px;
text-align: center;
line-height: 22px;
}
.text-left {
left: -28px;
top: 50%;
transform: translateY(-50%);
}
.text-right {
right: -28px;
top: 50%;
transform: translateY(-50%);
}
.text-left,
.text-right {
height: 36px;
width: 22px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #7fc0ff;
border-radius: 4px;
// 使文字竖向展示 并垂直居中
writing-mode: vertical-rl;
text-orientation: upright;
}
.add-reduce-btn {
position: absolute;
right: -58px;
top: -16px;
// height: 100px;
width: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
span {
font-size: 13px;
width: 30px;
text-align: center;
color: #7fc0ff;
}
img {
width: 20px;
height: 50px;
object-fit: contain;
cursor: pointer;
}
}
}
.row-3-item-3 {
width: 68px;
height: 68px;
margin: 20px auto 0 !important;
> img {
width: 16px;
height: 18px;
cursor: pointer;
object-fit: contain;
}
}
.row-3-item-2 {
width: 60px;
height: 120px;
margin: 32px auto 0 !important;
background: url('@/assets/home-imgs/control-2-up-down.png') no-repeat center center;
background-size: 100% 100%;
position: relative;
user-select: none;
.up-down-box {
position: absolute;
left: 50%;
width: 30px;
height: 10px;
background: url('@/assets/home-imgs/control-2-up-down-hand.png') no-repeat center center;
background-size: 100% 100%;
transform: translateX(-50%);
cursor: pointer;
touch-action: none;
}
}
.row-4 {
margin-top: 10px;
.row-4-item {
text-align: center;
}
}
.row-5-item-1 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
> img {
width: 16px;
height: 16px;
margin-right: 10px;
object-fit: contain;
}
}
.row-6 {
// margin-top: 20px;
display: flex;
align-items: center;
}
.row-6-item {
padding: 2px 8px;
display: inline-block;
background-color: #005db6;
border: 1px solid #7fc0ff;
border-radius: 4px;
cursor: pointer;
}
.row-6-item-1 {
text-align: center;
img {
width: 28px;
height: 40px;
object-fit: contain;
cursor: pointer;
}
}
:deep(.n-radio__label) {
color: #fff !important;
}
:deep(.n-radio__dot) {
background: transparent;
border: 1px solid #7fc0ff;
box-shadow: none;
}
:deep(.n-radio__dot::before) {
background: #7fc0ff;
}
// :deep(.n-base-selection .n-base-selection-label) {
// background-color: #054a92;
// color: #fff;
// }
:deep(.n-slider .n-slider-rail .n-slider-rail__fill) {
background-color: #7fc0ff;
}
:deep(.n-slider .n-slider-handles .n-slider-handle-wrapper .n-slider-handle) {
background-color: #7fc0ff;
}
:deep(.n-slider .n-slider-rail) {
background-color: #2d6098;
}
.modal-container {
height: 40vh;
width: 30%;
display: flex;
flex-direction: column;
background: url('@/assets/home-imgs/modal-bg.png') no-repeat center center;
background-size: 100% 100%;
}
</style>