|
|
|
<template>
|
|
|
|
<div class="page">
|
|
|
|
<el-tabs type="border-card" class="el_tabs">
|
|
|
|
<el-tab-pane label="拍摄">
|
|
|
|
<div class="IMG_VIEW_container">
|
|
|
|
<!-- 主摄像头 -->
|
|
|
|
<div id="IMG_VIEW_Content" class="IMG_VIEW1">
|
|
|
|
<img id="IMG_VIEW1" ref="IMG_VIEW" src="../../../public/img/load.gif" alt="主摄像头" />
|
|
|
|
<el-icon v-if="!IMG_startUp" @click="Devicerestart" class="SwitchButton"
|
|
|
|
><SwitchButton
|
|
|
|
/></el-icon>
|
|
|
|
<div class="el_icon" v-if="IMG_startUp">
|
|
|
|
<el-tooltip effect="dark" :content="IMG_Trimming_edge ? '关闭裁边' : '开启裁边'">
|
|
|
|
<el-icon
|
|
|
|
:style="{ color: IMG_Trimming_edge ? 'red' : '#fff' }"
|
|
|
|
@click="Opentrimming"
|
|
|
|
><Crop
|
|
|
|
/></el-icon>
|
|
|
|
</el-tooltip>
|
|
|
|
<el-tooltip effect="dark" content="点击拍摄">
|
|
|
|
<el-icon @click="view1_scan"><CameraFilled /></el-icon>
|
|
|
|
</el-tooltip>
|
|
|
|
<el-tooltip effect="dark" content="旋转摄像头">
|
|
|
|
<el-icon @click="Rotatingcamera(90)"><RefreshRight /></el-icon>
|
|
|
|
</el-tooltip>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 缩略图 -->
|
|
|
|
<div id="suoluetu" ref="IMG_Thumbnail">
|
|
|
|
<el-empty
|
|
|
|
style="margin: auto"
|
|
|
|
description="暂无缩略图"
|
|
|
|
v-if="!IMG_suoluetuList.length"
|
|
|
|
/>
|
|
|
|
<template v-for="(item, index) in IMG_suoluetuList" :key="index">
|
|
|
|
<el-image
|
|
|
|
ref="el => setImagePreviewRef(el, index)"
|
|
|
|
style="width: 90px; height: 90px"
|
|
|
|
:src="item"
|
|
|
|
:zoom-rate="1.2"
|
|
|
|
:max-scale="4"
|
|
|
|
:min-scale="0.2"
|
|
|
|
:preview-src-list="IMG_suoluetuList"
|
|
|
|
:initial-index="initialIndex"
|
|
|
|
fit="cover"
|
|
|
|
@click="showPreview(index)"
|
|
|
|
/>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
<el-tab-pane label="操作日志">
|
|
|
|
<div id="log"></div>
|
|
|
|
</el-tab-pane>
|
|
|
|
</el-tabs>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
import { reactive, ref, onMounted, watch, nextTick } from 'vue';
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
import IMG_error from '../../../public/img/loaderror.png'; //设备未连接
|
|
|
|
import IMG_drive from '../../../public/img/driveerror.png'; //设备驱动未启动
|
|
|
|
import IMG_load from '../../../public/img/load.gif'; //设备驱动未启动
|
|
|
|
|
|
|
|
const IMG_camera = ref(false); // 摄像头状态
|
|
|
|
const IMG_URL = 'http://127.0.0.1:38088/video=stream&camidx=0?' + Math.random(); // 主摄像头地址
|
|
|
|
const IMG_VIEW = ref(null); // 主摄像头实例
|
|
|
|
const IMG_Thumbnail = ref(null); // 缩略图实例
|
|
|
|
const IMG_suoluetuList = ref([]); // 示例图片列表
|
|
|
|
const initialIndex = ref(0); // 初始显示的索引
|
|
|
|
const imagePreviewRefs = ref([]);
|
|
|
|
let IMG_API_SERVER = reactive(true); //true表示线上环境,false表示开发环境
|
|
|
|
let IMG_Trimming_edge = ref(false); //图片裁边
|
|
|
|
let rectifying = ref(0); //裁边
|
|
|
|
let IMG_startUp = ref(false); //摄像头是否启动
|
|
|
|
let IMG_API = ref('/api/blade-resource/oss/endpoint/put-file');
|
|
|
|
let zoom = ref(5); //图片缩放等级
|
|
|
|
let IMGstate = ref(1); //是否清楚历史记录
|
|
|
|
const $emit = defineEmits(['upload-success']);
|
|
|
|
const props = defineProps({
|
|
|
|
IMGstate: {
|
|
|
|
type: Number, //参数类型
|
|
|
|
required: false, //是否必传
|
|
|
|
default: 1, //默认值;0,清除历史记录.1不清除
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.IMGstate,
|
|
|
|
newValue => {
|
|
|
|
if (newValue != IMGstate.value) {
|
|
|
|
IMGstate.value = props.IMGstate;
|
|
|
|
IMG_suoluetuList.value = []; //重置拍摄图片
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
onLoad(); // 页面初始化加载
|
|
|
|
});
|
|
|
|
|
|
|
|
// 封装请求方法
|
|
|
|
const request = (url, method = 'POST', data = {}) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
xhr.open(method, url, true);
|
|
|
|
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
|
|
|
xhr.onload = () => {
|
|
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
|
|
resolve(JSON.parse(xhr.responseText));
|
|
|
|
} else {
|
|
|
|
reject(new Error(`Error: ${xhr.statusText}`));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
xhr.onerror = () => reject(new Error(`Error: ${xhr.statusText}`));
|
|
|
|
xhr.send(JSON.stringify(data));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 打印日志
|
|
|
|
const mylog = val => {
|
|
|
|
let element = document.getElementById('log');
|
|
|
|
let old_val = element.innerHTML;
|
|
|
|
let date = new Date().toString().slice(16, 24);
|
|
|
|
element.innerHTML = date + ' ' + val + '<br>' + old_val;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 设置每个 el-image 的引用
|
|
|
|
const setImagePreviewRef = (el, index) => {
|
|
|
|
if (el) {
|
|
|
|
imagePreviewRefs.value[index] = el.$refs.imageRef;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const showPreview = async index => {
|
|
|
|
initialIndex.value = index; // 设置点击的图片为初始显示图片
|
|
|
|
await nextTick();
|
|
|
|
// 触发图片预览
|
|
|
|
if (imagePreviewRefs.value[index]) {
|
|
|
|
imagePreviewRefs.value[index].click();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const IsConnect = async () => {
|
|
|
|
try {
|
|
|
|
const response = await request('http://127.0.0.1:38088/device=isconnect');
|
|
|
|
if (response.code === '0') {
|
|
|
|
return response;
|
|
|
|
} else {
|
|
|
|
throw new Error('设备未连接');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error.message);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//(开启/关闭)图像裁边
|
|
|
|
const Opentrimming = () => {
|
|
|
|
IMG_Trimming_edge.value = !IMG_Trimming_edge.value;
|
|
|
|
if (IMG_Trimming_edge.value) {
|
|
|
|
rectifying.value = '1';
|
|
|
|
} else {
|
|
|
|
rectifying.value = '0';
|
|
|
|
}
|
|
|
|
let data = {
|
|
|
|
camidx: '0', // 摄像头索引,0:主头;1:副头
|
|
|
|
open: rectifying.value, // 状态,0:关;1:开
|
|
|
|
};
|
|
|
|
request('http://127.0.0.1:38088/dvideo=cameradeskew', 'POST', data)
|
|
|
|
.then(response => {
|
|
|
|
const message = '';
|
|
|
|
if (response.code !== '0') {
|
|
|
|
message = IMG_Trimming_edge.value ? '图像裁边已开启' : '图像裁边已关闭';
|
|
|
|
ElMessage({ message, type: 'success' });
|
|
|
|
mylog(message);
|
|
|
|
} else {
|
|
|
|
mylog(message);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error('Error:', error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 开启摄像头
|
|
|
|
const Deviceinitialization = async () => {
|
|
|
|
let IMG_state = await IsConnect();
|
|
|
|
if (!IMG_state) {
|
|
|
|
ElMessage({ message: '设备驱动未连接', type: 'warning' });
|
|
|
|
mylog('设备驱动未连接');
|
|
|
|
IMG_VIEW.value.src = IMG_drive;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (IMG_state.code === '0' && IMG_state.data === '1') {
|
|
|
|
ElMessage({ message: '设备已连接,设备数量:1', type: 'success' });
|
|
|
|
mylog('设备已连接,设备数量:1');
|
|
|
|
let TIME_IMG_VIEW = setTimeout(() => {
|
|
|
|
if (IMG_VIEW.value) {
|
|
|
|
IMG_startUp.value = true;
|
|
|
|
IMG_VIEW.value.src = IMG_URL;
|
|
|
|
ElMessage({ message: '摄像头已启动', type: 'success' });
|
|
|
|
mylog('摄像头已启动');
|
|
|
|
}
|
|
|
|
clearTimeout(TIME_IMG_VIEW);
|
|
|
|
}, 2000);
|
|
|
|
} else {
|
|
|
|
IMG_VIEW.value.src = IMG_error;
|
|
|
|
ElMessage({ message: '设备未连接', type: 'warning' });
|
|
|
|
mylog('设备未连接');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 页面初始化加载
|
|
|
|
const onLoad = async () => {
|
|
|
|
await Deviceinitialization();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 设备重启
|
|
|
|
const Devicerestart = () => {
|
|
|
|
IMG_camera.value = true;
|
|
|
|
mylog('重启设备');
|
|
|
|
IMG_VIEW.value.src = IMG_load;
|
|
|
|
Deviceinitialization();
|
|
|
|
};
|
|
|
|
|
|
|
|
// 将 base64 数据转换为 Blob 对象
|
|
|
|
const base64ToBlob = (base64, mime) => {
|
|
|
|
if (!base64) {
|
|
|
|
console.error('base64 数据为空');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const byteString = atob(base64);
|
|
|
|
const ab = new ArrayBuffer(byteString.length);
|
|
|
|
const ia = new Uint8Array(ab);
|
|
|
|
for (let i = 0; i < byteString.length; i++) {
|
|
|
|
ia[i] = byteString.charCodeAt(i);
|
|
|
|
}
|
|
|
|
return new Blob([ab], { type: mime });
|
|
|
|
};
|
|
|
|
|
|
|
|
// 上传图片函数
|
|
|
|
const Upload_Images = file => {
|
|
|
|
const formData = new FormData();
|
|
|
|
const filename = file.name || 'image.jpg';
|
|
|
|
const filetype = file.type || 'image/jpeg';
|
|
|
|
const fileBlob = new Blob([file], { type: filetype });
|
|
|
|
const fileWithMetadata = new File([fileBlob], filename, { type: filetype });
|
|
|
|
formData.append('file', fileWithMetadata);
|
|
|
|
axios
|
|
|
|
.post(IMG_API.value, formData)
|
|
|
|
.then(res => {
|
|
|
|
if (res.data.code === 200) {
|
|
|
|
console.log(res, '图片上传成功');
|
|
|
|
ElMessage({ message: '图片上传成功', type: 'success' });
|
|
|
|
$emit('upload-success', res); //图片地址传递出去
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
ElMessage({ message: '图片上传失败', type: 'error' });
|
|
|
|
console.error('上传失败', error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 添加缩略图
|
|
|
|
const IMG_add = img_base64 => {
|
|
|
|
const base64Data = img_base64.replace(/^data:image\/\w+;base64,/, '');
|
|
|
|
const blob = base64ToBlob(base64Data, 'image/jpeg');
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
if (IMG_Thumbnail.value) {
|
|
|
|
IMG_suoluetuList.value.push(url);
|
|
|
|
mylog('添加缩略图成功');
|
|
|
|
}
|
|
|
|
Upload_Images(blob);
|
|
|
|
};
|
|
|
|
|
|
|
|
// 旋转摄像头
|
|
|
|
const Rotatingcamera = angle => {
|
|
|
|
const data = { camidx: '0', rotate: String(angle) };
|
|
|
|
request('http://127.0.0.1:38088/video=rotate', 'POST', data)
|
|
|
|
.then(response => {
|
|
|
|
if (response.code !== '0') {
|
|
|
|
ElMessage.error('拍摄失败请重新尝试');
|
|
|
|
} else {
|
|
|
|
mylog('旋转摄像头成功');
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error('Error:', error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// 点击拍照
|
|
|
|
let view1_scan = async () => {
|
|
|
|
const data = {
|
|
|
|
filepath: 'base64',
|
|
|
|
rotate: '0',
|
|
|
|
cutpage: '0',
|
|
|
|
camidx: '0',
|
|
|
|
ColorMode: '0',
|
|
|
|
quality: '3',
|
|
|
|
};
|
|
|
|
request('http://127.0.0.1:38088/video=grabimage', 'POST', data)
|
|
|
|
.then(response => {
|
|
|
|
if (response.code !== '0') {
|
|
|
|
ElMessage.error('拍摄失败请重新尝试');
|
|
|
|
} else {
|
|
|
|
ElMessage({ message: '拍摄成功', type: 'success' });
|
|
|
|
IMG_add(response.photoBase64);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error('Error:', error);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
.el_tabs {
|
|
|
|
height: 380px;
|
|
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
.page {
|
|
|
|
width: 100%;
|
|
|
|
img {
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.IMG_VIEW_container {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 全局 */
|
|
|
|
#app {
|
|
|
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
-moz-osx-font-smoothing: grayscale;
|
|
|
|
color: #2c3e50;
|
|
|
|
margin-top: 10px;
|
|
|
|
}
|
|
|
|
.IMG_VIEW1 {
|
|
|
|
width: 60%;
|
|
|
|
height: 300px;
|
|
|
|
border: 1px solid red;
|
|
|
|
position: relative;
|
|
|
|
background-color: #000;
|
|
|
|
img {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
.el_icon {
|
|
|
|
position: absolute;
|
|
|
|
bottom: 10px;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, 0);
|
|
|
|
font-size: 25px;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
.el_icon {
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
.el_icon i {
|
|
|
|
margin: 0 10px;
|
|
|
|
}
|
|
|
|
.el_icon i:active {
|
|
|
|
color: #2196f3;
|
|
|
|
}
|
|
|
|
.SwitchButton {
|
|
|
|
position: absolute;
|
|
|
|
top: 10%;
|
|
|
|
right: 0%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
font-size: 25px;
|
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
.SwitchButton:active {
|
|
|
|
color: #2196f3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* 缩略图 */
|
|
|
|
#suoluetu {
|
|
|
|
width: 38%;
|
|
|
|
height: 300px;
|
|
|
|
overflow: scroll;
|
|
|
|
border: 1px solid #172e60;
|
|
|
|
padding: 0 !important;
|
|
|
|
display: flex;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
}
|
|
|
|
#suoluetu {
|
|
|
|
.el-image {
|
|
|
|
margin: 0 4px;
|
|
|
|
margin-bottom: 2px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* 操作按钮 */
|
|
|
|
#myactive {
|
|
|
|
border: 1px solid yellowgreen;
|
|
|
|
margin-top: 10px;
|
|
|
|
padding: 10px 5px;
|
|
|
|
}
|
|
|
|
/* 操作日志 */
|
|
|
|
#mylog {
|
|
|
|
border: 1px solid black;
|
|
|
|
padding: 10px;
|
|
|
|
margin-top: 10px;
|
|
|
|
overflow: auto;
|
|
|
|
}
|
|
|
|
</style>
|