Browse Source

盘点数据总览/视频

master
xuhuajiao 10 months ago
parent
commit
0dad785254
  1. 2
      public/index.html
  2. 3514
      public/static/adapter.min.js
  3. 305
      public/static/webrtcstreamer.js
  4. 313
      public/static/webrtcstreamer2.js
  5. 10
      src/api/area/index.js
  6. 10
      src/assets/styles/manage.scss
  7. 26
      src/router/routers.js
  8. 32
      src/views/components/bookSwiper.vue
  9. 317
      src/views/components/canvasPreview.vue
  10. 61
      src/views/components/hkVideo.vue
  11. 14
      src/views/home.vue
  12. 88
      src/views/visualCheck/bookstore/collection/index.vue
  13. 293
      src/views/visualCheck/checkManage/dataScreening/girdList.vue
  14. 320
      src/views/visualCheck/checkManage/dataScreening/index.vue
  15. 331
      src/views/visualCheck/checkManage/dataScreening/regionsList.vue
  16. 672
      src/views/visualCheck/checkManage/dataScreening/shelfList.vue
  17. 151
      src/views/visualCheck/checkManage/index.vue
  18. 67
      src/views/visualCheck/venueDevice/area/index.vue
  19. 63
      src/views/visualCheck/venueDevice/bookshelfPosition/index.vue
  20. 2
      src/views/visualCheck/venueDevice/floor/index.vue

2
public/index.html

@ -6,6 +6,8 @@
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.svg" type="image/svg+xml">
<script src="/static/adapter.min.js"></script>
<script src="/static/webrtcstreamer.js"></script>
<title><%= webpackConfig.name %></title>
<script type="text/javascript" src="/static/config.js"></script>
</head>

3514
public/static/adapter.min.js
File diff suppressed because it is too large
View File

305
public/static/webrtcstreamer.js

@ -0,0 +1,305 @@
var WebRtcStreamer = (function() {
/**
* Interface with WebRTC-streamer API
* @constructor
* @param {string} videoElement - id of the video element tag
* @param {string} srvurl - url of webrtc-streamer (default is current location)
*/
var WebRtcStreamer = function WebRtcStreamer (videoElement, srvurl) {
if (typeof videoElement === "string") {
this.videoElement = document.getElementById(videoElement);
} else {
this.videoElement = videoElement;
}
this.srvurl = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port;
this.pc = null;
this.mediaConstraints = { offerToReceiveAudio: true, offerToReceiveVideo: true };
this.iceServers = null;
this.earlyCandidates = [];
}
WebRtcStreamer.prototype._handleHttpErrors = function (response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
/**
* Connect a WebRTC Stream to videoElement
* @param {string} videourl - id of WebRTC video stream
* @param {string} audiourl - id of WebRTC audio stream
* @param {string} options - options of WebRTC call
* @param {string} stream - local stream to send
*/
WebRtcStreamer.prototype.connect = function(videourl, audiourl, options, localstream) {
this.disconnect();
// getIceServers is not already received
if (!this.iceServers) {
console.log("Get IceServers");
fetch(this.srvurl + "/api/getIceServers")
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.then( (response) => this.onReceiveGetIceServers(response, videourl, audiourl, options, localstream))
.catch( (error) => this.onError("getIceServers " + error ))
} else {
this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream);
}
}
/**
* Disconnect a WebRTC Stream and clear videoElement source
*/
WebRtcStreamer.prototype.disconnect = function() {
if (this.videoElement?.srcObject) {
this.videoElement.srcObject.getTracks().forEach(track => {
track.stop()
this.videoElement.srcObject.removeTrack(track);
});
}
if (this.pc) {
fetch(this.srvurl + "/api/hangup?peerid=" + this.pc.peerid)
.then(this._handleHttpErrors)
.catch( (error) => this.onError("hangup " + error ))
try {
this.pc.close();
}
catch (e) {
console.log ("Failure close peer connection:" + e);
}
this.pc = null;
}
}
/*
* GetIceServers callback
*/
WebRtcStreamer.prototype.onReceiveGetIceServers = function(iceServers, videourl, audiourl, options, stream) {
this.iceServers = iceServers;
this.pcConfig = iceServers || {"iceServers": [] };
try {
this.createPeerConnection();
var callurl = this.srvurl + "/api/call?peerid=" + this.pc.peerid + "&url=" + encodeURIComponent(videourl);
if (audiourl) {
callurl += "&audiourl="+encodeURIComponent(audiourl);
}
if (options) {
callurl += "&options="+encodeURIComponent(options);
}
if (stream) {
this.pc.addStream(stream);
}
// clear early candidates
this.earlyCandidates.length = 0;
// create Offer
this.pc.createOffer(this.mediaConstraints).then((sessionDescription) => {
console.log("Create offer:" + JSON.stringify(sessionDescription));
this.pc.setLocalDescription(sessionDescription)
.then(() => {
fetch(callurl, { method: "POST", body: JSON.stringify(sessionDescription) })
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.catch( (error) => this.onError("call " + error ))
.then( (response) => this.onReceiveCall(response) )
.catch( (error) => this.onError("call " + error ))
}, (error) => {
console.log ("setLocalDescription error:" + JSON.stringify(error));
});
}, (error) => {
alert("Create offer error:" + JSON.stringify(error));
});
} catch (e) {
this.disconnect();
alert("connect error: " + e);
}
}
WebRtcStreamer.prototype.getIceCandidate = function() {
fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid)
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.then( (response) => this.onReceiveCandidate(response))
.catch( (error) => this.onError("getIceCandidate " + error ))
}
/*
* create RTCPeerConnection
*/
WebRtcStreamer.prototype.createPeerConnection = function() {
console.log("createPeerConnection config: " + JSON.stringify(this.pcConfig));
this.pc = new RTCPeerConnection(this.pcConfig);
var pc = this.pc;
pc.peerid = Math.random();
pc.onicecandidate = (evt) => this.onIceCandidate(evt);
pc.onaddstream = (evt) => this.onAddStream(evt);
pc.oniceconnectionstatechange = (evt) => {
console.log("oniceconnectionstatechange state: " + pc.iceConnectionState);
if (this.videoElement) {
if (pc.iceConnectionState === "connected") {
this.videoElement.style.opacity = "1.0";
}
else if (pc.iceConnectionState === "disconnected") {
this.videoElement.style.opacity = "0.25";
}
else if ( (pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed") ) {
this.videoElement.style.opacity = "0.5";
} else if (pc.iceConnectionState === "new") {
this.getIceCandidate();
}
}
}
pc.ondatachannel = function(evt) {
console.log("remote datachannel created:"+JSON.stringify(evt));
evt.channel.onopen = function () {
console.log("remote datachannel open");
this.send("remote channel openned");
}
evt.channel.onmessage = function (event) {
console.log("remote datachannel recv:"+JSON.stringify(event.data));
}
}
pc.onicegatheringstatechange = function() {
if (pc.iceGatheringState === "complete") {
const recvs = pc.getReceivers();
recvs.forEach((recv) => {
if (recv.track && recv.track.kind === "video") {
console.log("codecs:" + JSON.stringify(recv.getParameters().codecs))
}
});
}
}
try {
var dataChannel = pc.createDataChannel("ClientDataChannel");
dataChannel.onopen = function() {
console.log("local datachannel open");
this.send("local channel openned");
}
dataChannel.onmessage = function(evt) {
console.log("local datachannel recv:"+JSON.stringify(evt.data));
}
} catch (e) {
console.log("Cannor create datachannel error: " + e);
}
console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) );
return pc;
}
/*
* RTCPeerConnection IceCandidate callback
*/
WebRtcStreamer.prototype.onIceCandidate = function (event) {
if (event.candidate) {
if (this.pc.currentRemoteDescription) {
this.addIceCandidate(this.pc.peerid, event.candidate);
} else {
this.earlyCandidates.push(event.candidate);
}
}
else {
console.log("End of candidates.");
}
}
WebRtcStreamer.prototype.addIceCandidate = function(peerid, candidate) {
fetch(this.srvurl + "/api/addIceCandidate?peerid="+peerid, { method: "POST", body: JSON.stringify(candidate) })
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.then( (response) => {console.log("addIceCandidate ok:" + response)})
.catch( (error) => this.onError("addIceCandidate " + error ))
}
/*
* RTCPeerConnection AddTrack callback
*/
WebRtcStreamer.prototype.onAddStream = function(event) {
console.log("Remote track added:" + JSON.stringify(event));
this.videoElement.srcObject = event.stream;
var promise = this.videoElement.play();
if (promise !== undefined) {
promise.catch((error) => {
console.warn("error:"+error);
this.videoElement.setAttribute("controls", true);
});
}
}
/*
* AJAX /call callback
*/
WebRtcStreamer.prototype.onReceiveCall = function(dataJson) {
console.log("offer: " + JSON.stringify(dataJson));
var descr = new RTCSessionDescription(dataJson);
this.pc.setRemoteDescription(descr).then(() => {
console.log ("setRemoteDescription ok");
while (this.earlyCandidates.length) {
var candidate = this.earlyCandidates.shift();
this.addIceCandidate(this.pc.peerid, candidate);
}
this.getIceCandidate()
}
, (error) => {
console.log ("setRemoteDescription error:" + JSON.stringify(error));
});
}
/*
* AJAX /getIceCandidate callback
*/
WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) {
console.log("candidate: " + JSON.stringify(dataJson));
if (dataJson) {
for (var i=0; i<dataJson.length; i++) {
var candidate = new RTCIceCandidate(dataJson[i]);
console.log("Adding ICE candidate :" + JSON.stringify(candidate) );
this.pc.addIceCandidate(candidate).then( () => { console.log ("addIceCandidate OK"); }
, (error) => { console.log ("addIceCandidate error:" + JSON.stringify(error)); } );
}
this.pc.addIceCandidate();
}
}
/*
* AJAX callback for Error
*/
WebRtcStreamer.prototype.onError = function(status) {
console.log("onError:" + status);
}
return WebRtcStreamer;
})();
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
window.WebRtcStreamer = WebRtcStreamer;
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = WebRtcStreamer;
}

313
public/static/webrtcstreamer2.js

@ -0,0 +1,313 @@
var WebRtcStreamer = (function() {
/**
* Interface with WebRTC-streamer API
* @constructor
* @param {string} videoElement - id of the video element tag
* @param {string} srvurl - url of webrtc-streamer (default is current location)
*/
var WebRtcStreamer = function WebRtcStreamer (videoElement, srvurl) {
if (typeof videoElement === "string") {
this.videoElement = document.getElementById(videoElement);
} else {
this.videoElement = videoElement;
}
this.srvurl = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port;
this.pc = null;
this.mediaConstraints = { offerToReceiveAudio: true, offerToReceiveVideo: true };
this.iceServers = null;
this.earlyCandidates = [];
}
WebRtcStreamer.prototype._handleHttpErrors = function (response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
/**
* Connect a WebRTC Stream to videoElement
* @param {string} videourl - id of WebRTC video stream
* @param {string} audiourl - id of WebRTC audio stream
* @param {string} options - options of WebRTC call
* @param {string} stream - local stream to send
* @param {string} prefmime - prefered mime
*/
WebRtcStreamer.prototype.connect = function(videourl, audiourl, options, localstream, prefmime) {
this.disconnect();
// getIceServers is not already received
if (!this.iceServers) {
console.log("Get IceServers");
fetch(this.srvurl + "/api/getIceServers")
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.then( (response) => this.onReceiveGetIceServers(response, videourl, audiourl, options, localstream, prefmime))
.catch( (error) => this.onError("getIceServers " + error ))
} else {
this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream, prefmime);
}
}
/**
* Disconnect a WebRTC Stream and clear videoElement source
*/
WebRtcStreamer.prototype.disconnect = function() {
if (this.videoElement?.srcObject) {
this.videoElement.srcObject.getTracks().forEach(track => {
track.stop()
this.videoElement.srcObject.removeTrack(track);
});
}
if (this.pc) {
fetch(this.srvurl + "/api/hangup?peerid=" + this.pc.peerid)
.then(this._handleHttpErrors)
.catch( (error) => this.onError("hangup " + error ))
try {
this.pc.close();
}
catch (e) {
console.log ("Failure close peer connection:" + e);
}
this.pc = null;
}
}
/*
* GetIceServers callback
*/
WebRtcStreamer.prototype.onReceiveGetIceServers = function(iceServers, videourl, audiourl, options, stream, prefmime) {
this.iceServers = iceServers;
this.pcConfig = iceServers || {"iceServers": [] };
try {
this.createPeerConnection();
var callurl = this.srvurl + "/api/call?peerid=" + this.pc.peerid + "&url=" + encodeURIComponent(videourl);
if (audiourl) {
callurl += "&audiourl="+encodeURIComponent(audiourl);
}
if (options) {
callurl += "&options="+encodeURIComponent(options);
}
if (stream) {
this.pc.addStream(stream);
}
// clear early candidates
this.earlyCandidates.length = 0;
// create Offer
this.pc.createOffer(this.mediaConstraints).then((sessionDescription) => {
console.log("Create offer:" + JSON.stringify(sessionDescription));
console.log(`video codecs:${Array.from(new Set(RTCRtpReceiver.getCapabilities("video")?.codecs?.map(codec => codec.mimeType)))}`)
console.log(`audio codecs:${Array.from(new Set(RTCRtpReceiver.getCapabilities("audio")?.codecs?.map(codec => codec.mimeType)))}`)
if (prefmime != undefined) {
//set prefered codec
const [prefkind] = prefmime.split('/');
const codecs = RTCRtpReceiver.getCapabilities(prefkind).codecs;
const preferedCodecs = codecs.filter(codec => codec.mimeType === prefmime);
console.log(`preferedCodecs:${JSON.stringify(preferedCodecs)}`);
this.pc.getTransceivers().filter(transceiver => transceiver.receiver.track.kind === prefkind).forEach(tcvr => {
if(tcvr.setCodecPreferences != undefined) {
tcvr.setCodecPreferences(preferedCodecs);
}
});
}
this.pc.setLocalDescription(sessionDescription)
.then(() => {
fetch(callurl, { method: "POST", body: JSON.stringify(sessionDescription) })
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.catch( (error) => this.onError("call " + error ))
.then( (response) => this.onReceiveCall(response) )
.catch( (error) => this.onError("call " + error ))
}, (error) => {
console.log ("setLocalDescription error:" + JSON.stringify(error));
});
}, (error) => {
alert("Create offer error:" + JSON.stringify(error));
});
} catch (e) {
this.disconnect();
alert("connect error: " + e);
}
}
WebRtcStreamer.prototype.getIceCandidate = function() {
fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid)
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.then( (response) => this.onReceiveCandidate(response))
.catch( (error) => this.onError("getIceCandidate " + error ))
}
/*
* create RTCPeerConnection
*/
WebRtcStreamer.prototype.createPeerConnection = function() {
console.log("createPeerConnection config: " + JSON.stringify(this.pcConfig));
this.pc = new RTCPeerConnection(this.pcConfig);
var pc = this.pc;
pc.peerid = Math.random();
pc.onicecandidate = (evt) => this.onIceCandidate(evt);
pc.onaddstream = (evt) => this.onAddStream(evt);
pc.oniceconnectionstatechange = (evt) => {
console.log("oniceconnectionstatechange state: " + pc.iceConnectionState);
if (this.videoElement) {
if (pc.iceConnectionState === "connected") {
this.videoElement.style.opacity = "1.0";
}
else if (pc.iceConnectionState === "disconnected") {
this.videoElement.style.opacity = "0.25";
}
else if ( (pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed") ) {
this.videoElement.style.opacity = "0.5";
} else if (pc.iceConnectionState === "new") {
this.getIceCandidate();
}
}
}
pc.ondatachannel = function(evt) {
console.log("remote datachannel created:"+JSON.stringify(evt));
evt.channel.onopen = function () {
console.log("remote datachannel open");
this.send("remote channel openned");
}
evt.channel.onmessage = function (event) {
console.log("remote datachannel recv:"+JSON.stringify(event.data));
}
}
try {
var dataChannel = pc.createDataChannel("ClientDataChannel");
dataChannel.onopen = function() {
console.log("local datachannel open");
this.send("local channel openned");
}
dataChannel.onmessage = function(evt) {
console.log("local datachannel recv:"+JSON.stringify(evt.data));
}
} catch (e) {
console.log("Cannor create datachannel error: " + e);
}
console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) );
return pc;
}
/*
* RTCPeerConnection IceCandidate callback
*/
WebRtcStreamer.prototype.onIceCandidate = function (event) {
if (event.candidate) {
if (this.pc.currentRemoteDescription) {
this.addIceCandidate(this.pc.peerid, event.candidate);
} else {
this.earlyCandidates.push(event.candidate);
}
}
else {
console.log("End of candidates.");
}
}
WebRtcStreamer.prototype.addIceCandidate = function(peerid, candidate) {
fetch(this.srvurl + "/api/addIceCandidate?peerid="+peerid, { method: "POST", body: JSON.stringify(candidate) })
.then(this._handleHttpErrors)
.then( (response) => (response.json()) )
.then( (response) => {console.log("addIceCandidate ok:" + response)})
.catch( (error) => this.onError("addIceCandidate " + error ))
}
/*
* RTCPeerConnection AddTrack callback
*/
WebRtcStreamer.prototype.onAddStream = function(event) {
console.log("Remote track added:" + JSON.stringify(event));
this.videoElement.srcObject = event.stream;
var promise = this.videoElement.play();
if (promise !== undefined) {
promise.catch((error) => {
console.warn("error:"+error);
this.videoElement.setAttribute("controls", true);
});
}
}
/*
* AJAX /call callback
*/
WebRtcStreamer.prototype.onReceiveCall = function(dataJson) {
console.log("offer: " + JSON.stringify(dataJson));
var descr = new RTCSessionDescription(dataJson);
this.pc.setRemoteDescription(descr).then(() => {
console.log ("setRemoteDescription ok");
while (this.earlyCandidates.length) {
var candidate = this.earlyCandidates.shift();
this.addIceCandidate(this.pc.peerid, candidate);
}
this.getIceCandidate()
}
, (error) => {
console.log ("setRemoteDescription error:" + JSON.stringify(error));
});
}
/*
* AJAX /getIceCandidate callback
*/
WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) {
console.log("candidate: " + JSON.stringify(dataJson));
if (dataJson) {
for (var i=0; i<dataJson.length; i++) {
var candidate = new RTCIceCandidate(dataJson[i]);
console.log("Adding ICE candidate :" + JSON.stringify(candidate) );
this.pc.addIceCandidate(candidate).then( () => { console.log ("addIceCandidate OK"); }
, (error) => { console.log ("addIceCandidate error:" + JSON.stringify(error)); } );
}
this.pc.addIceCandidate();
}
}
/*
* AJAX callback for Error
*/
WebRtcStreamer.prototype.onError = function(status) {
console.log("onError:" + status);
}
return WebRtcStreamer;
})();
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
window.WebRtcStreamer = WebRtcStreamer;
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = WebRtcStreamer;
}

10
src/api/area/index.js

@ -56,4 +56,12 @@ export function FetchRegionAllByFloor(params) {
})
}
export default { add, edit, del, sort, saveLibraryRegionSignPoint, FetchInitLibraryRegionList, FetchRegionAllByFloor }
// 根据楼层id获取区域详细信息
export function FetchLibraryRegionDetails(params) {
return request({
url: 'api/libraryRegion/getLibraryRegionDetails' + '?' + qs.stringify(params, { indices: false }),
method: 'get'
})
}
export default { add, edit, del, sort, saveLibraryRegionSignPoint, FetchInitLibraryRegionList, FetchRegionAllByFloor, FetchLibraryRegionDetails }

10
src/assets/styles/manage.scss

@ -297,12 +297,14 @@
flex: 1;
flex-wrap: wrap;
text-align: center;
height: 100%;
// height: 100%;
.cabinet-cell{
position: relative;
font-size: 14px;
color: #000;
border: 1px solid #000;
color: #0C0E1E;
border: 2px solid #fff;
background-color: #E8F2FF;
border-radius: 3px;
overflow: hidden;
// &:nth-last-of-type(-n+4) {
// margin-bottom: 0;
@ -321,7 +323,7 @@
}
&.active{
color: #fff;
background-color: #A6ADB6;
background-color: #0348F3;
}
}
.in-all, .out-all, .out-part, .in-part{

26
src/router/routers.js

@ -77,6 +77,32 @@ export const constantRouterMap = [
meta: { title: '架位列表', noCache: true, activeMenu: '/check/venueDevice/bookshelf' }
}
]
},
{
path: '/dataScreening',
component: Layout,
hidden: true,
redirect: 'noredirect',
children: [
{
path: 'regions',
component: (resolve) => require(['@/views/visualCheck/checkManage/dataScreening/regionsList'], resolve),
name: '区域总览',
meta: { title: '区域总览', noCache: true, activeMenu: '/check/check/dataScreening' }
},
{
path: 'shelf',
component: (resolve) => require(['@/views/visualCheck/checkManage/dataScreening/shelfList'], resolve),
name: '书架总览',
meta: { title: '书架总览', noCache: true, activeMenu: '/check/check/dataScreening' }
},
{
path: 'gird',
component: (resolve) => require(['@/views/visualCheck/checkManage/dataScreening/girdList'], resolve),
name: '架位总览',
meta: { title: '架位总览', noCache: true, activeMenu: '/check/check/dataScreening' }
}
]
}
// {
// path: '/bookSearch',

32
src/views/components/bookSwiper.vue

@ -39,11 +39,23 @@
:key="'content' + index"
class="swiper-slide-content"
>
<ul class="cabinet-row">
<li v-for="(item,index) in bookList" :key="index" :class="{ active: index === rightDataIndex }">
<span>{{ item }}</span>
</li>
</ul>
<el-table
:data="bookList"
stripe
style="width: 100%"
height="150"
>
<el-table-column
prop="name"
:label="swiperActiveIndex === 1 ? '书架' :'书名'"
/>
<el-table-column
prop="num"
label="出架册次"
width="80"
align="center"
/>
</el-table>
</swiper-slide>
</swiper>
</div>
@ -78,8 +90,14 @@ export default {
slidesPerView: 'auto',
freeMode: true
},
tabListData: [{ name: '热门图书' }, { name: '热门架位' }, { name: '冷面图书' }],
bookList: []
tabListData: [{ name: '热门图书' }, { name: '热门架位' }, { name: '冷门图书' }],
bookList: [
{ 'id': 1, 'name': '女帝英雄传', 'num': 10 },
{ 'id': 2, 'name': 'NBA赛场', 'num': 9 },
{ 'id': 3, 'name': 'CBA赛场', 'num': 8 },
{ 'id': 4, 'name': '贱客行', 'num': 7 },
{ 'id': 5, 'name': '龙凤奇侠传', 'num': 6 }
]
}
},
computed: {

317
src/views/components/canvasPreview.vue

@ -0,0 +1,317 @@
<template>
<div class="venue-preview">
<!-- v-show="currentMarkData && currentMarkData.signPoint" -->
<div v-show="currentMarkData && currentMarkData.signPoint">
<canvas :id="`canvasPreview${currentMarkData && currentMarkData.id}`" :width="width" :height="height" />
</div>
<img v-if="currentMarkData && !currentMarkData.signPoint" :src="imageUrl" :onerror="defaultImg" alt="">
<div id="tooltip" class="tooltip-style">
<!-- <div class="tooltip-top">
<h4>区域名称</h4>
<span class="update-time">2024-11-28 09:46</span>
</div>
<ul>
<li><p>在架</p><span><i>15000</i></span></li>
<li><p>错架</p><span><i>300</i></span> <span class="percentage">2.00%</span></li>
<li><p>错序</p><span><i>0</i></span><span class="percentage">0.00%</span></li>
</ul> -->
</div>
</div>
</template>
<script>
import defaultImg from '@/assets/images/system/default-img.jpg'
import { fabric } from 'fabric'
import { mapGetters } from 'vuex'
export default {
name: 'Mark',
props: {
currentMarkData: {
type: Object,
require: true,
default: function() {
return {}
}
},
imageUrl: {
type: String,
default: ''
},
pagePreview: {
type: String,
default: ''
}
},
data() {
return {
defaultImg: defaultImg,
canvasPreview: {},
width: 1200,
height: 600,
drawWidth: 2,
tooltipInfo: null
}
},
computed: {
...mapGetters([
'user',
'baseApi'
])
},
watch: {
width() {
this.canvasPreview.setWidth(this.width)
},
height() {
this.canvasPreview.setHeight(this.height)
},
currentMarkData: {
handler(newVal, oldVal) {
// newVal null undefined
if (!newVal) {
console.log('newVal is null or undefined')
return
}
},
deep: true
},
imageUrl(newVal, oldVal) {
if (newVal !== oldVal) {
console.log('imageUrl')
}
}
},
mounted() {
},
beforeDestroy() {
// if (this.canvasPreview) {
// this.canvasPreview.clear()
// this.canvasPreview.dispose()
// }
},
methods: {
initCanvasPreview(drawinfo) {
if (!this.currentMarkData) {
console.error('currentMarkData is null or undefined')
return
}
const canvasId = `canvasPreview${this.currentMarkData.id}`
this.canvasPreview = new fabric.Canvas(canvasId, {
skipTargetFind: false,
selectable: false,
selection: false
})
this.$nextTick(() => {
this.canvasPreview.selectionColor = 'rgba(0,0,0,0.05)'
this.loadDrawPreview(drawinfo)
this.canvasPreview.on('mouse:wheel', this.mouse)
})
},
//
mouse(e) {
if (undefined === e) return
let zoom = (e.e.deltaY > 0 ? -0.1 : 0.1) + this.canvasPreview.getZoom()
zoom = Math.max(0.8, zoom)
// 1/10
zoom = Math.min(3, zoom)
// 3
const zoomPoint = new fabric.Point(e.e.pageX, e.e.pageY)
this.canvasPreview.zoomToPoint(zoomPoint, zoom)
},
//
loadDrawPreview(drawinfo) {
const self = this
const pointGroup = drawinfo.pointInfo
const imgInfo = drawinfo.imgInfo
imgInfo.src = self.imageUrl
//
fabric.util.enlivenObjects([imgInfo], objects => {
objects.forEach(o => {
o.selectable = false
o.hasControls = false
o.centeredScaling = false
self.canvasPreview.add(o)
})
//
pointGroup.forEach(async(item, index) => {
if (item.pointInfo !== '') {
const polygon = new fabric.Polygon(item.pointInfo, {
id: item.id,
name: item.name,
floorId: item.floorId,
rowType: item.rowType,
toward: item.toward,
floorName: item.floorName,
regionName: item.regionName,
stroke: 'rgba(196,43, 1, 1)',
strokeWidth: self.drawWidth,
fill: 'rgba(196,43, 1, 0.3)',
opacity: 1,
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'left', //
originY: 'top' //
})
self.canvasPreview.add(polygon)
let lastClickTime = 0
const doubleClickInterval = 300
polygon.on('mousedown', function(e) {
const currentTime = new Date().getTime()
const timeDiff = currentTime - lastClickTime
if (timeDiff <= doubleClickInterval) {
console.log('双击事件', e)
lastClickTime = 0
const toReigonsData = {
id: e.target.id,
name: e.target.name,
floorId: e.target.floorId,
rowType: e.target.rowType,
toward: e.target.toward,
regionName: e.target.regionName,
floorName: e.target.floorName
}
if (self.pagePreview === 'floor') {
self.handleToRegions(toReigonsData)
} else if (self.pagePreview === 'region') {
self.handleToShelfs(toReigonsData)
}
} else {
lastClickTime = currentTime
}
})
polygon.on('mouseover', function(e) {
console.log('e', e)
console.log('e.target', e.target)
console.log('e.target.name', e.target.name)
this.tooltipInfo = {
'id': e.target.id,
'name': e.target.name
}
// this.set({ opacity: 0.3, hoverCursor: 'pointer' })
if (self.pagePreview === 'floor') {
document.getElementById('tooltip').innerHTML =
`<div class="tooltip-top">
<h4>${this.tooltipInfo.name}</h4>
<span class="update-time">2024-11-28 09:46</span>
</div>
<ul>
<li><p>在架</p><span><i>15000</i></span></li>
<li><p>错架</p><span><i>300</i></span> <span class="percentage">2.00%</span></li>
<li><p>错序</p><span><i>0</i></span><span class="percentage">0.00%</span></li>
</ul>`
} else if (self.pagePreview === 'region') {
document.getElementById('tooltip').innerHTML =
`<div class="tooltip-top">
<h4>书架概况</h4>
<span class="update-time">2024-11-28 09:46</span>
</div>
<ul>
<li><p>书架</p><span><i>${this.tooltipInfo.name}</i></span></li>
<li><p>规则</p><span><i>双面6*8</i></span></li>
<li><p>在架</p><span><i>15000</i></span></li>
<li><p>错架</p><span><i>300</i></span> <span class="percentage">2.00%</span></li>
<li><p>错序</p><span><i>0</i></span><span class="percentage">0.00%</span></li>
</ul>`
}
var rectLeft = e.target.left + e.target.width - 100
var rectTop = e.target.top + e.target.height - 40
document.getElementById('tooltip').style.left = rectLeft + 'px'
document.getElementById('tooltip').style.top = rectTop + 'px'
document.getElementById('tooltip').style.display = 'block'
self.canvasPreview.renderAll()
})
polygon.on('mouseout', function() {
this.set({ opacity: 1 })
document.getElementById('tooltip').style.display = 'none'
self.canvasPreview.renderAll()
})
}
})
})
self.canvasPreview.renderAll()
},
handleToRegions(data) {
this.$router.push({ path: '/dataScreening/regions' })
localStorage.setItem('dataScreenFloor', JSON.stringify(data))
},
handleToShelfs(data) {
this.$router.push({ path: '/dataScreening/shelf' })
localStorage.setItem('dataScreenRegion', JSON.stringify(data))
}
}
}
</script>
<style>
.tooltip-style{
display:none;
position:absolute;
width: 300px;
background:rgba(0,0,0,.6);
color: #fff;
border-radius: 6px;
}
.tooltip-top{
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
line-height: 40px;
padding: 0 10px;
border-bottom: 1px solid #fff;
}
.tooltip-top span{
font-size: 12px;
}
#tooltip ul{
padding: 10px;
}
#tooltip ul li{
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 36px;
font-style: normal;
}
#tooltip ul li p{
width: 80px;
font-weight: bold;
text-align: right;
}
#tooltip ul li span{
width: 100px;
display: block;
text-align: right;
}
#tooltip ul li i{
font-style: normal;
font-weight: bold;
padding: 0 10px;
color: #0348f3;
}
#tooltip ul li span.percentage{
width: auto;
}
</style>
<style lang="scss" scoped>
#expImg{
display: none;
}
.venue-preview{
background-color: #e8f2ff;
}
</style>

61
src/views/components/hkVideo.vue

@ -0,0 +1,61 @@
<template>
<div style="width: 100%; height: 100%; overflow: hidden;">
<!-- <h1>海康威视视频流</h1> -->
<!-- <h3>400W</h3>
<VideoCom :rtsp="rtspurl1" ref="video1"></VideoCom>
<h3>800W</h3>
<VideoCom :rtsp="rtspurl2" ref="video2"></VideoCom> -->
<video id="video" controls autoplay muted width="100%" height="100%" />
</div>
</template>
<script>
// import VideoCom from '../components/video'
// rtsp://admin:ftzn83560792@192.168.99.23:554/h264/ch1/main/av_stream
// rtsp://admin:ftzn83560792@192.168.99.26:554/Streaming/Channels/101
export default {
name: 'Home',
components: {
// VideoCom
},
props: {
hkConfig: {
type: Object,
required: true
}
},
data() {
return {
webRtcServer: null,
camera_ip: '127.0.0.1:8000'
}
},
created() {
// this.controlVolume()
},
mounted() {
// videovideoID
// 127.0.0.1:8000webrtc-streamerIP8000
this.webRtcServer = new WebRtcStreamer('video', location.protocol + '//' + this.camera_ip)
// rtsp,rtspH264
this.webRtcServer.connect('rtsp://' + this.hkConfig.username + ':' + this.hkConfig.password + '@' + this.hkConfig.ip + ':' + this.hkConfig.port + '/h264/ch1/main/av_stream')
},
//
beforeDestroy() {
this.webRtcServer.disconnect()
this.webRtcServer = null
},
methods: {
controlVolume() {
this.$nextTick(() => {
this.$refs.video1.volume = 0
this.$refs.video2.volume = 0
})
}
}
}
</script>
<style lang="scss">
</style>

14
src/views/home.vue

@ -74,7 +74,8 @@
<h3 class="home-item-title">
流通统计
</h3>
<div class="chart-wrapper">
<div class="refresh-date">2024-11-28 09:46</div>
<div class="chart-wrapper" style="padding: 0 10px; margin-top: -10px;">
<bookSwiper />
</div>
</div>
@ -104,7 +105,7 @@
<script>
import PanelGroup from './dashboard/PanelGroup'
import lendAcross from '@/views/components/echarts/lendAcross.vue'
import catePie from '@/views/components/echarts/catePie.vue'
// import catePie from '@/views/components/echarts/catePie.vue'
import typePie from '@/views/components/echarts/typePie.vue'
import bookSwiper from '@/views/components/bookSwiper.vue'
import serverProgress from '@/views/components/echarts/serverProgress.vue'
@ -117,7 +118,7 @@ export default {
components: {
PanelGroup,
lendAcross,
catePie,
// catePie,
bookSwiper,
typePie,
serverProgress
@ -390,4 +391,11 @@ export default {
::v-deep .home-flowable-list .el-table__body-wrapper::-webkit-scrollbar-corner {
background-color: #DDE8FB !important;
}
.refresh-date{
position: absolute;
right: 14px;
top: 10px;
font-size: 12px;
line-height: 30px;
}
</style>

88
src/views/visualCheck/bookstore/collection/index.vue

@ -42,6 +42,7 @@
<i class="iconfont icon-daochu" />
导出
</el-button>
<el-button type="primary" class="warehousing-btn iconfont" :disabled="crud.selections.length === 0"><i class="iconfont icon-xinzeng" />批量导入数据</el-button>
<el-button type="primary" class="warehousing-btn iconfont" :disabled="crud.selections.length === 0" @click="printArchivesCode(crud.selections)"><svg-icon icon-class="print" class="svg-arc-style" />批量打印条形码</el-button>
</template>
</crudOperation>
@ -126,7 +127,7 @@
<el-table
ref="table"
v-loading="crud.loading"
height="645"
height="586"
:data="crud.data"
@selection-change="crud.selectionChangeHandler"
@row-click="clickRowHandler"
@ -135,13 +136,13 @@
<el-table-column label="条码" prop="barcode" />
<el-table-column label="[ISBN]题名" prop="isbn">
<template slot-scope="scope">
<p>[{{ scope.row.isbn }}]{{ scope.row.collectionName }}</p>
<p>[{{ scope.row.isbn }}]{{ scope.row.bookName }}</p>
</template>
</el-table-column>
<el-table-column prop="floor" label="所在楼层" />
<el-table-column prop="area" label="所在区域" />
<el-table-column prop="actualShelfId" label="所在架位" />
<el-table-column prop="actualLocation" label="所属馆藏" />
<el-table-column prop="floorName" label="所在楼层" />
<el-table-column prop="regionName" label="所在区域" />
<el-table-column prop="gridName" label="所在架位" />
<el-table-column prop="collectionName" label="所属馆藏" />
<el-table-column prop="createTime" label="创建日期" :show-overflow-tooltip="true">
<template slot-scope="scope">
<div>{{ scope.row.createTime | parseTime }}</div>
@ -175,8 +176,7 @@ import crudOperation from '@crud/CRUD.operation'
import pagination from '@crud/Pagination'
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
import { exportFile } from '@/utils/index'
import { parseTime, saveAs, getBlob } from '@/utils/index'
import qs from 'qs'
import { mapGetters } from 'vuex'
import JsBarcode from 'jsbarcode'
@ -340,6 +340,7 @@ export default {
})
},
[CRUD.HOOK.beforeRefresh]() {
this.crud.query[this.optionVal] = this.keyWord
},
//
[CRUD.HOOK.afterToCU](crud, form) {
@ -354,6 +355,10 @@ export default {
},
//
[CRUD.HOOK.beforeToEdit](crud, form) {
console.log('crud', crud.form)
this.selectShelfVal = crud.form.actualShelfId
this.toIsbnSearch()
this.getShelfGridAllByShelfId()
},
//
[CRUD.HOOK.afterValidateCU](crud) {
@ -381,7 +386,6 @@ export default {
},
toIsbnSearch() {
if (this.form.isbn) {
console.log(this.form.isbn)
FetchBookBasiceByISBN({ 'isbn': this.form.isbn }).then(res => {
console.log(res)
if (res) {
@ -413,18 +417,7 @@ export default {
})
},
changeLibrarysValue(value) {
// this.depts = []
// var obj = {}
// obj = this.fondsOptions.find(function(item) {
// return item.id === value
// })
// this.form.deptsParentsId = null
// const params = {
// 'fondsId': obj.id,
// 'fondsName': obj.fondsName,
// 'status': 1
// }
console.log(value)
},
getCollectionLocationAll() {
FetchCollectionLocationAll().then(res => {
@ -432,35 +425,11 @@ export default {
this.librarysOptions = res
})
},
//
loadDepts({ action, parentNode, callback }) {
if (action === LOAD_CHILDREN_OPTIONS) {
// crudDept.FetchSonDepts({ deptsId: parentNode.deptsId }).then(res => {
// parentNode.children = res.map(function(obj) {
// if (obj.sonNum !== 0) {
// obj.hasChildren = true
// } else {
// obj.hasChildren = false
// }
// if (obj.hasChildren) {
// obj.children = null
// }
// return obj
// })
// setTimeout(() => {
// callback()
// }, 100)
// })
}
},
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)
},
toDelete(datas) {
this.deleteData = datas
this.$confirm('此操作将删除当前所选图书所有馆藏信息<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
@ -469,17 +438,17 @@ export default {
}).then(() => {
this.crud.delAllLoading = true
const ids = []
this.deleteData.forEach(val => {
ids.push(val.deptsId)
datas.forEach(val => {
ids.push(val.id)
})
crudCollBook.del(ids).then(() => {
this.$message({ message: '删除成功', type: 'success', offset: 8 })
this.crud.delAllLoading = false
this.crud.refresh()
}).catch(err => {
this.crud.delAllLoading = false
console.log(err)
})
// crudDept.del(ids).then(() => {
// this.$message({ message: '', type: 'success', offset: 8 })
// this.crud.delAllLoading = false
// this.crud.refresh()
// }).catch(err => {
// this.crud.delAllLoading = false
// console.log(err)
// })
}).catch(() => {
})
},
@ -493,12 +462,15 @@ export default {
}).then(() => {
const ids = []
data.forEach(val => {
ids.push(val.fondsId)
ids.push(val.id)
})
const params = {
'fondsIds': ids
'ids': ids
}
exportFile(this.baseApi + '/api/fonds/download?' + qs.stringify(params, { indices: false }))
const fileName = '图书馆藏-' + parseTime(new Date()) + '.xlsx'
getBlob(this.baseApi + '/api/bookBasice/exportBookDetailsByIds' + '?' + qs.stringify(params, { indices: false }), function(blob) {
saveAs(blob, fileName)
})
}).catch(() => {
})
},

293
src/views/visualCheck/checkManage/dataScreening/girdList.vue

@ -0,0 +1,293 @@
<template>
<div class="app-container">
<div class="venue-header">
<h4><i class="iconfont icon-shuju" />架位总览</h4>
<p><i class="iconfont icon-gongsi" />{{ user.fonds.fondsName }}</p>
</div>
<div class="venue-content">
<crudOperation :permission="permission">
<template v-slot:middle>
<el-button v-permission="permission.add" size="mini" @click="crud.toAdd">
<i class="iconfont icon-shengchengpandiandan" />
架位盘点
</el-button>
</template>
<template v-slot:right>
<el-button :loading="crud.downloadLoading" size="mini" @click="doExport(crud.selections)">
<i class="iconfont icon-daochu" />
导出
</el-button>
</template>
</crudOperation>
<div class="venue-left">
<div class="container-right tab-content">
<span class="right-top-line" />
<span class="left-bottom-line" />
<ul class="tab-nav">
<li v-for="(item,index) in floorOptions" :key="index" :class="{ 'active-tab-nav': tabIndex == index }" @click="changeActiveTab(index)">{{ item.floorName }}<i /></li>
<!-- 最右侧装饰img -->
<span class="tab-right-img" />
</ul>
</div>
</div>
<div class="venue-right" />
<!-- form -->
<el-dialog append-to-body :close-on-click-modal="false" :modal-append-to-body="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="盘点单号" prop="taskName">
<el-input v-model="form.taskName" />
</el-form-item>
<el-form-item label="盘点类型" prop="taskType">
<el-input v-model="form.taskType" />
</el-form-item>
<el-form-item label="目标位置" prop="location">
<el-input v-model="form.location" />
</el-form-item>
<el-form-item label="目标数量" prop="number">
<el-input v-model="form.number" />
</el-form-item>
<el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" style="width: 572px;" :rows="4" />
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">保存</el-button>
</div>
</div>
</el-dialog>
</div>
</div></template>
<script>
import { FetchLibraryFloorListAll } from '@/api/floor/index'
import { FetchInitLibraryRegionList } from '@/api/area/index'
import crudRegion from '@/api/area/index'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import { mapGetters } from 'vuex'
import defaultImg from '@/assets/images/system/default-img.jpg'
const defaultForm = { id: null, taskType: null, taskName: null, location: null, number: null, remark: null }
export default {
name: 'DataScreening',
components: { crudOperation },
cruds() {
return CRUD({ title: '数据总览', url: 'api/libraryRegion/initLibraryRegionList', crudMethod: { ...crudRegion }, sort: [], optShow: {
add: false,
edit: false,
del: false,
download: false,
group: false,
reset: false
}})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
const _this = this
return {
floorOptions: [],
tabIndex: 0,
defaultImg: defaultImg,
imageUrl: defaultImg,
imageRegionUrl: defaultImg,
currentMarkData: null,
allCoverData: [],
swiperActiveIndex: 0,
rightDataIndex: null,
swiperOptionContent: {
slidesPerView: 'auto',
on: {
slideChangeTransitionStart: function() {
_this.rightDataIndex = null
_this.swiperActiveIndex = this.activeIndex
_this.swiperTitle.slideTo(this.activeIndex, 500, false)
}
}
},
swiperOptionTitle: {
slidesPerView: 'auto',
freeMode: true
},
tabListData: [{ name: '热门图书' }, { name: '热门架位' }, { name: '冷面图书' }],
permission: {
add: ['admin', 'floor:add'],
edit: ['admin', 'floor:edit'],
del: ['admin', 'floor:del']
},
rules: {
taskName: [
{ required: true, message: '请输入盘点单号', trigger: 'blur' }
],
taskType: [
{ required: true, message: '请输入盘点类型', trigger: 'blur' }
],
location: [
{ required: true, message: '请输入目标位置', trigger: 'blur' }
],
number: [
{ required: true, message: '请输入目标数量', trigger: 'blur' }
]
}
}
},
computed: {
...mapGetters([
'user',
'baseApi'
]),
swiperContent() {
return this.$refs.swiperContent.$el.swiper
}
},
methods: {
[CRUD.HOOK.beforeRefresh]() {
this.getLibraryFloorListAll()
},
[CRUD.HOOK.afterRefresh](crud) {
},
//
[CRUD.HOOK.afterValidateCU](crud) {
return true
},
//
getLibraryFloorListAll() {
FetchLibraryFloorListAll().then(res => {
this.floorOptions = res
this.changeActiveTab(this.tabIndex)
}).catch(() => {
})
},
changeActiveTab(index) {
this.tabIndex = index
const params = {
'floorId': this.floorOptions[index].id
}
FetchInitLibraryRegionList(params).then(res => {
console.log(res)
this.allCoverData = res.content
if (this.allCoverData.length !== 0) {
this.currentMarkData = this.allCoverData[0]
if (this.allCoverData[0].floorMap) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.allCoverData[0].floorMap
} else {
this.imageUrl = this.defaultImg
}
if (this.tabIndex === 0) {
if (this.allCoverData[0].signPoint) {
const drawinfo = JSON.parse(this.allCoverData[0].signPoint)
this.$nextTick(() => {
this.$refs.previewRefs.initCanvasPreview(drawinfo)
})
}
}
} else {
this.currentMarkData = {}
if (this.floorOptions[index].floorMap) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.floorOptions[index].floorMap
} else {
this.imageUrl = this.defaultImg
}
}
}).catch(() => {
})
},
handleSlidClickFun(index) {
this.rightDataIndex = null
this.handleSlideToFun(index)
},
handleSlideToFun(index) {
this.swiperActiveIndex = index
this.swiperContent.slideTo(index, 500, false)
this.swiperTitle.slideTo(index, 500, false)
}
}
}
</script>
<style lang="scss" scoped>
.container-right{
min-height: calc(100vh - 232px) !important;
}
.venue-content{
position: relative;
}
.crud-opts{
position: absolute;
right: 20px;
top: 10px;
}
.venue-left{
flex: 1;
margin-right: 0 !important;
.venue-preview{
height: 633px !important;
}
}
.venue-right{
display: flex;
flex-direction: column;
width: 400px;
padding: 50px 10px 20px 10px !important;
.lib-right-item{
position: relative;
// padding: 10px;
// height: calc(100% / 3);
padding-bottom: 10px;
margin-bottom: 10px;
border: 1px solid #E8F2FF;
border-radius: 4px;
h4{
padding: 6px 10px;
background-color: #E8F2FF;
color: #000;
line-height: 30px;
border-bottom: 1px solid #edeff3;
}
.refresh-date{
position: absolute;
right: 14px;
top: 10px;
font-size: 12px;
line-height: 30px;
}
}
}
.data-right-list {
padding-top: 10px;
li{
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 36px;
p{
width: 80px;
font-weight: bold;
text-align: right;
}
span{
width: 140px;
display: block;
text-align: right;
i{
font-style: normal;
font-weight: bold;
padding: 0 10px;
color: #0348f3;
}
&.percentage{
width: auto;
}
}
}
}
</style>

320
src/views/visualCheck/checkManage/dataScreening/index.vue

@ -1,7 +1,7 @@
<template>
<div class="app-container">
<div class="venue-header">
<h4><i class="iconfont icon-shuju" />数据总览</h4>
<h4 @click="handleToRegions"><i class="iconfont icon-shuju" />数据总览</h4>
<p><i class="iconfont icon-gongsi" />{{ user.fonds.fondsName }}</p>
</div>
<div class="venue-content">
@ -24,16 +24,17 @@
<span class="right-top-line" />
<span class="left-bottom-line" />
<ul class="tab-nav">
<li v-for="(item,index) in floorOptions" :key="index" :class="{ 'active-tab-nav': activeIndex == index }" @click="changeActiveTab(index)">{{ item.floorName }}<i /></li>
<li v-for="(item,index) in floorOptions" :key="index" :class="{ 'active-tab-nav': tabIndex == index }" @click="changeActiveTab(index)">{{ item.floorName }}<i /></li>
<!-- 最右侧装饰img -->
<span class="tab-right-img" />
</ul>
<div class="venue-preview">
<div v-show="currentMarkData && currentMarkData.signPoint">
<canvas id="canvasPreview" :width="width" :height="height" />
</div>
<img v-if="currentMarkData && !currentMarkData.signPoint" :src="imageUrl" :onerror="defaultImg" alt="">
<div class="total-data">
<span>楼层概况</span>
<p>区域1</p>
<p>书架4</p>
<p>摄像头12</p>
</div>
<CanvasPreview ref="previewRefs" page-preview="floor" :current-mark-data="currentMarkData" :image-url="imageUrl" />
</div>
</div>
<div class="venue-right">
@ -58,52 +59,7 @@
<div class="lib-right-item">
<h4>流通统计</h4>
<div class="refresh-date">2024-11-28 09:46</div>
<swiper
ref="swiperTitle"
class="swiper-title"
:options="swiperOptionTitle"
:auto-update="true"
:auto-destroy="true"
:delete-instance-on-destroy="true"
:cleanup-styles-on-destroy="true"
>
<swiper-slide
v-for="(item, index) of tabListData"
ref="swiperSlideItem"
:key="'name' + index"
:iname="item.name"
class="swiper-slide-title"
>
<div
class="tab-name"
:class="{ active: index === swiperActiveIndex }"
@click="handleSlidClickFun(index)"
>
{{ item.name }}
</div>
</swiper-slide>
</swiper>
<swiper
ref="swiperContent"
class="swiper-content"
:options="swiperOptionContent"
:auto-update="true"
:auto-destroy="true"
:delete-instance-on-destroy="true"
:cleanup-styles-on-destroy="true"
>
<swiper-slide
v-for="(item, index) of tabListData"
:key="'content' + index"
class="swiper-slide-content"
>
<ul class="cabinet-row">
<li v-for="(item,index) in bookList" :key="index" :class="{ active: index === rightDataIndex }">
<span>{{ item }}</span>
</li>
</ul>
</swiper-slide>
</swiper>
<bookSwiper />
</div>
</div>
</div>
@ -142,22 +98,21 @@
</template>
<script>
import { FetchLibraryFloorListAll } from '@/api/floor/index'
import crudRegion from '@/api/area/index'
import { FetchInitLibraryRegionList } from '@/api/area/index'
import crudFloor from '@/api/floor/index'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import { mapGetters } from 'vuex'
import defaultImg from '@/assets/images/system/default-img.jpg'
import { fabric } from 'fabric'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
import bookSwiper from '@/views/components/bookSwiper.vue'
import CanvasPreview from '@/views/components/canvasPreview.vue'
const defaultForm = { id: null, taskType: null, taskName: null, location: null, number: null, remark: null }
export default {
name: 'DataScreening',
components: { swiper, swiperSlide, crudOperation },
components: { crudOperation, bookSwiper, CanvasPreview },
cruds() {
return CRUD({ title: '数据总览', url: 'api/libraryRegion/initLibraryRegionList', crudMethod: { ...crudRegion }, sort: [], optShow: {
return CRUD({ title: '数据总览', url: 'api/libraryFloor/initLibraryFloorList', crudMethod: { ...crudFloor }, sort: [], optShow: {
add: false,
edit: false,
del: false,
@ -171,6 +126,12 @@ export default {
const _this = this
return {
floorOptions: [],
tabIndex: 0,
defaultImg: defaultImg,
imageUrl: defaultImg,
imageRegionUrl: defaultImg,
currentMarkData: null,
allCoverData: [],
swiperActiveIndex: 0,
rightDataIndex: null,
swiperOptionContent: {
@ -188,7 +149,6 @@ export default {
freeMode: true
},
tabListData: [{ name: '热门图书' }, { name: '热门架位' }, { name: '冷面图书' }],
bookList: [],
permission: {
add: ['admin', 'floor:add'],
edit: ['admin', 'floor:edit'],
@ -207,21 +167,7 @@ export default {
number: [
{ required: true, message: '请输入目标数量', trigger: 'blur' }
]
},
activeIndex: 0,
defaultImg: defaultImg,
imageUrl: defaultImg,
imageRegionUrl: defaultImg,
sortTableData: [], // data
sortVisible: false, // dialog
markVisible: false, //
titleMark: '区域标注',
currentMarkData: null,
canvasPreview: {},
width: 900,
height: 600,
drawWidth: 2 //
}
}
},
computed: {
@ -233,71 +179,58 @@ export default {
return this.$refs.swiperContent.$el.swiper
}
},
watch: {
width() {
this.canvasPreview.setWidth(this.width)
},
height() {
this.canvasPreview.setHeight(this.height)
}
},
methods: {
[CRUD.HOOK.beforeRefresh]() {
this.getLibraryFloorListAll()
},
[CRUD.HOOK.afterRefresh](crud) {
if (crud.data.length !== 0) {
this.activeIndex = 0
} else {
this.imageUrl = this.defaultImg
this.imageRegionUrl = this.defaultImg
}
this.floorOptions = crud.data
this.changeActiveTab(this.tabIndex)
},
//
[CRUD.HOOK.afterValidateCU](crud) {
return true
},
//
getLibraryFloorListAll() {
FetchLibraryFloorListAll().then(res => {
this.floorOptions = res
}).catch(() => {
})
},
handleCover(value) {
console.log(value)
this.crud.form.regionMap = value
},
changeFloorValue(value) {
console.log(value)
},
changeActiveTab(data) {
this.activeIndex = data
if (this.crud.selections.length === 1) {
// if (this.canvasPreview) {
// this.canvasPreview.clear()
// this.canvasPreview.dispose()
// }
if (this.crud.selections[0].floorMap) {
this.currentMarkData = this.crud.selections[0]
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.crud.selections[0].floorMap
changeActiveTab(index) {
this.tabIndex = index
const params = {
'floorId': this.floorOptions[index].id
}
FetchInitLibraryRegionList(params).then(res => {
console.log(res)
this.allCoverData = res.content
if (this.floorOptions[index].floorMap) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.floorOptions[index].floorMap
} else {
this.imageUrl = this.defaultImg
}
if (this.crud.selections[0].regionMap) {
this.imageRegionUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.crud.selections[0].regionMap
} else {
this.imageRegionUrl = this.defaultImg
}
if (this.activeIndex === 0) {
if (this.crud.selections[0].signPoint) {
console.log('1111')
const drawinfo = JSON.parse(this.crud.selections[0].signPoint)
this.initCanvasPreview(drawinfo)
if (this.allCoverData.length !== 0) {
this.currentMarkData = this.allCoverData[0]
const parsedSignPoints = this.allCoverData.map(item => {
const signPoint = JSON.parse(item.signPoint)
return {
id: item.id,
name: item.regionName,
floorId: item.floorId,
pointInfo: signPoint.pointInfo[0].pointInfo
}
})
const imgInfo = JSON.parse(this.allCoverData[0].signPoint).imgInfo
const result = {
pointInfo: parsedSignPoints,
imgInfo: imgInfo
}
this.$nextTick(() => {
this.$refs.previewRefs.initCanvasPreview(result)
})
} else {
this.currentMarkData = {}
}
}
}).catch(() => {
})
},
handleToRegions() {
this.$router.push({ path: '/dataScreening/regions', query: { }})
},
handleSlidClickFun(index) {
this.rightDataIndex = null
@ -307,96 +240,6 @@ export default {
this.swiperActiveIndex = index
this.swiperContent.slideTo(index, 500, false)
this.swiperTitle.slideTo(index, 500, false)
},
async handleMark() {
if (this.crud.selections[0].floorMap) {
this.markVisible = true
this.currentMarkData = this.crud.selections[0]
this.titleMark = this.currentMarkData.regionName + ' - 区域标注'
this.$nextTick(() => {
this.$refs.markRefs.drawinfo = this.currentMarkData && this.currentMarkData.signPoint ? JSON.parse(this.currentMarkData.signPoint) : null
this.$refs.markRefs.initCanvas()
})
} else {
this.$message({ message: '请先上传当前楼层图', type: 'error', offset: 8 })
}
},
initCanvasPreview(drawinfo) {
this.canvasPreview = new fabric.Canvas('canvasPreview', {
skipTargetFind: false,
selectable: false,
selection: false
})
this.canvasPreview.selectionColor = 'rgba(0,0,0,0.05)'
this.loadDrawPreview(drawinfo)
this.canvasPreview.on('mouse:wheel', this.mouse)
},
//
mouse(e) {
if (undefined === e) return
let zoom = (e.e.deltaY > 0 ? -0.1 : 0.1) + this.canvasPreview.getZoom()
zoom = Math.max(0.8, zoom)
// 1/10
zoom = Math.min(3, zoom)
// 3
const zoomPoint = new fabric.Point(e.e.pageX, e.e.pageY)
this.canvasPreview.zoomToPoint(zoomPoint, zoom)
},
//
loadDrawPreview(drawinfo) {
const self = this
const pointGroup = drawinfo.pointInfo
const imgInfo = drawinfo.imgInfo
imgInfo.src = self.imageUrl
//
fabric.util.enlivenObjects([imgInfo], objects => {
objects.forEach(o => {
o.selectable = false
o.hasControls = false
o.centeredScaling = false
self.canvasPreview.add(o)
})
//
pointGroup.forEach(async(item, index) => {
if (item.pointInfo !== '') {
const polygon = new fabric.Polygon(item.pointInfo, {
name: item.name,
stroke: 'rgba(196,43, 1, 1)',
strokeWidth: self.drawWidth,
fill: 'rgba(196,43, 1, 0.3)',
opacity: 1,
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'left', //
originY: 'top' //
})
// polygon.index = index
self.canvasPreview.add(polygon)
polygon.on('mousedown', function(e) {
console.log('Rect ' + (index + 1) + ' clicked', e)
console.log('e.target.name', e.target.name)
})
polygon.on('mouseover', function(e) {
console.log('e', e)
console.log('e.target', e.target)
console.log('e.target.name', e.target.name)
this.set({ opacity: 0.3, hoverCursor: 'pointer' })
self.canvasPreview.renderAll()
})
//
polygon.on('mouseout', function() {
this.set({ opacity: 1 })
self.canvasPreview.renderAll()
})
}
})
})
self.canvasPreview.renderAll()
}
}
}
@ -428,12 +271,15 @@ export default {
padding: 50px 10px 20px 10px !important;
.lib-right-item{
position: relative;
padding: 10px;
// padding: 10px;
// height: calc(100% / 3);
padding-bottom: 10px;
margin-bottom: 10px;
border: 1px solid #0348f3;
border: 1px solid #E8F2FF;
border-radius: 4px;
h4{
padding: 6px 10px;
background-color: #E8F2FF;
color: #000;
line-height: 30px;
border-bottom: 1px solid #edeff3;
@ -477,30 +323,20 @@ export default {
}
}
}
.swiper-title{
::v-deep .swiper-wrapper{
margin: 10px 0;
border-bottom: 1px solid #EDEFF3;
.total-data{
display: flex;
justify-content: flex-start;
align-items: center;
position: absolute;
right: 12px;
top: 60px;
font-size: 14px;
padding: 4px 6px;
color: #fff;
background-color: rgba(0,0,0,.5);
border-radius: 4px;
p{
margin-left: 10px;
}
}
.swiper-slide-title {
width: auto !important;
margin-right: 20px;
cursor: pointer;
.tab-name {
padding: 10px;
&.active {
color: #0348F3;
border-bottom: 3px solid #0348F3;
}
}
}
.swiper-content{
// height: 544px;
}
.swiper-slide-content {
// padding: 0 10px;
// margin: 0 10px 0 0;
}
</style>

331
src/views/visualCheck/checkManage/dataScreening/regionsList.vue

@ -0,0 +1,331 @@
<template>
<div class="app-container">
<div class="venue-header">
<h4 @click="handleToShelfs"><i class="iconfont icon-shuju" />区域总览</h4>
<p><i class="iconfont icon-gongsi" />{{ user.fonds.fondsName }}</p>
</div>
<div class="venue-content">
<crudOperation :permission="permission">
<template v-slot:middle>
<el-button v-permission="permission.add" size="mini" @click="crud.toAdd">
<i class="iconfont icon-shengchengpandiandan" />
区域盘点
</el-button>
</template>
<template v-slot:right>
<el-button :loading="crud.downloadLoading" size="mini" @click="doExport(crud.selections)">
<i class="iconfont icon-daochu" />
导出
</el-button>
</template>
</crudOperation>
<div class="venue-left">
<div class="container-right tab-content">
<span class="right-top-line" />
<span class="left-bottom-line" />
<ul class="tab-nav">
<li v-for="(item,index) in regionOptions" :key="index" :class="{ 'active-tab-nav': tabIndex == index }" @click="changeActiveTab(index)">{{ item.regionName }}<i /></li>
<!-- 最右侧装饰img -->
<span class="tab-right-img" />
</ul>
<CanvasPreview ref="previewRefs" page-preview="region" :current-mark-data="currentMarkData" :image-url="imageUrl" />
</div>
</div>
<div class="venue-right">
<div class="lib-right-item lib-info">
<h4>本区概况</h4>
<ul class="data-right-list">
<li><p>书架</p><span><i>100</i></span></li>
<li><p>摄像头</p><span><i>300</i></span></li>
</ul>
</div>
<div class="lib-right-item">
<h4>本区盘点概况</h4>
<div class="refresh-date">2024-11-28 09:46</div>
<ul class="data-right-list">
<li><p>在架</p><span><i>15000</i></span></li>
<li><p>错架</p><span><i>300</i></span> <span class="percentage">2.00%</span></li>
<li><p>错序</p><span><i>0</i></span><span class="percentage">0.00%</span></li>
</ul>
</div>
<div class="lib-right-item">
<h4>本区流通统计</h4>
<div class="refresh-date">2024-11-28 09:46</div>
<bookSwiper />
</div>
</div>
</div>
<!-- form -->
<el-dialog append-to-body :close-on-click-modal="false" :modal-append-to-body="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="盘点单号" prop="taskName">
<el-input v-model="form.taskName" />
</el-form-item>
<el-form-item label="盘点类型" prop="taskType">
<el-input v-model="form.taskType" />
</el-form-item>
<el-form-item label="目标位置" prop="location">
<el-input v-model="form.location" />
</el-form-item>
<el-form-item label="目标数量" prop="number">
<el-input v-model="form.number" />
</el-form-item>
<el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" style="width: 572px;" :rows="4" />
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">保存</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import crudRegion from '@/api/area/index'
import { FetchInitBookShelfList } from '@/api/shelf/index'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import { mapGetters } from 'vuex'
import defaultImg from '@/assets/images/system/default-img.jpg'
import bookSwiper from '@/views/components/bookSwiper.vue'
import CanvasPreview from '@/views/components/canvasPreview.vue'
const defaultForm = { id: null, taskType: null, taskName: null, location: null, number: null, remark: null }
export default {
name: 'DataScreening',
components: { crudOperation, bookSwiper, CanvasPreview },
cruds() {
return CRUD({ title: '区域总览', url: 'api/libraryRegion/initLibraryRegionList', crudMethod: { ...crudRegion }, sort: [], optShow: {
add: false,
edit: false,
del: false,
download: false,
group: false,
reset: false
}
})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
const _this = this
return {
regionOptions: [],
currentRegionId: null,
tabIndex: 0,
defaultImg: defaultImg,
imageUrl: defaultImg,
imageRegionUrl: defaultImg,
currentMarkData: null,
allCoverData: [],
swiperActiveIndex: 0,
rightDataIndex: null,
swiperOptionContent: {
slidesPerView: 'auto',
on: {
slideChangeTransitionStart: function() {
_this.rightDataIndex = null
_this.swiperActiveIndex = this.activeIndex
_this.swiperTitle.slideTo(this.activeIndex, 500, false)
}
}
},
swiperOptionTitle: {
slidesPerView: 'auto',
freeMode: true
},
tabListData: [{ name: '热门图书' }, { name: '热门架位' }, { name: '冷面图书' }],
permission: {
add: ['admin', 'floor:add'],
edit: ['admin', 'floor:edit'],
del: ['admin', 'floor:del']
},
rules: {
taskName: [
{ required: true, message: '请输入盘点单号', trigger: 'blur' }
],
taskType: [
{ required: true, message: '请输入盘点类型', trigger: 'blur' }
],
location: [
{ required: true, message: '请输入目标位置', trigger: 'blur' }
],
number: [
{ required: true, message: '请输入目标数量', trigger: 'blur' }
]
}
}
},
computed: {
...mapGetters([
'user',
'baseApi'
]),
swiperContent() {
return this.$refs.swiperContent.$el.swiper
}
},
methods: {
handleToShelfs() {
this.$router.push({ path: '/dataScreening/shelf', query: { }})
},
[CRUD.HOOK.beforeRefresh]() {
const data = JSON.parse(localStorage.getItem('dataScreenFloor'))
this.crud.query.floorId = data.floorId
this.currentRegionId = data.id
},
[CRUD.HOOK.afterRefresh](crud) {
this.regionOptions = crud.data
const item = this.regionOptions.find(element => element.id === this.currentRegionId)
const index = item ? this.regionOptions.indexOf(item) : -1
this.tabIndex = index
this.changeActiveTab(index)
},
//
[CRUD.HOOK.afterValidateCU](crud) {
return true
},
changeActiveTab(index) {
this.tabIndex = index
const params = {
'floorId': this.regionOptions[index].floorId,
'regionId': this.regionOptions[index].id
}
FetchInitBookShelfList(params).then(res => {
console.log(res)
this.allCoverData = res.content
if (this.regionOptions[index].regionMap) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.regionOptions[index].regionMap
} else {
this.imageUrl = this.defaultImg
}
if (this.allCoverData.length !== 0) {
this.currentMarkData = this.regionOptions[index]
const signPoint = this.allCoverData.find(item => item.signPoint !== null)?.signPoint
const imgInfo = signPoint ? JSON.parse(signPoint).imgInfo : null
const parsedSignPoints = this.allCoverData.map(item => {
const signPoint = item.signPoint ? JSON.parse(item.signPoint) : null
return {
id: item.shelfId,
name: item.shelfName,
rowType: item.rowType,
toward: item.toward,
floorName: item.floorName,
regionName: item.regionName,
pointInfo: signPoint ? signPoint.pointInfo[0].pointInfo : null
}
})
const result = {
pointInfo: parsedSignPoints,
imgInfo: imgInfo
}
this.$nextTick(() => {
this.$refs.previewRefs.initCanvasPreview(result)
})
} else {
this.currentMarkData = {}
}
}).catch(() => {
})
},
handleSlidClickFun(index) {
this.rightDataIndex = null
this.handleSlideToFun(index)
},
handleSlideToFun(index) {
this.swiperActiveIndex = index
this.swiperContent.slideTo(index, 500, false)
this.swiperTitle.slideTo(index, 500, false)
}
}
}
</script>
<style lang="scss" scoped>
.container-right{
min-height: calc(100vh - 232px) !important;
}
.venue-content{
position: relative;
}
.crud-opts{
position: absolute;
right: 20px;
top: 10px;
}
.venue-left{
flex: 1;
margin-right: 0 !important;
.venue-preview{
height: 633px !important;
}
}
.venue-right{
display: flex;
flex-direction: column;
width: 400px;
padding: 50px 10px 20px 10px !important;
.lib-right-item{
position: relative;
// padding: 10px;
// height: calc(100% / 3);
padding-bottom: 10px;
margin-bottom: 10px;
border: 1px solid #E8F2FF;
border-radius: 4px;
h4{
padding: 6px 10px;
background-color: #E8F2FF;
color: #000;
line-height: 30px;
border-bottom: 1px solid #edeff3;
}
.refresh-date{
position: absolute;
right: 14px;
top: 10px;
font-size: 12px;
line-height: 30px;
}
}
}
.data-right-list {
padding-top: 10px;
li{
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 36px;
p{
width: 80px;
font-weight: bold;
text-align: right;
}
span{
width: 140px;
display: block;
text-align: right;
i{
font-style: normal;
font-weight: bold;
padding: 0 10px;
color: #0348f3;
}
&.percentage{
width: auto;
}
}
}
}
</style>

672
src/views/visualCheck/checkManage/dataScreening/shelfList.vue

@ -0,0 +1,672 @@
<template>
<div class="app-container">
<div class="venue-header">
<h4 @click="handleToGrids"><i class="iconfont icon-shuju" />书架总览</h4>
<span class="bookshelf-area">{{ floorName }} - {{ regionName }}</span>
<p><i class="iconfont icon-gongsi" />{{ user.fonds.fondsName }}</p>
</div>
<div class="venue-content">
<crudOperation :permission="permission">
<template v-slot:middle>
<el-select v-model="layerVal" clearable size="small" placeholder="楼层" class="filter-item" style="width: 100px; margin-right: 20px;">
<el-option v-for="item in layerOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button v-permission="permission.add" size="mini" @click="crud.toAdd">
<i class="iconfont icon-shengchengpandiandan" />
书架盘点
</el-button>
</template>
<template v-slot:right>
<el-button :loading="crud.downloadLoading" size="mini" @click="doExport(crud.selections)">
<i class="iconfont icon-daochu" />
导出
</el-button>
</template>
</crudOperation>
<div class="venue-left">
<div class="container-right tab-content">
<span class="right-top-line" />
<span class="left-bottom-line" />
<!-- <div style="display: flex;justify-content: flex-start; align-items: center;"> -->
<swiper
ref="swiperTitle"
class="swiper-title"
:options="swiperOptionTitle"
:auto-update="true"
:auto-destroy="true"
:delete-instance-on-destroy="true"
:cleanup-styles-on-destroy="true"
>
<swiper-slide
v-for="(item, index) of tabListData"
ref="swiperSlideItem"
:key="'name' + index"
:iname="item.name"
class="swiper-slide-title"
>
<div
class="tab-name"
:class="{ active: index === swiperActiveIndex }"
@click="handleSlidClickFun(index)"
>
{{ item.name }}
</div>
</swiper-slide>
</swiper>
<div class="tag-info">
<p>错序1</p>
<p>错架1</p>
<p>在架20</p>
</div>
<!-- </div> -->
<swiper
ref="swiperContent"
class="swiper-content"
:options="swiperOptionContent"
:auto-update="true"
:auto-destroy="true"
:delete-instance-on-destroy="true"
:cleanup-styles-on-destroy="true"
>
<swiper-slide
v-for="(item, index) of tabListData"
:key="'content' + index"
class="swiper-slide-content"
>
<ul class="cabinet-row">
<li
v-for="(cell,i) in booShelfGrid"
:key="i"
class="cabinet-cell"
:style="cellStyle"
:class="{ active: i === cellIndex }"
@click="handleCellCurrent(cell,i)"
@mouseenter="showPopover(i)"
@mouseleave="hidePopover"
>
<span class="cell-name">{{ removeAreaPrefix(cell.gridName) }}</span>
<!-- v-if="popoverIndex === i" -->
<el-popover
v-if="popoverIndex === i"
ref="popover"
:visible="popoverVisible[i]"
width="400"
:style="popoverStyles[i]"
trigger="manual"
>
<div slot="reference" class="popover-content">
<div class="tooltip-top">
<h4>层位概况</h4>
<i class="update-time">2024-11-28 09:46</i>
</div>
<ul>
<li><p>层位</p><em class="percentage"><i style="color: #fff;">{{ removeAreaPrefix(cell.gridName) }}</i></em></li>
<li><p>在架</p><em><i>15000</i></em></li>
<li><p>错架</p><em><i>300</i></em> <em class="percentage">2.00%</em></li>
<li><p>错序</p><em><i>0</i></em><em class="percentage">0.00%</em></li>
</ul>
</div>
</el-popover>
</li>
</ul>
</swiper-slide>
</swiper>
</div>
</div>
<div class="venue-right">
<div class="lib-right-item lib-info">
<h4>本架概况</h4>
<ul class="data-right-list">
<li><p>书架</p><span><i>001</i></span></li>
<li><p>规则</p><span><i>双面 6 x 5</i></span></li>
</ul>
</div>
<div class="lib-right-item">
<h4>本架盘点概况</h4>
<div class="refresh-date">2024-11-28 09:46</div>
<ul class="data-right-list">
<li><p>在架</p><span><i>15000</i></span></li>
<li><p>错架</p><span><i>300</i></span> <span class="percentage">2.00%</span></li>
<li><p>错序</p><span><i>0</i></span><span class="percentage">0.00%</span></li>
</ul>
</div>
</div>
</div>
<!-- form -->
<el-dialog append-to-body :close-on-click-modal="false" :modal-append-to-body="false" :before-close="crud.cancelCU" :visible="crud.status.cu > 0" :title="crud.status.title">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="80px">
<el-form-item label="盘点单号" prop="taskName">
<el-input v-model="form.taskName" />
</el-form-item>
<el-form-item label="盘点类型" prop="taskType">
<el-input v-model="form.taskType" />
</el-form-item>
<el-form-item label="目标位置" prop="location">
<el-input v-model="form.location" />
</el-form-item>
<el-form-item label="目标数量" prop="number">
<el-input v-model="form.number" />
</el-form-item>
<el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" style="width: 572px;" :rows="4" />
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="crud.cancelCU">取消</el-button>
<el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">保存</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { FetchInitShelfGridByShelfId, FetchBookShelfDetails } from '@/api/shelf/index'
import crudRegion from '@/api/area/index'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import { mapGetters } from 'vuex'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
const defaultForm = { id: null, taskType: null, taskName: null, location: null, number: null, remark: null }
export default {
name: 'DataScreening',
components: { swiper, swiperSlide, crudOperation },
cruds() {
return CRUD({ title: '架位总览', url: 'api/libraryRegion/initLibraryRegionList', crudMethod: { ...crudRegion }, sort: [], optShow: {
add: false,
edit: false,
del: false,
download: false,
group: false,
reset: false
},
queryOnPresenterCreated: false
})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
data() {
const _this = this
return {
floorName: null,
regionName: null,
bookShelfDetails: null,
booShelfGrid: null,
cellInfo: {
gridName: null,
startSortmark: null,
endSortmark: null,
cameraId: null
},
callNumVisible: false,
layerNum: 0,
rackNum: 0,
swiperActiveIndex: 0,
cellIndex: null,
swiperOptionContent: {
slidesPerView: 'auto',
on: {
slideChangeTransitionStart: function() {
_this.cellIndex = null
_this.swiperActiveIndex = this.activeIndex
console.log('activeIndexffff', this.swiperActiveIndex)
_this.swiperTitle.slideTo(this.activeIndex, 500, false)
}
}
},
swiperOptionTitle: {
slidesPerView: 'auto',
freeMode: true
},
layerVal: '001排',
layerOptions: [{ id: 1, name: '001排' }],
tabListData: [],
permission: {
add: ['admin', 'floor:add'],
edit: ['admin', 'floor:edit'],
del: ['admin', 'floor:del']
},
rules: {
taskName: [
{ required: true, message: '请输入盘点单号', trigger: 'blur' }
],
taskType: [
{ required: true, message: '请输入盘点类型', trigger: 'blur' }
],
location: [
{ required: true, message: '请输入目标位置', trigger: 'blur' }
],
number: [
{ required: true, message: '请输入目标数量', trigger: 'blur' }
]
},
popoverIndex: 1,
popoverVisible: [],
popoverStyles: []
}
},
computed: {
...mapGetters([
'user',
'baseApi'
]),
swiperContent() {
return this.$refs.swiperContent.$el.swiper
},
swiperTitle() {
return this.$refs.swiperTitle.$el.swiper
},
cellStyle: function() {
// const h = '100%/' + this.layerNum
// const w = '100%/' + this.rackNum
const h = '60px'
const w = '100%/' + this.rackNum
return { width: `calc(${w} - 4px )`, height: `calc(${h} - 2px)` }
}
},
async created() {
if (localStorage.getItem('dataScreenRegion')) {
const dataScreenRegion = JSON.parse(localStorage.getItem('dataScreenRegion'))
this.floorName = dataScreenRegion.floorName
this.regionName = dataScreenRegion.regionName
// /
this.tabListData = dataScreenRegion.rowType === 1
? dataScreenRegion.toward === 1
? [{ name: 'A面' }]
: [{ name: 'B面' }]
: [{ name: 'A面' }, { name: 'B面' }]
FetchBookShelfDetails({ 'shelfId': dataScreenRegion.id }).then(res => {
this.layerNum = res.shelfFloor
this.rackNum = res.shelfShelf
this.bookShelfDetails = res
this.getInitShelfGridByShelfId(this.bookShelfDetails.toward)
}).catch(() => {
})
}
},
methods: {
handleToGrids() {
this.$router.push({ path: '/dataScreening/gird', query: { }})
},
[CRUD.HOOK.beforeRefresh]() {
},
[CRUD.HOOK.afterRefresh](crud) {
},
//
[CRUD.HOOK.afterValidateCU](crud) {
return true
},
removeAreaPrefix(gridNames) {
const index = gridNames.indexOf('区')
if (index !== -1) {
return gridNames.substring(index + 1)
}
return gridNames
},
getInitShelfGridByShelfId(toward) {
// rowType 1 2
// toward 1 A 2 B
// shelfType 1 '1S'
// shelfType 2 'A1B1'
// shelfType 3 'B1A1'
// floorType 1 ''
// floorType 2 ''
FetchInitShelfGridByShelfId({ 'shelfId': this.bookShelfDetails.id, 'toward': toward }).then(res => {
const sortFunction = toward === 1 ? {
1: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' },
2: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' },
3: { 1: 'sortBookshelvesRightTop', 2: 'sortBookshelvesRightBottom' }
} : {
1: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' },
2: { 1: 'sortBookshelvesRightTop', 2: 'sortBookshelvesRightBottom' },
3: { 1: 'sortBookshelvesLeftTop', 2: 'sortBookshelvesLeftBottom' }
}
const shelfType = this.bookShelfDetails.shelfType
const floorType = this.bookShelfDetails.floorType
const sortMethod = sortFunction[shelfType][floorType]
this.booShelfGrid = this[sortMethod](res)
this.popoverVisible = Array(this.booShelfGrid.length).fill(false)
// this.popoverStyles = new Array(this.booShelfGrid.length).fill({ position: 'absolute', left: '20%', top: '48px' })
}).catch(() => {
})
},
// ,
sortBookshelvesLeftTop(data) {
const sortedData = []
const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
const maxShelf = Math.max(...data.map(item => parseInt(item.gridShelf.slice(-1))))
for (let i = 1; i <= maxFloor; i++) {
for (let j = 1; j <= maxShelf; j++) {
const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.slice(-1)) === j)
if (currentShelf) {
sortedData.push(currentShelf)
}
}
}
return sortedData
},
// ,,
sortBookshelvesRightTop(data) {
const sortedData = []
//
const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
const maxShelf = Math.max(...data.map(item => parseInt(item.gridShelf.match(/\d+$/)[0])))
for (let i = 1; i <= maxFloor; i++) {
//
for (let j = maxShelf; j >= 1; j--) {
const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.match(/\d+$/)[0]) === j)
if (currentShelf) {
sortedData.push(currentShelf)
}
}
}
return sortedData
},
// ,
sortBookshelvesLeftBottom(data) {
const sortedData = []
//
const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
//
const maxShelf = Math.max(...data.map(item => parseInt(item.gridShelf.slice(-1))))
for (let i = maxFloor; i >= 1; i--) {
for (let j = 1; j <= maxShelf; j++) {
const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.slice(-1)) === j)
if (currentShelf) {
sortedData.push(currentShelf)
}
}
}
return sortedData
},
// ,
sortBookshelvesRightBottom(data) {
const sortedData = []
//
const maxFloor = Math.max(...data.map(item => parseInt(item.gridFloor)))
const maxShelfPerFloor = data.map(item => parseInt(item.gridShelf.match(/\d+$/)[0]))
.reduce((acc, curr, index, arr) => {
const floor = parseInt(data[index].gridFloor)
if (!acc[floor]) acc[floor] = 1
if (acc[floor] < curr) acc[floor] = curr
return acc
}, {})
//
for (let i = maxFloor; i >= 1; i--) {
//
for (let j = maxShelfPerFloor[i] || 1; j >= 1; j--) {
const currentShelf = data.find(item => parseInt(item.gridFloor) === i && parseInt(item.gridShelf.match(/\d+$/)[0]) === j)
if (currentShelf) {
sortedData.push(currentShelf)
}
}
}
return sortedData
},
handleSlidClickFun(index) {
this.cellIndex = null
this.handleSlideToFun(index)
if (localStorage.getItem('bookShelfDetails')) {
this.getInitShelfGridByShelfId(index + 1)
}
},
handleSlideToFun(index) {
this.swiperActiveIndex = index
this.swiperContent.slideTo(index, 500, false)
this.swiperTitle.slideTo(index, 500, false)
},
handleCellCurrent(item, index) {
console.log('index', index)
this.cellIndex = index
this.cellInfo = {
id: item.id,
gridName: item.gridName,
startSortmark: item.startSortmark,
endSortmark: item.endSortmark,
cameraId: item.cameraId,
check: item.isCheck,
order: item.isOrder
}
},
showPopover(index) {
this.popoverIndex = index
// popover
if (!this.popoverVisible[index]) {
this.$set(this.popoverVisible, index, true)
}
const lastColumnIndexes = []
const secondLastColumnIndexes = []
for (let i = 0; i < this.booShelfGrid.length; i++) {
//
const columnIndex = i % this.rackNum
// 5
if (columnIndex === this.rackNum - 1) {
lastColumnIndexes.push(i)
//
this.$set(this.popoverStyles, i, { position: 'absolute', left: '-140px', top: '48px' })
}
// 4
if (columnIndex === this.rackNum - 2) {
secondLastColumnIndexes.push(i)
//
this.$set(this.popoverStyles, i, { position: 'absolute', left: '-20px', top: '48px' })
}
}
},
hidePopover() {
this.popoverIndex = null
// popover
this.popoverVisible.forEach((isVisible, index) => {
if (isVisible) {
this.$set(this.popoverVisible, index, false)
}
})
}
}
}
</script>
<style lang="scss" scoped>
.container-right{
min-height: calc(100vh - 232px) !important;
}
.venue-content{
position: relative;
}
.venue-header{
h4{
flex: 1;
}
.bookshelf-area{
padding-right: 30px;
font-weight: bold;
color: #0348F3;
}
}
.crud-opts{
position: absolute;
right: 20px;
top: 10px;
}
.venue-left{
flex: 1;
margin-right: 0 !important;
.venue-preview{
height: 633px !important;
}
}
.venue-right{
display: flex;
flex-direction: column;
width: 400px;
padding: 50px 10px 20px 10px !important;
.lib-right-item{
position: relative;
padding-bottom: 10px;
margin-bottom: 10px;
border: 1px solid #E8F2FF;
border-radius: 4px;
h4{
padding: 6px 10px;
background-color: #E8F2FF;
color: #000;
line-height: 30px;
border-bottom: 1px solid #edeff3;
}
.refresh-date{
position: absolute;
right: 14px;
top: 10px;
font-size: 12px;
line-height: 30px;
}
}
}
.data-right-list {
padding-top: 10px;
li{
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 36px;
p{
width: 80px;
font-weight: bold;
text-align: right;
}
span{
width: 140px;
display: block;
text-align: right;
i{
font-style: normal;
font-weight: bold;
padding: 0 10px;
color: #0348f3;
}
&.percentage{
width: auto;
}
}
}
}
.swiper-title{
::v-deep .swiper-wrapper{
margin: 10px 0;
border-bottom: 1px solid #EDEFF3;
}
}
.swiper-slide-title {
width: auto !important;
margin-right: 20px;
cursor: pointer;
.tab-name {
padding: 10px;
&.active {
color: #0348F3;
border-bottom: 3px solid #0348F3;
}
}
}
.swiper-content{
height: 600px;
}
.tag-info{
position: absolute;
right: 20px;
top: 34px;
display: flex;
justify-content: flex-start;
padding-left: 100px;
p{
margin-left: 20px;
margin-top: -4px;
font-size: 14px;
}
}
.cabinet-row .cabinet-cell{
overflow: inherit;
cursor: pointer;
span.cell-name{
position: initial !important;
transform: none;
line-height: 58px;
}
}
::v-deep .cabinet-row .cabinet-cell span.el-popover__reference-wrapper{
position: absolute !important;
left: 50% !important;
top: 20px !important;
transform: none;
width: 300px;
height: 210px;
background:rgba(0,0,0,.8);
color: #fff;
border-radius: 6px;
z-index: 99999999;
.popover-content{
.tooltip-top{
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
line-height: 40px;
padding: 0 10px;
border-bottom: 1px solid #fff;
}
.tooltip-top i{
font-style: normal;
font-size: 12px;
}
ul{
padding: 10px;
}
ul li{
display: flex;
justify-content: flex-start;
align-items: center;
line-height: 36px;
font-style: normal;
}
ul li p{
width: 80px;
font-weight: bold;
text-align: right;
}
ul li em{
width: 100px;
display: block;
text-align: right;
font-style: normal;
}
ul li i{
font-style: normal;
font-weight: bold;
padding: 0 10px;
color: #0348f3;
}
ul li em.percentage{
width: auto;
}
}
}
</style>

151
src/views/visualCheck/checkManage/index.vue

@ -1,159 +1,16 @@
<template>
<div class="app-container row-container">
<div class="head-container">
<div class="head-search">
<!-- 搜索 -->
<el-select v-model="query.type" clearable size="small" placeholder="类型" class="filter-item" style="width: 120px" @change="crud.toQuery">
<i slot="prefix" class="iconfont icon-zhuangtai" />
<el-option v-for="item in typeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<el-select v-model="query.other" clearable size="small" placeholder="方式" class="filter-item" style="width: 120px" @change="crud.toQuery">
<i slot="prefix" class="iconfont icon-zhuangtai" />
<el-option v-for="item in otherOptions" :key="item.key" :label="item.display_name" :value="item.key" />
</el-select>
<el-input v-model="query.search" clearable size="small" placeholder="输入关键字搜索" prefix-icon="el-icon-search" style="width: 220px;" class="filter-item" @clear="crud.toQuery" @keyup.enter.native="crud.toQuery" />
<rrOperation />
</div>
<crudOperation :permission="permission">
<template v-slot:right>
<el-button :loading="crud.downloadLoading" size="mini" @click="doExport(crud.selections)">
<i class="iconfont icon-daochu" />
导出
</el-button>
</template>
</crudOperation>
</div>
<div class="container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<el-table
ref="table"
v-loading="crud.loading"
class="archives-table"
:data="crud.data"
style="width: 100%;"
height="590"
@selection-change="crud.selectionChangeHandler"
@row-click="clickRowHandler"
@row-dblclick="handleDbClick"
>
<el-table-column prop="regionName" label="类型" />
<el-table-column prop="regionCode" label="方式" />
<el-table-column prop="floorName" label="题名" />
<el-table-column prop="booksheflCount" label="索书号" />
<el-table-column prop="booksheflCount" label="条码" />
<el-table-column prop="booksheflCount" label="目标层位" />
<el-table-column prop="booksheflCount" label="盘点单号" />
<el-table-column prop="booksheflCount" label="操作者" />
<el-table-column prop="createTime" label="操作时间">
<template slot-scope="scope">
<div>{{ scope.row.createTime | parseTime }}</div>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<pagination v-if="crud.data.length!==0" />
</div>
<div>
<router-view />
</div>
</template>
<script>
import crudRegion from '@/api/area/index'
import CRUD, { presenter, header, form, crud } from '@crud/crud'
import crudOperation from '@crud/CRUD.operation'
import rrOperation from '@crud/RR.operation'
import pagination from '@crud/Pagination'
import { mapGetters } from 'vuex'
// import { exportFile } from '@/utils/index'
// import qs from 'qs'
const defaultForm = { id: null, taskType: null, taskName: null, location: null, number: null, remark: null }
export default {
name: 'UpDownLog',
components: { crudOperation, rrOperation, pagination },
cruds() {
return CRUD({ title: '上架/下架日志', url: 'api/libraryRegion/initLibraryRegionList', crudMethod: { ...crudRegion }, sort: [], optShow: {
add: false,
edit: false,
del: false,
download: false,
group: false,
reset: false
}})
},
mixins: [presenter(), header(), form(defaultForm), crud()],
name: 'CheckManage',
data() {
return {
otherOptions: [
{ key: '1', display_name: '自动' },
{ key: '2', display_name: '手动' }
],
typeOptions: [
{ key: '1', display_name: '上架' },
{ key: '2', display_name: '下架' }
],
permission: {
add: ['admin', 'checkLog:add'],
edit: ['admin', 'checkLog:edit'],
del: ['admin', 'checkLog:del']
}
}
},
computed: {
...mapGetters([
'user',
'baseApi'
])
},
methods: {
[CRUD.HOOK.beforeRefresh]() {
},
[CRUD.HOOK.afterRefresh](crud) {
},
//
[CRUD.HOOK.beforeToAdd]() {
}, //
[CRUD.HOOK.beforeToEdit](crud, form) {
},
[CRUD.HOOK.beforeValidateCU](crud, form) {
},
//
[CRUD.HOOK.afterValidateCU](crud) {
return false
},
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)
},
doExport(data) {
console.log(data)
this.crud.downloadLoading = true
this.$confirm('此操作将导出所选数据' + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
const ids = []
data.forEach(val => {
ids.push(val.deptsId)
})
const params = {
'deptsIds': ids
}
console.log(params)
// exportFile(this.baseApi + '/api/depts/download?' + qs.stringify(params, { indices: false }))
// this.crud.downloadLoading = false
}).catch(() => {
})
},
handleCloseDialog() {
}
}
}
</script>
<style lang="scss" scoped>
<style scoped>
</style>

67
src/views/visualCheck/venueDevice/area/index.vue

@ -9,7 +9,7 @@
<div class="head-container">
<div class="head-search">
<!-- 搜索 -->
<el-select v-model="setfloorId" clearable size="small" placeholder="楼层" class="filter-item" style="width: 80px" @change="crud.toQuery">
<el-select v-model="query.floorId" clearable size="small" placeholder="楼层" class="filter-item" style="width: 80px" @change="changeInitFloorValue($event)">
<el-option v-for="item in floorOptions" :key="item.id" :label="item.floorName" :value="item.id" />
</el-select>
<el-input v-model="query.search" clearable size="small" placeholder="输入关键字搜索" prefix-icon="el-icon-search" style="width: 200px;" class="filter-item" @clear="crud.toQuery" @keyup.enter.native="crud.toQuery" />
@ -72,8 +72,8 @@
</ul>
<div v-show="activeIndex == 0" class="venue-preview">
<!-- <MarkCover v-if="currentMarkData && currentMarkData.signPoint" ref="markRefs2" :is-canvas-show="true" :current-mark-data="currentMarkData" :image-floor-url="imageUrl" /> -->
<div v-show="currentMarkData && currentMarkData.signPoint">
<canvas id="canvasPreview" :width="width" :height="height" />
<div v-show="currentMarkData && currentMarkData.signPoint ">
<canvas :id="`canvasPreview${currentMarkData && currentMarkData.id}`" :width="width" :height="height" />
</div>
<img v-if="currentMarkData && !currentMarkData.signPoint" :src="imageUrl" :onerror="defaultImg" alt="">
<img v-if="!currentMarkData" :src="imageUrl" :onerror="defaultImg" alt="">
@ -242,14 +242,13 @@ export default {
},
[CRUD.HOOK.beforeRefresh]() {
this.getLibraryFloorListAll()
console.log('this.setfloorId', this.setfloorId)
this.crud.query.floorId = this.setfloorId
// const formFloor = JSON.parse(localStorage.getItem('formFloor'))
// if (formFloor) {
// this.crud.query.floorId = formFloor.id
// } else {
// this.crud.query.floorId = null
// }
console.log(this.$route.query)
if (this.$route.query.formFloor) {
const formFloor = JSON.parse(localStorage.getItem('formFloor'))
if (formFloor) {
this.crud.query.floorId = formFloor.id
}
}
},
[CRUD.HOOK.afterRefresh](crud) {
console.log('crud.data', crud.data)
@ -277,20 +276,34 @@ export default {
console.log(value)
this.crud.form.regionMap = value
},
changeInitFloorValue(value) {
if (this.$route.query.formFloor) {
localStorage.removeItem('formFloor')
}
this.crud.query.floorId = value
this.crud.toQuery()
console.log('value', value)
},
changeFloorValue(value) {
console.log(value)
},
clickRowHandler(row) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row)
// http://192.168.99.67:12010/api/fileRelevant/getImg?imgId=f6d3ecea-0456-4429-ba77-1a4921d5c806
this.currentMarkData = row
if (this.canvasPreview.lowerCanvasEl) {
this.canvasPreview.clear()
this.canvasPreview.dispose()
}
if (this.activeIndex === 0) {
if (row.floorMap) {
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + row.floorMap
if (row.signPoint) {
const drawinfo = JSON.parse(row.signPoint)
this.initCanvasPreview(drawinfo)
this.$nextTick(() => {
const drawinfo = JSON.parse(row.signPoint)
this.initCanvasPreview(drawinfo)
})
}
} else {
this.imageUrl = this.defaultImg
@ -306,10 +319,6 @@ export default {
changeActiveTab(data) {
this.activeIndex = data
if (this.crud.selections.length === 1) {
// if (this.canvasPreview) {
// this.canvasPreview.clear()
// this.canvasPreview.dispose()
// }
if (this.crud.selections[0].floorMap) {
this.currentMarkData = this.crud.selections[0]
this.imageUrl = this.baseApi + '/api/fileRelevant/getImg?imgId=' + this.crud.selections[0].floorMap
@ -325,8 +334,10 @@ export default {
if (this.activeIndex === 0) {
if (this.crud.selections[0].signPoint) {
console.log('1111')
const drawinfo = JSON.parse(this.crud.selections[0].signPoint)
this.initCanvasPreview(drawinfo)
this.$nextTick(() => {
const drawinfo = JSON.parse(this.crud.selections[0].signPoint)
this.initCanvasPreview(drawinfo)
})
}
}
}
@ -414,15 +425,23 @@ export default {
this.crud.refresh()
},
initCanvasPreview(drawinfo) {
this.canvasPreview = new fabric.Canvas('canvasPreview', {
if (!this.currentMarkData) {
console.error('currentMarkData is null or undefined')
return
}
const canvasId = `canvasPreview${this.currentMarkData.id}`
this.canvasPreview = new fabric.Canvas(canvasId, {
skipTargetFind: false,
selectable: false,
selection: false
})
this.canvasPreview.selectionColor = 'rgba(0,0,0,0.05)'
this.loadDrawPreview(drawinfo)
this.canvasPreview.on('mouse:wheel', this.mouse)
this.$nextTick(() => {
this.canvasPreview.selectionColor = 'rgba(0,0,0,0.05)'
this.loadDrawPreview(drawinfo)
this.canvasPreview.on('mouse:wheel', this.mouse)
})
},
//
mouse(e) {

63
src/views/visualCheck/venueDevice/bookshelfPosition/index.vue

@ -21,7 +21,7 @@
<i class="iconfont icon-daochu" />
导出层位编码
</el-button>
<el-button size="mini" :disabled="!cellInfo.cameraId">
<el-button size="mini" :disabled="!cellInfo.cameraId" @click="handleViewVideo(cellInfo.cameraId)">
<i class="iconfont icon-yulan" />
{{ cellInfo.cameraId ? '摄像头预览' : '未绑定摄像头' }}
</el-button>
@ -70,7 +70,7 @@
>
<ul class="cabinet-row">
<li v-for="(item,index) in booShelfGrid" :key="index" class="cabinet-cell" :style="cellStyle" :class="{ active: index === cellIndex }" @click="handleCellCurrent(item,index)">
<span>{{ item.gridName }}</span>
<span>{{ removeAreaPrefix(item.gridName) }}</span>
</li>
</ul>
</swiper-slide>
@ -147,21 +147,33 @@
</div>
</div>
</el-dialog>
<!-- 查看监控视频 -->
<el-dialog class="view-video" ppend-to-body :close-on-click-modal="false" :modal-append-to-body="false" :visible="hkVideoVisible" title="查看监控视频" :before-close="handleClose">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<hkVideo :hk-config="hkConfig" />
</div>
</el-dialog>
</div>
</template>
<script>
// https://blog.csdn.net/qq_37236395/article/details/119737898
import { FetchInitShelfGridByShelfId, FetcheEditSortmarkByGrid, FetchChangeOrderByGrid, FetchChangeCheckByGrid } from '@/api/shelf/index'
import { FetchDeviceById } from '@/api/deviceVI/index'
import { mapGetters } from 'vuex'
import { swiper, swiperSlide } from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
import { parseTime, saveAs, getBlob } from '@/utils/index'
import qs from 'qs'
import hkVideo from '@/views/components/hkVideo.vue'
export default {
name: 'BookshelfPosition',
components: { swiper, swiperSlide },
components: { swiper, swiperSlide, hkVideo },
data() {
const _this = this
return {
@ -217,6 +229,13 @@ export default {
endSortmark: [
{ required: true, message: '结束索书号不可为空', trigger: 'blur' }
]
},
hkVideoVisible: false,
hkConfig: {
'username': null,
'password': null,
'ip': null,
'port': null
}
}
},
@ -232,7 +251,9 @@ export default {
return this.$refs.swiperTitle.$el.swiper
},
cellStyle: function() {
const h = '100%/' + this.layerNum
// const h = '100%/' + this.layerNum
// const w = '100%/' + this.rackNum
const h = '60px'
const w = '100%/' + this.rackNum
return { width: `calc(${w} - 4px )`, height: `calc(${h} - 2px)` }
}
@ -269,6 +290,13 @@ export default {
mounted() {
},
methods: {
removeAreaPrefix(gridNames) {
const index = gridNames.indexOf('区')
if (index !== -1) {
return gridNames.substring(index + 1)
}
return gridNames
},
getInitShelfGridByShelfId(toward) {
// rowType 1 2
// toward 1 A 2 B
@ -422,8 +450,26 @@ export default {
}
},
handleClose() {
this.$refs['form'].resetFields()
this.callNumVisible = false
if (this.callNumVisible) {
this.$refs['form'].resetFields()
this.callNumVisible = false
}
this.hkVideoVisible = false
},
handleViewVideo(data) {
console.log('data', data)
FetchDeviceById({ 'deviceId': data }).then(res => {
if (res) {
console.log(res)
this.hkConfig = {
'username': res.account,
'password': res.password,
'ip': res.ipv4,
'port': res.rtsp
}
this.hkVideoVisible = true
}
})
},
//
changeCheckSwitch(data) {
@ -542,4 +588,9 @@ export default {
// padding: 0 10px;
// margin: 0 10px 0 0;
}
.view-video{
::v-deep .el-dialog{
width: 1000px !important;
}
}
</style>

2
src/views/visualCheck/venueDevice/floor/index.vue

@ -207,7 +207,7 @@ export default {
// }).catch(() => {
// })
localStorage.setItem('formFloor', JSON.stringify(row))
this.$router.push({ path: '/check/venueDevice/area' })
this.$router.push({ path: '/check/venueDevice/area', query: { 'formFloor': true }})
},
toDelete(datas) {
this.$confirm('此操作将删除当前所选楼层<span>你是否还要继续?</span>', '提示', {

Loading…
Cancel
Save