Commit 81b7a05a authored by 柳 佳乐's avatar 柳 佳乐
Browse files

头版

parents
Pipeline #81 failed with stages
in 0 seconds
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# paceperfect-cameraconsole
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "paceperfect-cameraconsole",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"axios": "^1.11.0",
"core-js": "^3.8.3",
"element-ui": "^2.15.14",
"vue": "^2.6.14",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<template>
<div>
<!-- 患者信息 -->
<div>
<el-button
class="toggle-btn"
@click="togglePanel"
circle
:icon="isVisible ? 'el-icon-close' : 'el-icon-menu'"
size="mini"
/>
<transition name="slide">
<div v-show="isVisible" class="scale-panel">
<RaiseTheScale></RaiseTheScale>
</div>
</transition>
</div>
<!-- 静态相机 -->
<div>
<StillCamera></StillCamera>
</div>
<!-- 动态相机和双目相机 -->
<div>
<DynamiCamera></DynamiCamera>
</div>
</div>
</template>
<script>
import RaiseTheScale from '@/components/raiseTheScale.vue'
import StillCamera from "@/components/stillCamera.vue"
import DynamiCamera from "@/components/dynamiCamera.vue"
export default {
name: 'App',
components: {
RaiseTheScale,
StillCamera,
DynamiCamera
},
data() {
return {
isVisible: true
}
},
methods: {
togglePanel() {
this.isVisible = !this.isVisible
}
}
}
</script>
<style>
.scale-panel {
position: fixed;
right: 20px;
top: 20px;
z-index: 99;
}
.toggle-btn {
position: fixed;
right: 20px;
top: 70px;
z-index: 100;
background: #409EFF;
color: white;
}
.slide-enter-active, .slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter, .slide-leave-to {
transform: translateX(100%);
opacity: 0;
}
</style>
import request from '@/utils/request'
import { baseURLConsole } from '@/utils/url'
//获取双目相机列表
export function getStereoCameraList() {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/rgbd/rgbd',
method: 'GET',
})
}
//初始化双目相机
export function initStereoCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/rgbd/rgbd/initialize',
method: 'POST',
data
})
}
//关闭指定的双目相机
export function closeStereoCamera(camera_id) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/${camera_id}/close`,
method: 'POST',
})
}
//获取双目相机当前帧
export function getStereoCameraFrame(camera_id) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/${camera_id}/current-frame`,
method: 'GET',
})
}
//获取指定双目相机的录制状态
export function getStereoCameraRecordStatus(camera_id) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/${camera_id}/recording-status`,
method: 'GET',
})
}
//开始双目相机录制
export function startStereoCameraRecording(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/start-recording`,
method: 'POST',
data
})
}
//停止双目相机录制
export function stopStereoCameraRecording(camera_id) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/${camera_id}/stop-recording`,
method: 'POST',
})
}
//设置相机曝光和增益参数
export function setStereoCameraExposureGain(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/set_rgbd_exposure_gain`,
method: 'PUT',
data
})
}
//设置相机亮度
export function setStereoCameraBrightness(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/brightness`,
method: 'PUT',
data
})
}
//设置相机对比度
export function setStereoCameraContrast(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/contrast`,
method: 'PUT',
data
})
}
//设置相机色调
export function setStereoCameraHue(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/hue`,
method: 'PUT',
data
})
}
//设置相机饱和度
export function setStereoCameraSaturation(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/saturation`,
method: 'PUT',
data
})
}
//设置相机锐度
export function setStereoCameraSharpness(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/sharpness`,
method: 'PUT',
data
})
}
//设置相机伽马
export function setStereoCameraGamma(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/gamma`,
method: 'PUT',
data
})
}
//设置相机白平衡
export function setStereoCameraWhiteBalance(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/whitebalance`,
method: 'PUT',
data
})
}
//设置相机分辨率
export function setStereoCameraResolution(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/resolution`,
method: 'PUT',
data
})
}
//设置相机帧率
export function setStereoCameraFps(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/frame-rate`,
method: 'PUT',
data
})
}
//设置相机深度开关
export function setStereoCameraDepthEnabled(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/depth-enabled`,
method: 'PUT',
data
})
}
//设置相机深度模式
export function setStereoCameraDepthMode(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/rgbd/rgbd/depth-mode`,
method: 'PUT',
data
})
}
import request from '@/utils/request'
import { baseURLConsole } from '@/utils/url'
//获取测量数据
export function quickMMeasure(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/bluetooth_scale/quick-measure',
method: 'POST',
data
})
}
import request from '@/utils/request'
import { baseURL } from '@/utils/url'
//获取预约列表
export function getBookList(data) {
return request({
baseURL:baseURL,
url: '/booking/get_book_list',
method: 'POST',
data,
})
}
//获取患者信息
export function getPatients(data) {
return request({
baseURL:baseURL,
url: '/patient/get_patients',
method: 'POST',
data,
})
}
//获取医生列表
export function getDoctorList(data) {
return request({
baseURL:baseURL,
url: '/doctor/get_doctor_list',
method: 'POST',
data,
})
}
import request from '@/utils/request'
import { baseURLConsole } from '@/utils/url'
//获取动态拍摄高清相机列表
export function getDynamiCameraList() {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_video/get_list',
method: 'GET',
})
}
//初始化指定的动态高清相机
export function initDynamiCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_video/initialize',
method: 'POST',
data,
})
}
//配置高清-视频采集相机参数
export function configDynamiCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_video/${data.camera_id}/set_orientation`,
method: 'POST',
data,
})
}
//关闭指定的静态采集高清相机
export function closeDynamiCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_video/close',
method: 'POST',
data
})
}
//断开视频相机WebSocket连接
export function disconnectDynamiCamera(camera_id) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_video/ws/disconnect/${camera_id}`,
method: 'POST',
})
}
//设置视频相机低帧率模式
export function lowDynamiCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_video/ws/fps/batch/set_ws_fps`,
method: 'POST',
data
})
}
//动态视频拍摄相机同步视频采集
export function startDynamiCameraStart(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_video/multi_hd_video_recode/start`,
method: 'POST',
data
})
}
//动态视频拍摄相机同步视频采集停止
export function stopDynamiCameraStart(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_video/multi_hd_video_recode/stop`,
method: 'POST',
data
})
}
//获取动态相机的链接状态
export function getWebSocketLink(){
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_video/ws/status`,
method: 'GET',
})
}
//双目相机和动态相机一键录制
export function startMultiCameraRecode(data){
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/multi_control/multi_camera_recode/start`,
method: 'POST',
})
}
//双目相机和动态相机一键停止
export function stopMultiCameraRecode(data){
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/multi_control/multi_camera_recode/stop`,
method: 'POST',
})
}
import request from '@/utils/request'
import { baseURLConsole } from '@/utils/url'
//获取静态拍摄高清相机列表
export function getStillCameraList() {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_static/get_list',
method: 'GET',
})
}
//初始化指定的静态高清相机
export function initializeStillCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_static/initialize',
method: 'POST',
data
})
}
//配置静态采集高清相机参数
export function setStillCameraOrientation(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_static/set_orientation',
method: 'POST',
data
})
}
//批量设置低帧率模式
export function lowStillCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_static/ws/fps/batch/set_ws_rate`,
method: 'POST',
data
})
}
//关闭指定的静态采集高清相机
export function closeStillCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_static/close`,
method: 'POST',
data
})
}
//捕获高清相机的单帧图像并保存
export function captureStillCamera(data) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_static/capture`,
method: 'POST',
data
})
}
//websocket断开链接清理后端状态
export function disconnectStillCamera(camera_id) {
return request({
baseURL:`http://${baseURLConsole}`,
url: `/api/cameras/hd_static/ws/disconnect/${camera_id}`,
method: 'POST',
})
}
//查询webSocket链接
export function getWebSocketLink() {
return request({
baseURL:`http://${baseURLConsole}`,
url: '/api/cameras/hd_static/ws/status',
method: 'GET',
})
}
import {
getStereoCameraList, // 获取双目相机列表
initStereoCamera, // 初始化双目相机
closeStereoCamera, // 关闭双目相机
getStereoCameraFrame, // 获取双目相机帧
getStereoCameraRecordStatus, // 获取双目相机录制状态
setStereoCameraExposureGain, // 设置相机曝光和增益参数
setStereoCameraBrightness, // 设置相机亮度
setStereoCameraContrast, // 设置相机对比度
setStereoCameraHue, // 设置相机色调
setStereoCameraSaturation, // 设置相机饱和度
setStereoCameraSharpness, // 设置相机锐度
setStereoCameraGamma, // 设置相机伽马值
setStereoCameraWhiteBalance, // 设置相机白平衡
setStereoCameraResolution, // 设置相机分辨率
setStereoCameraFps, // 设置相机帧率
setStereoCameraDepthEnabled, // 设置相机深度传感器是否启用
setStereoCameraDepthMode // 设置相机深度传感器模式
} from '@/api/binocularCamera'
export const binocularCameraMixins = {
data() {
return {
stereoCameras: [],
stereoCamerasImage: {
depthImage: null,//深度图像
leftImage: null,//左目图像
rightImage: null,//右目图像
},
// 双目相机设置
stereoSettings: {
autoExposure: true,//自动曝光
exposure: 0,//曝光值
gain: 0,//增益
brightness: 4,//亮度
contrast: 4,//对比度
hue: 0,//色调
saturation: 4,//饱和度
sharpness: 4,//锐度
gamma: 5,//伽马值
whiteBalanceAuto: true,//自动白平衡
whiteBalance: 40,//白平衡
resolution: 'HD720',//分辨率
fps: 60,//帧率
depthEnabled: true,//深度传感器是否启用
depthMode: 'NEURAL_PLUS'//深度传感器模式
},
resolutionOptions: [
{ label: 'HD720 2560×720', value: 'HD720', fpsOptions: [15, 30, 60] },
{ label: 'HD1080 3840×1080', value: 'HD1080', fpsOptions: [15, 30] },
{ label: 'HD2K 4416×1242', value: 'HD2K', fpsOptions: [15] },
{ label: 'VGA 1344×376', value: 'VGA', fpsOptions: [15, 30, 60, 100] }
],
currentFpsOptions: [15, 30, 60],
depthModes: [
'PERFORMANCE',
'QUALITY',
'ULTRA',
'NEURAL_LIGHT',
'NEURAL',
'NEURAL_PLUS'
],
autoRefresh: false,
refreshInterval: 15,
interval: null,
}
},
methods: {
// 获取双目相机列表
getStereoCameraList() {
this.activeTab = 'stereo'
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
if (sessionStorage.getItem('stereoCameras')) {
this.stereoCameras = JSON.parse(sessionStorage.getItem('stereoCameras'))
this.stereoSettings.fps = this.stereoCameras[0].camera_fps
this.stereoSettings.resolution = this.stereoCameras[0].camera_resolution
this.stereoSettings.depthMode = this.stereoCameras[0].depth_mode
this.$message.success(`获取到 ${this.stereoCameras.length} 个双目相机`);
loading.close();
return
}
getStereoCameraList().then(res => {
console.log(res)
if (res.data.length > 0) {
this.stereoSettings.fps = res.data[0].camera_fps
this.stereoSettings.resolution = res.data[0].camera_resolution
this.stereoSettings.depthMode = res.data[0].depth_mode
this.stereoCameras = res.data
this.$message.success(`获取到 ${this.stereoCameras.length} 个双目相机`);
} else {
this.$message.error('未检测到双目相机')
}
}).finally(() => {
loading.close();
})
},
//初始化相机
initStereoCameras() {
const loading = this.$loading({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
let param = {
camera_ids: this.stereoCameras[0].camera_id + '',
camear_config: {
init_params_config: {
camera_resolution: this.stereoSettings.resolution,
camera_fps: this.stereoSettings.fps,
depth_mode: this.stereoSettings.depthMode,
coordinate_units: this.stereoCameras[0].coordinate_units,
sdk_verbose: this.stereoCameras[0].sdk_verbose,
sdk_verbose_log_file: this.stereoCameras[0].sdk_verbose_log_file,
depth_minimum_distance: this.stereoCameras[0].depth_minimum_distance,
depth_maximum_distance: this.stereoCameras[0].depth_maximum_distance,
depth_stabilization: this.stereoCameras[0].depth_stabilization
},
video_params_config: {
aec_agc: this.stereoSettings.autoExposure,
gain: this.stereoSettings.gain,
exposure: this.stereoSettings.exposure,
whitebalance_auto: this.stereoSettings.whiteBalanceAuto,
whitebalance: this.stereoSettings.whiteBalance,
gamma: this.stereoSettings.gamma,
sharpness: this.stereoSettings.sharpness,
brightness: this.stereoSettings.brightness,
contrast: this.stereoSettings.contrast,
saturation: this.stereoSettings.saturation,
hue: this.stereoSettings.hue,
}
}
}
initStereoCamera(param).then(res => {
console.log(res)
sessionStorage.setItem('stereoCameras', JSON.stringify(this.stereoCameras));
getStereoCameraRecordStatus(this.stereoCameras[0].camera_id).then(res => {
console.log(res)
})
}).finally(() => {
loading.close();
})
},
//关闭双目相机
closeStereoCameras() {
closeStereoCamera(this.stereoCameras[0].camera_id).then(res => {
sessionStorage.removeItem('stereoCameras')
this.stereoCameras = []
console.log(res)
})
},
//获取帧
getStereoFrame() {
getStereoCameraFrame(this.stereoCameras[0].camera_id).then(res => {
console.log(res)
this.stereoCamerasImage.leftImage = res.data.frame_data.left_image
this.stereoCamerasImage.rightImage = res.data.frame_data.right_image
this.stereoCamerasImage.depthImage = res.data.frame_data.depth_image
this.$nextTick(() => {
this.canvasLeftImage();
this.canvasRightImage();
this.canvasDepthImage();
});
})
},
canvasLeftImage() {
if (!this.stereoCamerasImage.leftImage) return;
const canvas = this.$refs['canvas-leftImage'];
if (!canvas) return;
const img = new Image();
img.onload = () => {
// 计算保持比例的尺寸
const containerWidth = canvas.parentElement.clientWidth;
const ratio = img.width / img.height;
const height = containerWidth / ratio;
// 设置canvas尺寸
canvas.width = containerWidth;
canvas.height = height;
// 绘制图像
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
img.src = '';
};
img.src = this.stereoCamerasImage.leftImage;
},
canvasRightImage() {
if (!this.stereoCamerasImage.rightImage) return;
const canvas = this.$refs['canvas-rightImage'];
if (!canvas) return;
const img = new Image();
img.onload = () => {
// 计算保持比例的尺寸
const containerWidth = canvas.parentElement.clientWidth;
const ratio = img.width / img.height;
const height = containerWidth / ratio;
// 设置canvas尺寸
canvas.width = containerWidth;
canvas.height = height;
// 绘制图像
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
img.src = '';
};
img.src = this.stereoCamerasImage.rightImage;
},
canvasDepthImage() {
if (!this.stereoCamerasImage.depthImage) return;
const canvas = this.$refs['canvas-depthImage'];
if (!canvas) return;
const img = new Image();
img.onload = () => {
// 计算保持比例的尺寸
const containerWidth = canvas.parentElement.clientWidth;
const ratio = img.width / img.height;
const height = containerWidth / ratio;
// 设置canvas尺寸
canvas.width = containerWidth;
canvas.height = height;
// 绘制图像
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
img.src = '';
};
img.src = this.stereoCamerasImage.depthImage;
},
toggleAutoRefresh() {
console.log("123")
this.autoRefresh = !this.autoRefresh
if (this.autoRefresh) {
let intervalTime = 1000 / this.refreshInterval
this.interval = setInterval(() => {
this.getStereoFrame()
}, intervalTime);
} else {
clearInterval(this.interval)
this.interval = null;
}
},
//修改曝光和增益
handleAutoExposureChange() {
console.log("修改曝光和增益")
let param = {
camera_id: this.stereoCameras[0].camera_id,
is_auto_exposure_gain: this.stereoSettings.autoExposure,
exposure: this.stereoSettings.exposure,
gain: this.stereoSettings.gain
}
// console.log(param)
//修改曝光和增益
setStereoCameraExposureGain(param).then(res => {
console.log(res)
})
},
//修改亮度
handleBrightnessChange() {
console.log("修改亮度")
let param = {
camera_id: this.stereoCameras[0].camera_id,
brightness: this.stereoSettings.brightness
}
setStereoCameraBrightness(param).then(res => {
console.log(res)
})
},
//修改对比度
handleContrastChange() {
console.log("修改对比度")
let param = {
camera_id: this.stereoCameras[0].camera_id,
contrast: this.stereoSettings.contrast
}
setStereoCameraContrast(param).then(res => {
console.log(res)
})
},
//修改色调
handleHueChange() {
console.log("修改色调")
let param = {
camera_id: this.stereoCameras[0].camera_id,
hue: this.stereoSettings.hue
}
setStereoCameraHue(param).then(res => {
console.log(res)
})
},
//修改饱和度
handleSaturationChange() {
console.log("修改饱和度")
let param = {
camera_id: this.stereoCameras[0].camera_id,
saturation: this.stereoSettings.saturation
}
setStereoCameraSaturation(param).then(res => {
console.log(res)
})
},
//修改锐度
handleSharpnessChange() {
console.log("修改锐度")
let param = {
camera_id: this.stereoCameras[0].camera_id,
sharpness: this.stereoSettings.sharpness
}
setStereoCameraSharpness(param).then(res => {
console.log(res)
})
},
//修改伽马值
handleGammaChange() {
console.log("修改伽马值")
let param = {
camera_id: this.stereoCameras[0].camera_id,
gamma: this.stereoSettings.gamma
}
setStereoCameraGamma(param).then(res => {
console.log(res)
})
},
//修改白平衡
handleWhiteBalanceChange() {
console.log("修改白平衡")
let param = {
camera_id: this.stereoCameras[0].camera_id,
is_auto_whitebalance: this.stereoSettings.whiteBalanceAuto,
whitebalance_temperature: this.stereoSettings.whiteBalance
}
setStereoCameraWhiteBalance(param).then(res => {
console.log(res)
})
},
//修改分辨率
handleResolutionChange() {
if (this.interval) {
clearInterval(this.interval)
this.autoRefresh = !this.autoRefresh
}
this.list = this.resolutionOptions.filter(item => item.value == this.stereoSettings.resolution)
let param = {
camera_id: this.stereoCameras[0].camera_id,
resolution: this.stereoSettings.resolution
}
setStereoCameraResolution(param).then(res => {
console.log(res)
this.currentFpsOptions = this.list.fpsOptions
this.stereoSettings.fps = this.currentFpsOptions[this.currentFpsOptions.length - 1]
this.handleFpsChange()
})
},
//修改帧率
handleFpsChange() {
if (this.interval) {
clearInterval(this.interval)
this.autoRefresh = !this.autoRefresh
}
let param = {
camera_id: this.stereoCameras[0].camera_id,
frame_rate: this.stereoSettings.fps
}
setStereoCameraFps(param).then(res => {
console.log(res)
})
},
//修改深度传感器是否启用
handleDepthEnabledChange() {
if (this.interval) {
clearInterval(this.interval)
this.autoRefresh = !this.autoRefresh
}
let param = {
camera_id: this.stereoCameras[0].camera_id,
depth_enabled: this.stereoSettings.depthEnabled
}
setStereoCameraDepthEnabled(param).then(res => {
console.log(res)
})
},
//修改深度传感器模式
handleDepthModeChange() {
if (this.interval) {
clearInterval(this.interval)
this.autoRefresh = !this.autoRefresh
}
let param = {
camera_id: this.stereoCameras[0].camera_id,
depth_mode: this.stereoSettings.depthMode
}
setStereoCameraDepthMode(param).then(res => {
console.log(res)
})
},
},
beforeDestroy() {
//关闭双目相机
this.closeStereoCameras()
//清除自动获取帧
if (this.interval) {
clearInterval(this.interval)
}
//清除相机缓存
sessionStorage.removeItem('stereoCameras')
}
}
<template>
<div class="work-container">
<el-tabs v-model="activeTab" stretch>
<el-tab-pane label="预约" name="appointment">
<bookingList :work="true" @selectBookingList="selectBookingList"></bookingList>
</el-tab-pane>
<el-tab-pane label="非预约" name="nonAppointment">
<el-form :model="nonAppointmentForm" :rules="formRules" ref="nonAppointmentForm" label-width="80px">
<el-form-item label="姓名" prop="name">
<el-input v-model="nonAppointmentForm.name"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model="nonAppointmentForm.age" type="number" max="150" min="0"></el-input>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-select v-model="nonAppointmentForm.gender" placeholder="请选择">
<el-option label="男" :value="0"></el-option>
<el-option label="女" :value="1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="nonAppointmentForm.phone" placeholder="请输入手机号"></el-input>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="nonAppointmentForm.idCard" placeholder="请输入身份证号"></el-input>
</el-form-item>
<el-form-item label="籍贯">
<el-input v-model="nonAppointmentForm.nativePlace" placeholder="请输入籍贯"></el-input>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<div class="button-group">
<el-button type="primary" @click="handleSubmit">确认</el-button>
<el-button @click="handleCancel">取消</el-button>
</div>
</div>
</template>
<script>
import bookingList from "@/components/bookingList.vue"
import { timeProcessing } from "@/utils/myMethod"
export default {
name:'Booking',
components: {
bookingList
},
data() {
return {
activeTab: 'appointment',
selectedRows: null,
nonAppointmentForm: {},
formRules: {
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
idCard: [
{ pattern: /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/, message: '请输入正确的身份证号', trigger: 'blur' }
]
}
}
},
created(){
this.nonAppointmentForm = this.$store.getters.patientInfo
},
methods: {
selectBookingList(val) {
this.selectedRows = val;
},
handleSubmit() {
if (this.activeTab === 'appointment') {
if (this.selectedRows) {
let param = {
...this.selectedRows,
phone: null,
idCard: null,
nativePlace: null
}
this.$store.dispatch('updatePatientInfo', param)
this.$emit('handleCloseBooking')
} else {
this.$message.error('请选择预约记录')
return
}
} else {
this.$refs.nonAppointmentForm.validate(valid => {
if (!valid) {
this.$message.error('请填写完整表单')
return
}
this.nonAppointmentForm.bkBkTime = timeProcessing()
this.$store.dispatch('updatePatientInfo', this.nonAppointmentForm)
this.$emit('handleCloseBooking')
})
}
},
handleCancel(){
this.$emit('handleCloseBooking')
}
}
}
</script>
<style scoped>
.work-container {
padding: 20px;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.button-group {
display: flex;
justify-content: center;
margin-top: 20px;
gap: 20px;
}
</style>
<template>
<!-- 预约列表 -->
<div>
<!-- 搜索 -->
<el-form ref="form" :inline="true" :model="queryForm" @submit.native.prevent>
<el-form-item>
<!-- 患者 -->
<el-select v-model="queryForm.pkPtPtid" clearable filterable :loading="patientLoading" placeholder="患者搜索" remote
:remote-method="patientRemoteMethod" reserve-keyword>
<el-option v-for="item in patientList" :key="item.patientId" :label="item.patientName"
:value="item.patientId" />
</el-select>
</el-form-item>
<el-form-item>
<!-- 医生 -->
<el-select v-model="queryForm.pkDocDocid" clearable filterable :loading="doctorLoading" placeholder="医生搜索"
remote :remote-method="doctorRemoteMethod" reserve-keyword>
<el-option v-for="item in doctorList" :key="item.docId" :label="item.docName" :value="item.docId" />
</el-select>
</el-form-item>
<el-form-item>
<!-- 时间 -->
<el-date-picker v-model="timeValue" clearable end-placeholder="结束日期" range-separator="至"
start-placeholder="开始日期" type="daterange" @change="getData" />
</el-form-item>
<el-form-item>
<el-button icon="el-icon-search" native-type="submit" type="primary" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
<!-- 内容 -->
<el-table ref="tableSort" v-loading="listLoading" :data="bookingList" highlight-current-row
@current-change="selectBooking">
<el-table-column label="序号" show-overflow-tooltip width="95">
<template #default="scope">
{{ (queryForm.pageNum - 1) * 10 + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="医生" prop="doctorVO.docName" show-overflow-tooltip />
<el-table-column label="患者" prop="patientVO.patientName" show-overflow-tooltip />
<el-table-column label="性别" prop="patientVO.patientSex" show-overflow-tooltip />
<el-table-column label="年龄" prop="patientVO.patientAge" show-overflow-tooltip />
<el-table-column label="身高(cm)" prop="bkHeight" show-overflow-tooltip />
<el-table-column label="体重(kg)" prop="bkWeight" show-overflow-tooltip />
<el-table-column label="预约时间" prop="bkBkTime" show-overflow-tooltip />
<el-table-column label="创建时间" prop="bkCreateTime" show-overflow-tooltip />
</el-table>
<div class="pagination">
<el-pagination :background="true" :current-page="queryForm.pageNum" layout="total, sizes, prev, pager, next, jumper"
:page-size="queryForm.pageSize" :total="total" @current-change="handleCurrentChange"
@size-change="handleSizeChange" />
</div>
</div>
</template>
<script>
import { getBookList } from '@/api/booking.js'
import { getPatients } from '@/api/booking.js'
import { getDoctorList } from '@/api/booking.js'
import { timeProcessing, startTimeProcessing, endTimeProcessing } from '@/utils/myMethod.js'
export default {
name: 'Booking',
props: {
work: {
type: Boolean,
default: false
}
},
data() {
return {
listLoading: false,
bookingList: [],
queryForm: {
bkHeight: null, //身高
bkStatus: null, //状态
bkWeight: null, //体重
endTime: null, //结束时间
pkDocDocid: null, //医生ID
pkPtPtid: null, //患者ID
startTime: null, //开始时间
pageNum: 1,
pageSize: 10,
},
total: 0,
patientLoading: false,
patientList: [], //患者列表
doctorLoading: false,
doctorList: [], //医生列表
timeValue: null, //时间
//选择
selectedRows: [],
}
},
created() {
this.getList()
},
methods: {
//选择
selectBooking(val) {
if (this.work) {
//数据处理
let userInfo = {
bookingId: val.bookingId,//预约ID
gender: val.patientVO.patientSex,//性别 0男 1女
age: val.patientVO.patientAge,//年龄
height: val.bkHeight,//身高
weight: val.bkWeight,//体重
name: val.patientVO.patientName,//患者姓名
bkBkTime: val.bkBkTime,//预约时间
}
this.$emit('selectBookingList', userInfo)
}
},
//获取数据
getList() {
if (this.work) {
this.listLoading = true
this.bookingList = [
{
bookingId: 1,
doctorVO: {
docName: '张医生'
},
patientVO: {
patientName: '李患者',
patientSex: 0,
patientAge: 20,
},
bkHeight: 175,
bkWeight: 70,
bkBkTime: '2025-07-22 10:00',
bkCreateTime: '2025-07-22 10:00'
},
{
bookingId: 2,
doctorVO: {
docName: '王医生'
},
patientVO: {
patientName: '赵患者',
patientSex: 1,
patientAge: 21,
},
bkHeight: 165,
bkWeight: 55,
bkBkTime: '2025-07-22 11:00',
bkCreateTime: '2025-07-22 11:00'
}
]
this.listLoading = false
} else {
getBookList(this.queryForm)
.then((res) => {
if (res.success) {
this.bookingList = res.data.list
for (let item of this.bookingList) {
item.bkBkTime = timeProcessing(item.bkBkTime)
item.bkCreateTime = timeProcessing(item.bkCreateTime)
}
this.total = res.data.total
} else {
this.$baseMessage(res.msg, 'error')
}
})
.catch((error) => {
console.log(error)
})
.finally(() => {
this.listLoading = false
})
}
},
//查询患者
patientRemoteMethod(query) {
if (query !== '') {
this.patientLoading = true
let param = {
pageNum: 1,
pageSize: 10,
patientName: query,
}
getPatients(param)
.then((res) => {
if (res.success) {
this.patientList = res.data.list
}
this.patientLoading = false
})
.catch((error) => {
this.patientLoading = false
this.patientList = []
})
}
},
//查询医生
doctorRemoteMethod(query) {
if (query !== '') {
this.doctorLoading = true
let param = {
pageNum: 1,
pageSize: 10,
docName: query,
}
getDoctorList(param)
.then((res) => {
if (res.success) {
this.doctorList = res.data.list
}
this.doctorLoading = false
})
.catch((error) => {
this.doctorLoading = false
this.doctorList = []
})
}
},
//时间筛选
getData(value) {
if (value == null) {
this.queryForm.startTime = null
this.queryForm.endTime = null
} else {
this.queryForm.startTime = startTimeProcessing(value[0])
this.queryForm.endTime = endTimeProcessing(value[1])
}
},
//查询
handleQuery() {
this.queryForm.pageNum = 1
this.getList()
},
//分页
handleSizeChange(val) {
this.queryForm.pageSize = val
this.getList()
},
handleCurrentChange(val) {
this.queryForm.pageNum = val
this.getList()
},
},
}
</script>
<style scoped>
.pagination{
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>
<template>
<div class="camera-container">
<el-card>
<!-- 头部控制区 -->
<div class="header-container">
<div class="header-title">相机2、3模块</div>
<div class="header-buttons">
<el-button type="primary" @click="getVideoList">获取相机2列表</el-button>
<el-button type="primary" @click="getStereoCameraList">获取相机3列表</el-button>
</div>
</div>
<!-- 相机参数设置区 -->
<el-card class="camera-settings" v-if="dynamiCameras.length > 0 || stereoCameras.length > 0">
<el-tabs v-model="activeTab">
<!-- 动态相机参数 -->
<el-tab-pane label="相机2" name="dynami" v-if="dynamiCameras.length > 0">
<div class="settings-group">
<el-form inline size="mini" class="compact-form">
<el-form-item label="分辨率">
<el-select v-model="dynamiSettings.resolution" style="width: 140px">
<el-option label="2560×1440" value="2560×1440"></el-option>
<el-option label="1920×1080" value="1920×1080"></el-option>
<el-option label="1280×960" value="1280×960"></el-option>
</el-select>
</el-form-item>
<el-form-item label="帧率">
<el-select v-model="dynamiSettings.fps" style="width: 80px">
<el-option label="15" :value="15"></el-option>
<el-option label="30" :value="30"></el-option>
<el-option label="60" :value="60"></el-option>
</el-select>
</el-form-item>
<el-form-item label="保存路径">
<el-input v-model="dynamiSettings.savePath" placeholder="/images" style="width: 180px"></el-input>
</el-form-item>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="initDynamiCameras" icon="el-icon-refresh">初始化</el-button>
<el-button type="danger" @click="closeAllCameras" icon="el-icon-close">关闭</el-button>
</el-button-group>
</el-form-item>
<el-form-item>
<el-switch v-model="dynamiSettings.lowFpsMode" active-text="低帧率" inactive-text="原始帧"
@change="toggleLowFpsMode" style="margin-left: 10px;" />
</el-form-item>
</el-form>
</div>
</el-tab-pane>
<!-- 双目相机参数 -->
<el-tab-pane label="相机3" name="stereo" v-if="stereoCameras.length > 0">
<div class="settings-group">
<el-form label-width="120px" size="mini">
<el-row :gutter="20">
<!-- 曝光和增益 -->
<el-col :span="12">
<el-form-item label="自动曝光/增益">
<el-switch v-model="stereoSettings.autoExposure" active-text="自动" inactive-text="手动"
@change="handleAutoExposureChange"></el-switch>
</el-form-item>
</el-col>
<el-col :span="6" v-if="!stereoSettings.autoExposure">
<el-form-item label="曝光">
<el-input-number v-model="stereoSettings.exposure" :min="0" :max="100" :step="1" size="mini"
style="width: 100px" @change="handleAutoExposureChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="6" v-if="!stereoSettings.autoExposure">
<el-form-item label="增益">
<el-input-number v-model="stereoSettings.gain" :min="0" :max="100" :step="1" size="mini"
style="width: 100px" @change="handleAutoExposureChange"></el-input-number>
</el-form-item>
</el-col>
<!-- 基础参数 -->
<el-col :span="6">
<el-form-item label="亮度">
<el-input-number v-model="stereoSettings.brightness" :min="0" :max="8" :step="1" size="mini"
style="width: 100px" @change="handleBrightnessChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="对比度">
<el-input-number v-model="stereoSettings.contrast" :min="0" :max="8" :step="1" size="mini"
style="width: 100px" @change="handleContrastChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="色调">
<el-input-number v-model="stereoSettings.hue" :min="0" :max="11" :step="1" size="mini"
style="width: 100px" @change="handleHueChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="饱和度">
<el-input-number v-model="stereoSettings.saturation" :min="0" :max="8" :step="1" size="mini"
style="width: 100px" @change="handleSaturationChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="锐度">
<el-input-number v-model="stereoSettings.sharpness" :min="0" :max="8" :step="1" size="mini"
style="width: 100px" @change="handleSharpnessChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="伽马值">
<el-input-number v-model="stereoSettings.gamma" :min="1" :max="9" :step="1" size="mini"
style="width: 100px" @change="handleGammaChange"></el-input-number>
</el-form-item>
</el-col>
<!-- 白平衡 -->
<el-col :span="12">
<el-form-item label="白平衡">
<el-switch v-model="stereoSettings.whiteBalanceAuto" active-text="自动" inactive-text="手动"
@change="handleWhiteBalanceChange"></el-switch>
</el-form-item>
</el-col>
<el-col :span="12" v-if="!stereoSettings.whiteBalanceAuto">
<el-form-item label="白平衡值">
<el-slider v-model="stereoSettings.whiteBalance" :min="28" :max="65" :step="1"
style="width: 200px" show-input @change="handleWhiteBalanceChange"></el-slider>
</el-form-item>
</el-col>
<!-- 分辨率和帧率 -->
<el-col :span="12">
<el-form-item label="分辨率">
<el-select v-model="stereoSettings.resolution" style="width: 200px"
@change="handleResolutionChange">
<el-option v-for="opt in resolutionOptions" :key="opt.value" :label="opt.label"
:value="opt.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="帧率">
<el-select v-model="stereoSettings.fps" style="width: 200px" @change="handleFpsChange">
<el-option v-for="fps in currentFpsOptions" :key="fps" :label="`${fps} FPS`"
:value="fps"></el-option>
</el-select>
</el-form-item>
</el-col>
<!-- 深度计算 -->
<el-col :span="12">
<el-form-item label="深度计算">
<el-switch v-model="stereoSettings.depthEnabled" active-text="开" inactive-text="关"
@change="handleDepthEnabledChange"></el-switch>
</el-form-item>
</el-col>
<el-col :span="12" v-if="stereoSettings.depthEnabled">
<el-form-item label="深度模式">
<el-select v-model="stereoSettings.depthMode" style="width: 200px"
@change="handleDepthModeChange">
<el-option v-for="mode in depthModes" :key="mode" :label="mode" :value="mode"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="initStereoCameras">初始化</el-button>
<el-button type="danger" @click="closeStereoCameras">关闭</el-button>
</el-button-group>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 视频帧显示区 -->
<div class="frame-display-area" v-if="dynamiCameras.length > 0 || stereoCameras.length > 0">
<!-- 相机显示区 -->
<div class="camera-grid">
<!-- 动态相机显示区 -->
<div class="dynami-frames" v-if="dynamiCameras.length > 0">
<h3 class="frame-section-title">相机2</h3>
<el-row :gutter="10" class="frame-display-row">
<el-col :span="8" v-for="camera in dynamiCameras" :key="camera.id" class="frame-display-wrapper">
<el-card class="frame-display">
<div slot="header" class="clearfix" style="padding: 5px;">
<div class="frame-title">
<span class="camera-id">相机 {{ camera.id }}</span>
<span class="camera-spec">({{ camera.width }}×{{ camera.height }} @{{ camera.fps }}fps)</span>
</div>
</div>
<div class="frame-header" style="padding: 5px; text-align: center;">
<el-select v-model="camera.cameraOrientation" size="mini" style="width: 80px; margin-right: 10px;">
<el-option label="不指定" value="不指定"></el-option>
<el-option label="右中" value="右中"></el-option>
<el-option label="右下" value="右下"></el-option>
<el-option label="后中" value="后中"></el-option>
<el-option label="后下" value="后下"></el-option>
<el-option label="左中" value="左中"></el-option>
<el-option label="左下" value="左下"></el-option>
</el-select>
<el-button size="mini" type="primary" plain @click="configCamera(camera)">配置</el-button>
<el-button size="mini" type="primary" plain @click="startStream(camera)">链接</el-button>
<el-button size="mini" type="warning" plain @click="stopStream(camera)">断链</el-button>
</div>
<div v-if="!camera.imageData"
style="width: 100%; height: 180px; background: #000;">
</div>
<canvas v-else :ref="`canvas-${camera.id}`" style="width: 100%; background: #000;"></canvas>
<div style="padding: 5px; font-size: 12px; color: #666; min-height: 40px;">
<div v-if="camera.statusInfo">{{ camera.statusInfo }}</div>
<div v-if="camera.captureLog">{{ camera.captureLog }}</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 双目相机显示区 -->
<div class="stereo-frames" v-if="stereoCameras.length > 0">
<h3 class="frame-section-title">相机3</h3>
<el-card class="frame-display">
<div slot="header" class="clearfix" style="padding: 5px;">
<div class="frame-title">
<span class="camera-id">相机3</span>
</div>
</div>
<div class="stereo-layout">
<div class="stereo-video-area">
<div class="stereo-video-grid">
<div class="video-panel">
<div class="video-label">左目画面</div>
<div class="video-frame" v-if="!stereoCamerasImage.leftImage"></div>
<canvas v-else ref="canvas-leftImage" class="video-canvas"></canvas>
</div>
<div class="video-panel">
<div class="video-label">右目画面</div>
<div class="video-frame" v-if="!stereoCamerasImage.rightImage"></div>
<canvas v-else ref="canvas-rightImage" class="video-canvas"></canvas>
</div>
<div class="video-panel">
<div class="video-label">深度画面</div>
<div class="video-frame" v-if="!stereoCamerasImage.depthImage"></div>
<canvas v-else ref="canvas-depthImage" class="video-canvas"></canvas>
</div>
</div>
</div>
<div class="stereo-control-panel">
<div class="control-group">
<el-button size="mini" type="primary" @click="getStereoFrame">获取帧</el-button>
<el-button size="mini" type="primary" :type="autoRefresh ? 'success' : 'primary'"
@click="toggleAutoRefresh">
{{ autoRefresh ? '停止自动获取' : '自动获取' }}
</el-button>
<div class="interval-control">
<span class="control-text">刷新间隔:</span>
<el-input-number v-model="refreshInterval" size="mini" :min="1" :max="15" :step="1"
:disabled="autoRefresh"></el-input-number>
<span class="control-text">次/秒</span>
</div>
</div>
</div>
</div>
</el-card>
</div>
</div>
</div>
<!-- 总控制台区域 -->
<div class="global-controls" v-if="dynamiCameras.length > 0 && stereoCameras.length > 0">
<el-button-group>
<el-button type="danger" @click="startAllRecordings">开始录制</el-button>
<el-button type="info" @click="stopAllRecordings">停止录制</el-button>
</el-button-group>
<div class="recording-time" v-if="isRecording">
<i class="el-icon-video-camera"></i>
<span>{{ formatRecordingTime() }}</span>
</div>
</div>
<div v-if="dynamiCameras.length <= 0 && stereoCameras.length <= 0" class="empty-tip">
<el-empty description="暂无相机,请点击右上角按钮获取相机列表"></el-empty>
</div>
</el-card>
</div>
</template>
<script>
import {
baseURLConsole
} from '@/utils/url'
import {
getDynamiCameraList,
initDynamiCamera,
getWebSocketLink,
configDynamiCamera,
lowDynamiCamera,
closeDynamiCamera,
disconnectDynamiCamera,
startMultiCameraRecode,
stopMultiCameraRecode
} from '@/api/dynamiCamera.js'
import { binocularCameraMixins } from '@/components/binocularCamera'
export default {
name: 'DynamiCamera',
mixins: [binocularCameraMixins],
data() {
return {
activeTab: 'dynami',
dynamiCameras: [],
frameCanvas: null,
// 动态相机设置
dynamiSettings: {
resolution: '1920x1080',
fps: 60,
savePath: 'images/captures',
lowFpsMode: false
},
// 录制相关状态
isRecording: false,
recordingStartTime: null,
recordingTimer: null,
}
},
methods: {
// 渲染图像到canvas
renderImageToCanvas(camera) {
if (!camera.imageData) return;
const canvas = this.$refs[`canvas-${camera.id}`];
if (!canvas || !canvas[0]) return;
const img = new Image();
img.onload = () => {
// 计算保持比例的尺寸
const containerWidth = canvas[0].parentElement.clientWidth;
const ratio = img.width / img.height;
const height = containerWidth / ratio;
// 设置canvas尺寸
canvas[0].width = containerWidth;
canvas[0].height = height;
// 绘制图像
const ctx = canvas[0].getContext('2d');
ctx.clearRect(0, 0, canvas[0].width, canvas[0].height);
ctx.drawImage(img, 0, 0, canvas[0].width, canvas[0].height);
img.src = '';
};
img.src = camera.imageData;
},
// 获取动态相机列表
getVideoList() {
getDynamiCameraList().then(res => {
console.log(res)
if (res.data.length > 0) {
let list = []
res.data.forEach(item => {
list.push({
id: item.camera_id,
name: item.name,
width: 0,
height: 0,
fps: 0,
cameraOrientation: '不指定',//方位
websocket: null,
reconnectAttempts: 0,//重连次数限制
imageData: null,
objectURL: null,
statusInfo: null,
captureLog: '等待连接',
})
})
this.dynamiCameras = list
this.$message.success(`获取到 ${this.dynamiCameras.length} 个相机2`)
this.activeTab = 'dynami'
} else {
this.$message.error('当前无相机2')
}
//查询相机是否已webStock连接。有就关闭
getWebSocketLink().then(res => {
if (res.data.connected_cameras.length > 0) {
res.data.connected_cameras.forEach(item => {
disconnectDynamiCamera(item)
})
}
})
})
},
//初始化动态相机
initDynamiCameras() {
let cameraIds = [];
const [width, height] = this.dynamiSettings.resolution.split('x')
//
//设置相机
this.dynamiCameras.forEach(camera => {
cameraIds.push(camera.id)
})
console.log(this.dynamiCameras)
console.log(cameraIds)
//设置参数
let param = {
camera_ids: cameraIds.join(","),
init_params: {
resolution_width: parseInt(width),
resolution_height: parseInt(height),
fps: this.dynamiSettings.fps
}
}
initDynamiCamera(param).then(res => {
console.log(res)
this.dynamiCameras.forEach(camera => {
camera.width = parseInt(width)
camera.height = parseInt(height)
camera.fps = this.dynamiSettings.fps
})
this.$message.success(`初始化所有相机成功`)
})
},
// 关闭所有相机
closeAllCameras() {
let cameraIds = [];
this.dynamiCameras.forEach(camera => {
cameraIds.push(camera.id);
if (camera.websocket) {
camera.websocket.close();
camera.websocket = null;
}
});
let param = {
camera_ids: cameraIds.join(',')
};
closeDynamiCamera(param).then(res => {
this.dynamiCameras = []
this.dynamiSettings = {
resolution: '1920x1080',
fps: 60,
savePath: 'images/captures',
lowFpsMode: false
},
this.$message.success(`关闭所有相机成功`)
})
},
// 切换低帧率模式
toggleLowFpsMode(isLowFps) {
// 调用接口设置帧率模式
let cameraIds = [];
this.dynamiCameras.forEach(camera => {
cameraIds.push(camera.id)
});
let param = {
camera_ids: cameraIds.join(','),
fps: isLowFps ? 5 : 25
};
lowDynamiCamera(param).then(res => {
this.$message.success(isLowFps ? '已启用低帧率模式' : '已恢复原始帧率');
})
},
//配置相机
configCamera(camera) {
let param = {
camera_id: camera.id,
orientation: camera.cameraOrientation
};
configDynamiCamera(param).then(res => {
this.$message.success(`配置相机成功`)
})
},
//开启WebStock连接
startStream(camera) {
// 创建canvas用于渲染帧数据
if (camera.websocket) {
camera.websocket.close();
camera.websocket = null;
// 等待一小段时间确保连接完全关闭
setTimeout(() => {
this.establishWebSocketConnection(camera);
}, 100);
} else {
this.establishWebSocketConnection(camera);
}
},
establishWebSocketConnection(camera) {
const wsUrl = `ws://${baseURLConsole}/api/cameras/hd_video/ws/connect/${camera.id}`;
camera.websocket = new WebSocket(wsUrl);
camera.reconnectAttempts = 0;
camera.websocket.onopen = (event) => {
camera.captureLog = 'WebSocket已连接,等待相机响应...'
setInterval(() => {
if (camera.websocket.readyState === WebSocket.OPEN) {
camera.websocket.send('ping');
}
}, 30000);
};
camera.websocket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// 处理连接状态消息
if (data.status) {
camera.captureLog = `连接成功: ${data.message}`
return;
}
// 处理错误消息
if (data.error) {
// 如果是相机未初始化的错误,提示用户
if (data.error.includes('未初始化')) {
camera.captureLog = '请先在"相机控制"部分初始化相机,然后再尝试连接视频流'
}
// 如果是相机已在使用的错误,提示用户稍后重试
if (data.error.includes('已在流式传输中')) {
camera.captureLog = '相机正在使用中,请等待一会儿后再尝试连接'
}
camera.captureLog = `错误: ${data.error}`
return;
}
// 处理视频帧数据
if (data.data) {
// 显示图像
camera.imageData = `data:image/jpeg;base64,${data.data}`;
this.$nextTick(() => {
this.renderImageToCanvas(camera);
});
// 更新状态信息
let statusInfo = `分辨率: ${data.width}x${data.height}`;
if (data.performance) {
statusInfo += ` | 实际FPS: ${data.performance.actual_fps} | 目标FPS: ${data.performance.target_fps}`;
}
if (data.encode_time_ms) {
statusInfo += ` | 编码耗时: ${data.encode_time_ms}ms`;
}
if (data.data_size_kb) {
statusInfo += ` | 数据大小: ${data.data_size_kb}KB`;
}
camera.statusInfo = statusInfo
}
} catch (error) {
camera.captureLog = `解析消息数据时出错: ${error}`
}
}
camera.websocket.onerror = (error) => {
console.log(error)
camera.captureLog = `WebSocket连接错误`
};
camera.websocket.onclose = (event) => {
console.log(`WebSocket连接已关闭 (代码: ${event.code}, 原因: ${event.reason || '未知'})`);
// 不同的关闭代码有不同的含义
let closeReason = '';
switch (event.code) {
case 1000:
closeReason = '正常关闭';
break;
case 1001:
closeReason = '端点离开';
break;
case 1002:
closeReason = '协议错误';
break;
case 1003:
closeReason = '不支持的数据类型';
break;
case 1006:
closeReason = '异常关闭';
break;
case 1011:
closeReason = '服务器错误';
break;
default:
closeReason = `未知错误 (${event.code})`;
}
camera.captureLog = `WebSocket连接已关闭: ${closeReason}`
// 只有在非正常关闭的情况下才自动重连
if (event.code !== 1000 && camera.reconnectAttempts < 5) {
camera.reconnectAttempts++;
const retryDelay = Math.min(1000 * Math.pow(2, camera.reconnectAttempts - 1), 10000); // 指数退避,最大10秒
console.log(`连接异常关闭,${retryDelay / 1000}秒后尝试重连 (${camera.reconnectAttempts}/5)...`);
camera.captureLog = `连接异常关闭,${retryDelay / 1000}秒后尝试重连 (${camera.reconnectAttempts}/5)...`
setTimeout(() => this.startStream(camera), retryDelay);
} else if (event.code === 1000) {
console.log('WebSocket正常关闭,不进行重连');
} else {
console.log('重连次数已达上限,停止重连');
camera.captureLog = '重连失败,请检查相机状态后手动重连'
}
};
},
stopStream(camera) {
//调用接口断开链接
disconnectDynamiCamera(camera.id).then(res => {
this.$message.success(`断开链接成功`)
if (camera.websocket) {
camera.websocket.close()
this.$nextTick(() => {
camera.websocket = null
camera.reconnectAttempts = 0
camera.imageData = null
camera.objectURL = null
camera.statusInfo = null
camera.captureLog = '等待链接'
})
}
})
},
//开始录制
startAllRecordings() {
let hdCameras = [];
this.dynamiCameras.forEach(camera => {
hdCameras.push(camera.id);
});
let param = {
hd_cameras: hdCameras.join(','),
rgbd_cameras: this.stereoCameras[0].camera_id + '',
user_name: this.$store.getters.username,
save_dir: this.dynamiSettings.savePath
}
startMultiCameraRecode(param).then(res => {
this.isRecording = true;
this.recordingStartTime = new Date();
this.startRecordingTimer();
this.$message.success(`开始录制`);
})
},
// 启动录制计时器
startRecordingTimer() {
this.recordingTimer = setInterval(() => {
this.$forceUpdate(); // 强制更新视图以刷新时间显示
}, 1000);
},
// 停止录制计时器
stopRecordingTimer() {
if (this.recordingTimer) {
clearInterval(this.recordingTimer);
this.recordingTimer = null;
}
},
// 格式化录制时间
formatRecordingTime() {
if (!this.recordingStartTime) return '00:00:00';
const now = new Date();
const diff = Math.floor((now - this.recordingStartTime) / 1000); // 秒数
const hours = Math.floor(diff / 3600).toString().padStart(2, '0');
const minutes = Math.floor((diff % 3600) / 60).toString().padStart(2, '0');
const seconds = (diff % 60).toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
},
//停止录制
stopAllRecordings() {
let hdCameras = [];
this.dynamiCameras.forEach(camera => {
hdCameras.push(camera.id);
});
let param = {
hd_cameras: hdCameras.join(','),//高清相机列表
rgbd_cameras: this.stereoCameras[0].camera_id + '',//双目相机列表
user_name: this.$store.getters.username,
save_dir: this.dynamiSettings.savePath,
user_name: this.userInfo.name,
save_dir: this.dynamiSettings.savePath,
}
stopMultiCameraRecode(param).then(res => {
this.isRecording = false;
this.stopRecordingTimer();
this.recordingStartTime = null;
this.$message.success(`停止录制`);
})
},
}
}
</script>
<style scoped>
.camera-container {
padding: 20px;
background-color: #f5f7fa;
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-title {
font-size: 24px;
font-weight: bold;
color: #303133;
padding-left: 10px;
border-left: 4px solid #409eff;
}
.header-buttons {
display: flex;
gap: 10px;
}
.patient-info {
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 10px;
font-size: 14px;
}
.info-item label {
width: 80px;
color: #666;
font-weight: 500;
}
.camera-control {
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.camera-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.camera-item {
display: flex;
justify-content: space-between;
padding: 15px;
border: 1px solid #ebeef5;
border-radius: 6px;
background-color: #fff;
transition: all 0.3s;
margin-bottom: 10px;
}
.camera-item:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.selected-camera {
border: 2px solid #409eff;
background-color: #f0f7ff;
}
.frame-title {
display: flex;
align-items: center;
font-size: 14px;
padding: 2px 0;
}
.camera-id {
font-weight: bold;
margin-right: 8px;
}
.camera-spec {
font-size: 12px;
color: #909399;
}
.frame-header {
padding: 8px 15px;
display: flex;
justify-content: center;
background-color: #f5f7fa;
border-bottom: 1px solid #ebeef5;
}
.camera-info {
flex: 1;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.info-row label {
width: 70px;
font-size: 12px;
color: #666;
text-align: right;
padding-right: 8px;
flex-shrink: 0;
}
.camera-actions {
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
}
.frame-display-row {
margin-bottom: 20px;
}
.frame-display {
height: 100%;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.frame-display>>>.el-card__header {
padding: 8px 10px !important;
}
/* 紧凑表单样式 */
.compact-form>>>.el-form-item {
margin-bottom: 8px;
}
.compact-form>>>.el-form-item__label {
padding: 0;
line-height: 28px;
}
.compact-form>>>.el-form-item__content {
line-height: 28px;
}
.compact-form .el-row {
margin-bottom: 8px;
}
/* 双目相机显示区样式 */
.stereo-layout {
display: flex;
flex-direction: column;
width: 100%;
}
.stereo-video-area {
width: 100%;
margin-bottom: 10px;
}
.stereo-video-grid {
display: flex;
gap: 10px;
}
.video-panel {
flex: 1;
display: flex;
flex-direction: column;
background: #111;
border-radius: 4px;
overflow: hidden;
}
.video-label {
color: #fff;
padding: 8px;
font-size: 12px;
text-align: center;
background: #333;
}
.video-frame {
height: 180px;
background: #000;
display: flex;
align-items: center;
justify-content: center;
}
.video-canvas {
width: 100%;
height: 100%;
background: #000;
}
.stereo-control-panel {
padding: 10px;
background: #f5f7fa;
border-top: 1px solid #ebeef5;
margin-top: auto;
}
.control-group {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
}
.interval-control {
display: flex;
align-items: center;
gap: 5px;
}
.control-text {
font-size: 12px;
color: #666;
}
.control-buttons {
display: flex;
align-items: center;
gap: 10px;
}
.refresh-control {
display: flex;
align-items: center;
gap: 5px;
margin-left: 15px;
}
.control-label {
font-size: 12px;
color: #666;
}
.global-controls {
margin-top: 20px;
text-align: center;
padding: 15px;
background-color: #f5f7fa;
border-radius: 6px;
}
/* .capture-controls {
margin-top: 15px;
padding: 15px;
background-color: #f5f7fa;
border-radius: 6px;
} */
.button-group {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 15px;
}
</style>
<template>
<div class="raise-scale-container">
<el-card class="patient-info">
<div slot="header" class="clearfix">
<span>患者信息</span>
<el-button style="float: right; padding: 3px 0" type="text" @click="changePatient">
选择患者
</el-button>
</div>
<div class="compact-info">
<div class="info-line">
<span class="info-label">患者:</span>
<span>{{ patientInfo.name }} ({{ patientInfo.gender == 0 ? '' : '' }}, {{ patientInfo.age }}岁)</span>
</div>
<div class="info-line">
<span class="info-label">手机:</span>
<span>{{ patientInfo.phone || '-' }}</span>
</div>
<div class="info-line">
<span class="info-label">身份证:</span>
<span>{{ patientInfo.idCard || '-' }}</span>
</div>
<div class="info-line">
<span class="info-label">预约号:</span>
<span>{{ patientInfo.bookingId }}</span>
<span class="info-divider">|</span>
<span class="info-label">时间:</span>
<span>{{ patientInfo.bkBkTime }}</span>
<span class="info-divider">|</span>
<span class="info-label">籍贯:</span>
<span>{{ patientInfo.nativePlace || '-' }}</span>
</div>
<div class="info-line">
<span class="info-label">身高:</span>
<span>{{ patientInfo.height || '-' }}cm</span>
<span class="info-divider">|</span>
<span class="info-label">体重:</span>
<span>{{ patientInfo.weight || '-' }}kg</span>
</div>
</div>
<el-col :span="24" style="margin-top: 20px; text-align: center;">
<el-button type="primary" @click="getBodyData">
获取身高体重数据
</el-button>
</el-col>
</el-card>
<el-dialog
:visible.sync="showBookingDialog"
fullscreen
title="更换患者"
:show-close="true"
:close-on-click-modal="false"
:modal="false"
>
<booking @handleCloseBooking="handleCloseBooking" />
</el-dialog>
</div>
</template>
<script>
import { quickMMeasure } from '@/api/bluetoothScale'
import { mapState } from 'vuex';
import booking from '@/components/booking.vue'
export default {
name: 'RaiseTheScale',
components: {
booking
},
data() {
return {
showBookingDialog: false
}
},
computed: {
...mapState({
patientInfo: (state) => state.patientInfo
})
},
methods: {
//获取升高体重称的数据
getBodyData() {
if(this.patientInfo.gender == null && this.patientInfo.name == null){
this.$message.error('请先选择患者')
return
}
let param = {
isBooking: this.patientInfo.bookingId ? true : false,
bookingId: this.patientInfo.bookingId || 1,//预约ID
gender: this.patientInfo.gender,//性别
age: this.patientInfo.age,//年龄
deviceAddress: null,//设备地址
timeout: null,//测量超时时间
}
quickMMeasure(param).then(res => {
if (res.code == 200) {
let patientInfo = {
... this.patientInfo
}
patientInfo.height = res.data.height
patientInfo.weight = res.data.weight
this.$store.dispatch('updatePatientInfo', patientInfo)
this.$message.success(`测量成功:身高: ${this.patientInfo.height}cm, 体重: ${this.patientInfo.weight}kg`)
}
})
},
// 更换患者
changePatient() {
this.showBookingDialog = true
},
handleCloseBooking() {
this.showBookingDialog = false
},
handleSubmit() {
if (this.patientInfo.height <= 0 || this.patientInfo.weight <= 0) {
this.$message.error('请先获取身高和体重数据')
return
}
this.$emit('next-step', this.patientInfo)
}
}
}
</script>
<style scoped>
.patient-info {
margin-bottom: 20px;
}
.compact-info {
font-size: 14px;
line-height: 1.8;
}
.info-line {
padding: 6px 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.info-label {
color: #666;
font-weight: 500;
margin-right: 4px;
}
.info-divider {
color: #ddd;
margin: 0 8px;
}
</style>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment