2023年4月21日 添加功能
This commit is contained in:
parent
edeb5e8abd
commit
fd33b72f79
|
@ -14,10 +14,13 @@ VITE_DROP_CONSOLE = true
|
|||
VITE_GLOB_API_URL=/jeecgboot
|
||||
|
||||
#后台接口全路径地址(必填)
|
||||
VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
|
||||
VITE_GLOB_DOMAIN_URL=http://localhost:8090/jeecg-boot
|
||||
|
||||
# 接口前缀
|
||||
VITE_GLOB_API_URL_PREFIX=
|
||||
|
||||
#微前端qiankun应用,命名必须以VITE_APP_SUB_开头,jeecg-app-1为子应用的项目名称,也是子应用的路由父路径
|
||||
VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'
|
||||
|
||||
#RTC服务器地址,
|
||||
VITE_GLOB_RTC_SERVER = http://GTR6-83T4P40:9001
|
|
@ -21,6 +21,9 @@ VITE_GLOB_API_URL=/jeecg-boot
|
|||
#后台接口全路径地址(必填)
|
||||
VITE_GLOB_DOMAIN_URL=http://bylwcs.nenu.edu.cn/jeecg-boot
|
||||
|
||||
#RTC服务器地址
|
||||
VITE_GLOB_RTC_SERVER = http://bylwcs.nenu.edu.cn/RTCServer
|
||||
|
||||
# 接口父路径前缀
|
||||
VITE_GLOB_API_URL_PREFIX=
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -77,6 +77,14 @@ const site: AppRouteModule = {
|
|||
title: '东北师范大学本科课堂教学质量评价表',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'liveView',
|
||||
name: 'LiveBroadcastRoom',
|
||||
component: () => import('/@/views/site/common/webRTC/index.vue'),
|
||||
meta: {
|
||||
title: '直播',
|
||||
}
|
||||
}
|
||||
|
||||
],
|
||||
};
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
list = '/jiaoshi/kcJiaoshixinxibiao/list',
|
||||
save='/jiaoshi/kcJiaoshixinxibiao/add',
|
||||
edit='/jiaoshi/kcJiaoshixinxibiao/edit',
|
||||
deleteOne = '/jiaoshi/kcJiaoshixinxibiao/delete',
|
||||
deleteBatch = '/jiaoshi/kcJiaoshixinxibiao/deleteBatch',
|
||||
importExcel = '/jiaoshi/kcJiaoshixinxibiao/importExcel',
|
||||
exportXls = '/jiaoshi/kcJiaoshixinxibiao/exportXls',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 删除单个
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const deleteOne = (params,handleSuccess) => {
|
||||
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const batchDelete = (params, handleSuccess) => {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或者更新
|
||||
* @param params
|
||||
* @param isUpdate
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params }, { isTransformResponse: false });
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
import {BasicColumn} from '/@/components/Table';
|
||||
import {FormSchema} from '/@/components/Table';
|
||||
import { rules} from '/@/utils/helper/validator';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
//列表数据
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '教室编号',
|
||||
align: "center",
|
||||
dataIndex: 'jsbh'
|
||||
},
|
||||
{
|
||||
title: '教室名称',
|
||||
align: "center",
|
||||
dataIndex: 'jsmc'
|
||||
},
|
||||
{
|
||||
title: '门牌号',
|
||||
align: "center",
|
||||
dataIndex: 'mph'
|
||||
},
|
||||
{
|
||||
title: '校区',
|
||||
align: "center",
|
||||
dataIndex: 'xq'
|
||||
},
|
||||
{
|
||||
title: '教室类型',
|
||||
align: "center",
|
||||
dataIndex: 'jslx'
|
||||
},
|
||||
{
|
||||
title: '所属教学楼',
|
||||
align: "center",
|
||||
dataIndex: 'ssjxl'
|
||||
},
|
||||
{
|
||||
title: '所属单位',
|
||||
align: "center",
|
||||
dataIndex: 'ssdw'
|
||||
},
|
||||
{
|
||||
title: '排课优先级',
|
||||
align: "center",
|
||||
dataIndex: 'pkyxj'
|
||||
},
|
||||
{
|
||||
title: '是否专用',
|
||||
align: "center",
|
||||
dataIndex: 'sfzy'
|
||||
},
|
||||
{
|
||||
title: '教室类型2',
|
||||
align: "center",
|
||||
dataIndex: 'jslx2'
|
||||
},
|
||||
{
|
||||
title: '座位数',
|
||||
align: "center",
|
||||
dataIndex: 'zws'
|
||||
},
|
||||
{
|
||||
title: '考试座位数',
|
||||
align: "center",
|
||||
dataIndex: 'kszws'
|
||||
},
|
||||
{
|
||||
title: '是否可用',
|
||||
align: "center",
|
||||
dataIndex: 'sfky'
|
||||
},
|
||||
{
|
||||
title: '是否笔试考试',
|
||||
align: "center",
|
||||
dataIndex: 'sfbsks'
|
||||
},
|
||||
{
|
||||
title: '是否可借用',
|
||||
align: "center",
|
||||
dataIndex: 'sfkjy'
|
||||
},
|
||||
];
|
||||
|
||||
//查询数据
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
];
|
||||
|
||||
//表单数据
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '教室编号',
|
||||
field: 'jsbh',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '教室名称',
|
||||
field: 'jsmc',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '门牌号',
|
||||
field: 'mph',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '校区',
|
||||
field: 'xq',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '教室类型',
|
||||
field: 'jslx',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '所属教学楼',
|
||||
field: 'ssjxl',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '所属单位',
|
||||
field: 'ssdw',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '排课优先级',
|
||||
field: 'pkyxj',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '是否专用',
|
||||
field: 'sfzy',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '教室类型2',
|
||||
field: 'jslx2',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '座位数',
|
||||
field: 'zws',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '考试座位数',
|
||||
field: 'kszws',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '是否可用',
|
||||
field: 'sfky',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '是否笔试考试',
|
||||
field: 'sfbsks',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '是否可借用',
|
||||
field: 'sfkjy',
|
||||
component: 'Input',
|
||||
},
|
||||
// TODO 主键隐藏字段,目前写死为ID
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
];
|
|
@ -0,0 +1,215 @@
|
|||
<template>
|
||||
<div>
|
||||
<!--查询区域-->
|
||||
<div class="jeecg-basic-table-form-container">
|
||||
<a-form @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-row :gutter="24">
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>批量操作
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
|
||||
</template>
|
||||
<!--字段回显插槽-->
|
||||
<template #htmlSlot="{text}">
|
||||
<div v-html="text"></div>
|
||||
</template>
|
||||
<!--省市区字段回显插槽-->
|
||||
<!--<template #pcaSlot="{text}">
|
||||
{{ getAreaTextByCode(text) }}
|
||||
</template>-->
|
||||
<template #fileSlot="{text}">
|
||||
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
|
||||
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download-outlined" size="small" @click="downloadFile(text)">下载</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- 表单区域 -->
|
||||
<KcJiaoshixinxibiaoModal ref="registerModal" @success="handleSuccess"></KcJiaoshixinxibiaoModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="jiaoshi-kcJiaoshixinxibiao" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns } from './KcJiaoshixinxibiao.data';
|
||||
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './KcJiaoshixinxibiao.api';
|
||||
import { downloadFile } from '/@/utils/common/renderUtils';
|
||||
import KcJiaoshixinxibiaoModal from './components/KcJiaoshixinxibiaoModal.vue'
|
||||
|
||||
const queryParam = ref<any>({});
|
||||
const toggleSearchStatus = ref<boolean>(false);
|
||||
const registerModal = ref();
|
||||
//注册table数据
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '教室信息表',
|
||||
api: list,
|
||||
columns,
|
||||
canResize:false,
|
||||
useSearchForm: false,
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
params.column = '',params.order = '';//新生成的默认不带排序
|
||||
return Object.assign(params, queryParam.value);
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: "教室信息表",
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
success: handleSuccess
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const labelCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 7 },
|
||||
});
|
||||
const wrapperCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
registerModal.value.disableSubmit = false;
|
||||
registerModal.value.add();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record: Recordable) {
|
||||
registerModal.value.disableSubmit = false;
|
||||
registerModal.value.edit(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
function handleDetail(record: Recordable) {
|
||||
registerModal.value.disableSubmit = true;
|
||||
registerModal.value.edit(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteOne({ id: record.id }, handleSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
(selectedRowKeys.value = []) && reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
}, {
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function searchQuery() {
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function searchReset() {
|
||||
queryParam.value = {};
|
||||
selectedRowKeys.value = [];
|
||||
//刷新数据
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.jeecg-basic-table-form-container {
|
||||
.table-page-search-submitButtons {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.query-group-cust{
|
||||
width: calc(50% - 15px);
|
||||
min-width: 100px !important;
|
||||
}
|
||||
.query-group-split-cust{
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,72 @@
|
|||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
enum Api {
|
||||
list = '/jiaoshi/kcZhihuijiaoshi/list',
|
||||
save='/jiaoshi/kcZhihuijiaoshi/add',
|
||||
edit='/jiaoshi/kcZhihuijiaoshi/edit',
|
||||
deleteOne = '/jiaoshi/kcZhihuijiaoshi/delete',
|
||||
deleteBatch = '/jiaoshi/kcZhihuijiaoshi/deleteBatch',
|
||||
importExcel = '/jiaoshi/kcZhihuijiaoshi/importExcel',
|
||||
exportXls = '/jiaoshi/kcZhihuijiaoshi/exportXls',
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
/**
|
||||
* 导入api
|
||||
*/
|
||||
export const getImportUrl = Api.importExcel;
|
||||
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
export const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
/**
|
||||
* 删除单个
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const deleteOne = (params,handleSuccess) => {
|
||||
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
* @param params
|
||||
* @param handleSuccess
|
||||
*/
|
||||
export const batchDelete = (params, handleSuccess) => {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '是否删除选中数据',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或者更新
|
||||
* @param params
|
||||
* @param isUpdate
|
||||
*/
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url: url, params }, { isTransformResponse: false });
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import {BasicColumn} from '/@/components/Table';
|
||||
import {FormSchema} from '/@/components/Table';
|
||||
import { rules} from '/@/utils/helper/validator';
|
||||
import { render } from '/@/utils/common/renderUtils';
|
||||
//列表数据
|
||||
export const columns: BasicColumn[] = [
|
||||
{
|
||||
title: '校区',
|
||||
align: "center",
|
||||
dataIndex: 'xq'
|
||||
},
|
||||
{
|
||||
title: '项目',
|
||||
align: "center",
|
||||
dataIndex: 'xm'
|
||||
},
|
||||
{
|
||||
title: '新ip',
|
||||
align: "center",
|
||||
dataIndex: 'ip'
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
align: "center",
|
||||
dataIndex: 'user'
|
||||
},
|
||||
{
|
||||
title: '密码',
|
||||
align: "center",
|
||||
dataIndex: 'mima'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
align: "center",
|
||||
dataIndex: 'bz'
|
||||
},
|
||||
];
|
||||
|
||||
//查询数据
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
];
|
||||
|
||||
//表单数据
|
||||
export const formSchema: FormSchema[] = [
|
||||
{
|
||||
label: '校区',
|
||||
field: 'xq',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '项目',
|
||||
field: 'xm',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '新ip',
|
||||
field: 'ip',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '账号',
|
||||
field: 'user',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '密码',
|
||||
field: 'mima',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'bz',
|
||||
component: 'Input',
|
||||
},
|
||||
// TODO 主键隐藏字段,目前写死为ID
|
||||
{
|
||||
label: '',
|
||||
field: 'id',
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
];
|
|
@ -0,0 +1,215 @@
|
|||
<template>
|
||||
<div>
|
||||
<!--查询区域-->
|
||||
<div class="jeecg-basic-table-form-container">
|
||||
<a-form @keyup.enter.native="searchQuery" :model="queryParam" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-row :gutter="24">
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" @click="handleAdd" preIcon="ant-design:plus-outlined"> 新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
|
||||
<j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>批量操作
|
||||
<Icon icon="mdi:chevron-down"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)"/>
|
||||
</template>
|
||||
<!--字段回显插槽-->
|
||||
<template #htmlSlot="{text}">
|
||||
<div v-html="text"></div>
|
||||
</template>
|
||||
<!--省市区字段回显插槽-->
|
||||
<!--<template #pcaSlot="{text}">
|
||||
{{ getAreaTextByCode(text) }}
|
||||
</template>-->
|
||||
<template #fileSlot="{text}">
|
||||
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
|
||||
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download-outlined" size="small" @click="downloadFile(text)">下载</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- 表单区域 -->
|
||||
<KcZhihuijiaoshiModal ref="registerModal" @success="handleSuccess"></KcZhihuijiaoshiModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="jiaoshi-kcZhihuijiaoshi" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns } from './KcZhihuijiaoshi.data';
|
||||
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './KcZhihuijiaoshi.api';
|
||||
import { downloadFile } from '/@/utils/common/renderUtils';
|
||||
import KcZhihuijiaoshiModal from './components/KcZhihuijiaoshiModal.vue'
|
||||
|
||||
const queryParam = ref<any>({});
|
||||
const toggleSearchStatus = ref<boolean>(false);
|
||||
const registerModal = ref();
|
||||
//注册table数据
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
tableProps: {
|
||||
title: '智慧教室',
|
||||
api: list,
|
||||
columns,
|
||||
canResize:false,
|
||||
useSearchForm: false,
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
params.column = '',params.order = '';//新生成的默认不带排序
|
||||
return Object.assign(params, queryParam.value);
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: "智慧教室",
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
success: handleSuccess
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
const labelCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 7 },
|
||||
});
|
||||
const wrapperCol = reactive({
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
});
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
registerModal.value.disableSubmit = false;
|
||||
registerModal.value.add();
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record: Recordable) {
|
||||
registerModal.value.disableSubmit = false;
|
||||
registerModal.value.edit(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
function handleDetail(record: Recordable) {
|
||||
registerModal.value.disableSubmit = true;
|
||||
registerModal.value.edit(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deleteOne({ id: record.id }, handleSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDelete({ ids: selectedRowKeys.value }, handleSuccess);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功回调
|
||||
*/
|
||||
function handleSuccess() {
|
||||
(selectedRowKeys.value = []) && reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉操作栏
|
||||
*/
|
||||
function getDropDownAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
}, {
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*/
|
||||
function searchQuery() {
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
function searchReset() {
|
||||
queryParam.value = {};
|
||||
selectedRowKeys.value = [];
|
||||
//刷新数据
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.jeecg-basic-table-form-container {
|
||||
.table-page-search-submitButtons {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.query-group-cust{
|
||||
width: calc(50% - 15px);
|
||||
min-width: 100px !important;
|
||||
}
|
||||
.query-group-split-cust{
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="教室编号" v-bind="validateInfos.jsbh">
|
||||
<a-input v-model:value="formData.jsbh" placeholder="请输入教室编号" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="教室名称" v-bind="validateInfos.jsmc">
|
||||
<a-input v-model:value="formData.jsmc" placeholder="请输入教室名称" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="门牌号" v-bind="validateInfos.mph">
|
||||
<a-input v-model:value="formData.mph" placeholder="请输入门牌号" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="校区" v-bind="validateInfos.xq">
|
||||
<a-input v-model:value="formData.xq" placeholder="请输入校区" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="教室类型" v-bind="validateInfos.jslx">
|
||||
<a-input v-model:value="formData.jslx" placeholder="请输入教室类型" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="所属教学楼" v-bind="validateInfos.ssjxl">
|
||||
<a-input v-model:value="formData.ssjxl" placeholder="请输入所属教学楼" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="所属单位" v-bind="validateInfos.ssdw">
|
||||
<a-input v-model:value="formData.ssdw" placeholder="请输入所属单位" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="排课优先级" v-bind="validateInfos.pkyxj">
|
||||
<a-input v-model:value="formData.pkyxj" placeholder="请输入排课优先级" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="是否专用" v-bind="validateInfos.sfzy">
|
||||
<a-input v-model:value="formData.sfzy" placeholder="请输入是否专用" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="教室类型2" v-bind="validateInfos.jslx2">
|
||||
<a-input v-model:value="formData.jslx2" placeholder="请输入教室类型2" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="座位数" v-bind="validateInfos.zws">
|
||||
<a-input v-model:value="formData.zws" placeholder="请输入座位数" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="考试座位数" v-bind="validateInfos.kszws">
|
||||
<a-input v-model:value="formData.kszws" placeholder="请输入考试座位数" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="是否可用" v-bind="validateInfos.sfky">
|
||||
<a-input v-model:value="formData.sfky" placeholder="请输入是否可用" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="是否笔试考试" v-bind="validateInfos.sfbsks">
|
||||
<a-input v-model:value="formData.sfbsks" placeholder="请输入是否笔试考试" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="是否可借用" v-bind="validateInfos.sfkjy">
|
||||
<a-input v-model:value="formData.sfkjy" placeholder="请输入是否可借用" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getValueType } from '/@/utils';
|
||||
import { saveOrUpdate } from '../KcJiaoshixinxibiao.api';
|
||||
import { Form } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
formDisabled: { type: Boolean, default: false },
|
||||
formData: { type: Object, default: ()=>{} },
|
||||
formBpm: { type: Boolean, default: true }
|
||||
});
|
||||
const formRef = ref();
|
||||
const useForm = Form.useForm;
|
||||
const emit = defineEmits(['register', 'ok']);
|
||||
const formData = reactive<Record<string, any>>({
|
||||
id: '',
|
||||
jsbh: '',
|
||||
jsmc: '',
|
||||
mph: '',
|
||||
xq: '',
|
||||
jslx: '',
|
||||
ssjxl: '',
|
||||
ssdw: '',
|
||||
pkyxj: '',
|
||||
sfzy: '',
|
||||
jslx2: '',
|
||||
zws: '',
|
||||
kszws: '',
|
||||
sfky: '',
|
||||
sfbsks: '',
|
||||
sfkjy: '',
|
||||
});
|
||||
const { createMessage } = useMessage();
|
||||
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
|
||||
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
//表单验证
|
||||
const validatorRules = {
|
||||
};
|
||||
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
|
||||
|
||||
// 表单禁用
|
||||
const disabled = computed(()=>{
|
||||
if(props.formBpm === true){
|
||||
if(props.formData.disabled === false){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return props.formDisabled;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function add() {
|
||||
edit({});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function edit(record) {
|
||||
nextTick(() => {
|
||||
resetFields();
|
||||
//赋值
|
||||
Object.assign(formData, record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交数据
|
||||
*/
|
||||
async function submitForm() {
|
||||
// 触发表单验证
|
||||
await validate();
|
||||
confirmLoading.value = true;
|
||||
const isUpdate = ref<boolean>(false);
|
||||
//时间格式化
|
||||
let model = formData;
|
||||
if (model.id) {
|
||||
isUpdate.value = true;
|
||||
}
|
||||
//循环数据
|
||||
for (let data in model) {
|
||||
//如果该数据是数组并且是字符串类型
|
||||
if (model[data] instanceof Array) {
|
||||
let valueType = getValueType(formRef.value.getProps, data);
|
||||
//如果是字符串类型的需要变成以逗号分割的字符串
|
||||
if (valueType === 'string') {
|
||||
model[data] = model[data].join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
await saveOrUpdate(model, isUpdate.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
createMessage.success(res.message);
|
||||
emit('ok');
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
defineExpose({
|
||||
add,
|
||||
edit,
|
||||
submitForm,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.antd-modal-form {
|
||||
min-height: 500px !important;
|
||||
overflow-y: auto;
|
||||
padding: 24px 24px 24px 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
|
||||
<KcJiaoshixinxibiaoForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></KcJiaoshixinxibiaoForm>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick, defineExpose } from 'vue';
|
||||
import KcJiaoshixinxibiaoForm from './KcJiaoshixinxibiaoForm.vue'
|
||||
|
||||
const title = ref<string>('');
|
||||
const width = ref<number>(800);
|
||||
const visible = ref<boolean>(false);
|
||||
const disableSubmit = ref<boolean>(false);
|
||||
const registerForm = ref();
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function add() {
|
||||
title.value = '新增';
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
registerForm.value.add();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @param record
|
||||
*/
|
||||
function edit(record) {
|
||||
title.value = disableSubmit.value ? '详情' : '编辑';
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
registerForm.value.edit(record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定按钮点击事件
|
||||
*/
|
||||
function handleOk() {
|
||||
registerForm.value.submitForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* form保存回调事件
|
||||
*/
|
||||
function submitCallback() {
|
||||
handleCancel();
|
||||
emit('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消按钮回调事件
|
||||
*/
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
add,
|
||||
edit,
|
||||
disableSubmit,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/**隐藏样式-modal确定按钮 */
|
||||
.jee-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,157 @@
|
|||
<template>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-row>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="校区" v-bind="validateInfos.xq">
|
||||
<a-input v-model:value="formData.xq" placeholder="请输入校区" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="项目" v-bind="validateInfos.xm">
|
||||
<a-input v-model:value="formData.xm" placeholder="请输入项目" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="新ip" v-bind="validateInfos.ip">
|
||||
<a-input v-model:value="formData.ip" placeholder="请输入新ip" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="账号" v-bind="validateInfos.user">
|
||||
<a-input v-model:value="formData.user" placeholder="请输入账号" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="密码" v-bind="validateInfos.mima">
|
||||
<a-input v-model:value="formData.mima" placeholder="请输入密码" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="备注" v-bind="validateInfos.bz">
|
||||
<a-input v-model:value="formData.bz" placeholder="请输入备注" :disabled="disabled"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted } from 'vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getValueType } from '/@/utils';
|
||||
import { saveOrUpdate } from '../KcZhihuijiaoshi.api';
|
||||
import { Form } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
formDisabled: { type: Boolean, default: false },
|
||||
formData: { type: Object, default: ()=>{} },
|
||||
formBpm: { type: Boolean, default: true }
|
||||
});
|
||||
const formRef = ref();
|
||||
const useForm = Form.useForm;
|
||||
const emit = defineEmits(['register', 'ok']);
|
||||
const formData = reactive<Record<string, any>>({
|
||||
id: '',
|
||||
xq: '',
|
||||
xm: '',
|
||||
ip: '',
|
||||
user: '',
|
||||
mima: '',
|
||||
bz: '',
|
||||
});
|
||||
const { createMessage } = useMessage();
|
||||
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
|
||||
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
//表单验证
|
||||
const validatorRules = {
|
||||
};
|
||||
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
|
||||
|
||||
// 表单禁用
|
||||
const disabled = computed(()=>{
|
||||
if(props.formBpm === true){
|
||||
if(props.formData.disabled === false){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return props.formDisabled;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function add() {
|
||||
edit({});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*/
|
||||
function edit(record) {
|
||||
nextTick(() => {
|
||||
resetFields();
|
||||
//赋值
|
||||
Object.assign(formData, record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交数据
|
||||
*/
|
||||
async function submitForm() {
|
||||
// 触发表单验证
|
||||
await validate();
|
||||
confirmLoading.value = true;
|
||||
const isUpdate = ref<boolean>(false);
|
||||
//时间格式化
|
||||
let model = formData;
|
||||
if (model.id) {
|
||||
isUpdate.value = true;
|
||||
}
|
||||
//循环数据
|
||||
for (let data in model) {
|
||||
//如果该数据是数组并且是字符串类型
|
||||
if (model[data] instanceof Array) {
|
||||
let valueType = getValueType(formRef.value.getProps, data);
|
||||
//如果是字符串类型的需要变成以逗号分割的字符串
|
||||
if (valueType === 'string') {
|
||||
model[data] = model[data].join(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
await saveOrUpdate(model, isUpdate.value)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
createMessage.success(res.message);
|
||||
emit('ok');
|
||||
} else {
|
||||
createMessage.warning(res.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
defineExpose({
|
||||
add,
|
||||
edit,
|
||||
submitForm,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.antd-modal-form {
|
||||
min-height: 500px !important;
|
||||
overflow-y: auto;
|
||||
padding: 24px 24px 24px 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
|
||||
<KcZhihuijiaoshiForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></KcZhihuijiaoshiForm>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick, defineExpose } from 'vue';
|
||||
import KcZhihuijiaoshiForm from './KcZhihuijiaoshiForm.vue'
|
||||
|
||||
const title = ref<string>('');
|
||||
const width = ref<number>(800);
|
||||
const visible = ref<boolean>(false);
|
||||
const disableSubmit = ref<boolean>(false);
|
||||
const registerForm = ref();
|
||||
const emit = defineEmits(['register', 'success']);
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function add() {
|
||||
title.value = '新增';
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
registerForm.value.add();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
* @param record
|
||||
*/
|
||||
function edit(record) {
|
||||
title.value = disableSubmit.value ? '详情' : '编辑';
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
registerForm.value.edit(record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定按钮点击事件
|
||||
*/
|
||||
function handleOk() {
|
||||
registerForm.value.submitForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* form保存回调事件
|
||||
*/
|
||||
function submitCallback() {
|
||||
handleCancel();
|
||||
emit('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消按钮回调事件
|
||||
*/
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
add,
|
||||
edit,
|
||||
disableSubmit,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/**隐藏样式-modal确定按钮 */
|
||||
.jee-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div style="width:100%;height: 100%;">
|
||||
<a-row>
|
||||
<a-col :span="4">
|
||||
<a-menu
|
||||
v-model:openKeys="openKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
style="overflow: hidden;overflow-y:scroll"
|
||||
:style="{ height: `calc(100vh - ${topWidth})`, borderRight: 0 }"
|
||||
mode="inline"
|
||||
>
|
||||
<a-menu-item v-for="(item, index) of leftList" :key="index" @click="(e) => titleClick(e,item)" :title="`${item.jsmc}-${item.xm}`">
|
||||
{{ item.jsmc }}-{{ item.xm }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<!-- document.querySelector('.ant-layout .jeecg-default-layout-main > div').style.height -->
|
||||
</a-col>
|
||||
<a-col :span="20">
|
||||
<bVideo ref="bVideoMainRef" :videoOption="{ autoPlay: true }" :videoKey="'main'+currentItem.id"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<!-- <a-layout>
|
||||
<a-layout-sider width="200" style="background: #fff">
|
||||
<a-menu
|
||||
v-model:openKeys="openKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
:style="{ height: '100%', borderRight: 0 }"
|
||||
mode="inline"
|
||||
>
|
||||
<a-menu-item v-for="(item, index) of leftList" :key="index" @click="(e) => titleClick(e,item)" :title="`${item.xq}-${item.jsmc}-${item.xm}`">
|
||||
{{ item.xq }}-{{ item.jsmc }}-{{ item.xm }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
|
||||
</a-layout-sider>
|
||||
<a-layout>
|
||||
<a-layout-content> -->
|
||||
<!-- {{ currentItem }} -->
|
||||
<!-- <bVideo ref="bVideoMainRef" :videoOption="{ autoPlay: true }" :videoKey="'main'+currentItem.id"/> -->
|
||||
<!-- <a-row>
|
||||
<a-col :span="12">
|
||||
<bVideo ref="bVideoMainRef" :videoOption="{ autoPlay: true }" :videoKey="'main'+currentItem.id"/>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<bVideo ref="bVideoLeftRef" :videoOption="{ autoPlay: true }" :videoKey="'main2'+currentItem.id"/>
|
||||
</a-col>
|
||||
</a-row> -->
|
||||
<!-- </a-layout-content>
|
||||
</a-layout> -->
|
||||
<!-- </a-layout> -->
|
||||
</template>
|
||||
<script lang="ts" setup name="zhihuijiaoshiIndexPage">
|
||||
import type { MenuProps } from 'ant-design-vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useMessage } from "/@/hooks/web/useMessage";
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
|
||||
import bVideo from '/@/views/site/common/webRTC/video.vue';
|
||||
|
||||
const bVideoMainRef = ref();
|
||||
const _document = window.document;
|
||||
// const bVideoLeftRef = ref();
|
||||
|
||||
const leftList = <any>ref([]);
|
||||
const currentItem = <any>ref({});
|
||||
const topWidth = <any>ref('0');
|
||||
|
||||
onMounted(() => {
|
||||
list({ pageSize: -1 }).then(res => {
|
||||
leftList.value = (res?.records) ?? [];
|
||||
topWidth.value = _document?.querySelector('.ant-layout .jeecg-default-layout-main > div')?.style?.height?? '0';
|
||||
// console.log(_document?.querySelector('.ant-layout .jeecg-default-layout-main > div')?.style?.height?? '0')
|
||||
});
|
||||
});
|
||||
|
||||
// const topWidthCalc = computed(() => {
|
||||
// return _document?.querySelector('.ant-layout .jeecg-default-layout-main > div')?.style?.height?? '0';
|
||||
// });
|
||||
// xxx = document.querySelector('.ant-layout .jeecg-default-layout-main > div').style.height
|
||||
//
|
||||
enum Api {
|
||||
list = '/jiaoshi/kcZhihuijiaoshi/list',
|
||||
}
|
||||
/**
|
||||
* 列表接口
|
||||
* @param params
|
||||
*/
|
||||
const list = (params) => defHttp.get({ url: Api.list, params });
|
||||
|
||||
|
||||
const selectedKeys = ref<string[]>(['1']);
|
||||
const openKeys = ref<string[]>(['sub1']);
|
||||
const titleClick = (e: Event, item: any) => {
|
||||
console.log('titleClick ->', e, item);
|
||||
currentItem.value = item;
|
||||
playerVideo(item);
|
||||
};
|
||||
|
||||
function playerVideo(item){
|
||||
bVideoMainRef.value.connectFn(item.ip);
|
||||
// bVideoMainRef.value.connectFn('rtsp://176.139.87.16/axis-media/media.amp','external');
|
||||
// bVideoLeftRef.value.connectFn('rtsp://176.139.87.16/axis-media/media.amp','external');
|
||||
}
|
||||
|
||||
|
||||
|
||||
//页面销毁时销毁webRtc
|
||||
onBeforeUnmount(() => {
|
||||
bVideoMainRef.value.closeWebRtcStreamerFn();
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -17,6 +17,7 @@
|
|||
</div>
|
||||
<span class="topTitle">
|
||||
{{ projectName }}
|
||||
<RouterLink hidden target='_blank' :to="{path:'/site/liveView',query:{ url: 'rtsp://176.139.87.16/axis-media/media.amp' }}">直播测试页</RouterLink>
|
||||
</span>
|
||||
<span class="topRight">
|
||||
<a-dropdown>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<!-- <div @click="onPlay">点击开始播放(连接)直播</div>
|
||||
<div @click="onPlay2">点击开始播放(连接)直播2</div> -->
|
||||
<!-- <div>{{ route.query.url }}</div> -->
|
||||
<bVideo ref="bVideoRef"/>
|
||||
<!-- <video id="video" muted="" playsinline="" controls="" style="opacity: 1;"></video> -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount} from 'vue';
|
||||
import { useRoute } from 'vue-router'
|
||||
import bVideo from '/@/views/site/common/webRTC/video.vue';
|
||||
|
||||
|
||||
const bVideoRef = ref();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
//页面销毁时销毁webRtc
|
||||
onBeforeUnmount(() => {
|
||||
bVideoRef.value.closeWebRtcStreamerFn();
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if(route.query.url){
|
||||
bVideoRef.value.connectFn(route.query.url);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
video {
|
||||
//margin: auto;
|
||||
//left: 0;
|
||||
//right: 0;
|
||||
//position: relative;
|
||||
background: grey;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,40 @@
|
|||
// import { ref, nextTick, onBeforeUnmount} from 'vue';
|
||||
|
||||
export const rtcServerUrl = import.meta.env.VITE_GLOB_RTC_SERVER;
|
||||
|
||||
export function initWebRtcStreamer(domId) {
|
||||
const windowObj = <any>window;
|
||||
const WebRtcStreamer = windowObj.WebRtcStreamer;
|
||||
console.log('初始化连机器!',domId);
|
||||
|
||||
if(WebRtcStreamer){
|
||||
//获取webRtc服务
|
||||
|
||||
//video:需要绑定的video控件ID
|
||||
//127.0.0.1:8000:启动webrtc-streamer的设备IP和端口,默认8000
|
||||
// webRtcServer.value = new WebRtcStreamer('video', location.protocol + '//192.168.10.26:8000')
|
||||
const webRtcServer = new WebRtcStreamer(domId, rtcServerUrl);
|
||||
//需要查看的rtsp地址
|
||||
// webRtcServer.connect(`rtsp://176.139.87.16/axis-media/media.amp`)
|
||||
//windowObj.__webRtcServer = webRtcServer
|
||||
return webRtcServer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function connect(webRtcServer,videoUrl) {
|
||||
console.log('连接!',webRtcServer,videoUrl);
|
||||
|
||||
if(webRtcServer && videoUrl){
|
||||
console.log('连接2222!',webRtcServer,videoUrl);
|
||||
webRtcServer.connect(videoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export function closeWebRtcStreamer(webRtcServer) {
|
||||
console.log('注销!',webRtcServer);
|
||||
if(webRtcServer){
|
||||
webRtcServer.disconnect()
|
||||
// webRtcServer = null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<!-- <div @click="onPlay">点击开始播放(连接)直播</div>
|
||||
<div @click="onPlay2">点击开始播放(连接)直播2</div> -->
|
||||
<!-- <div>{{ route.query.url }}</div> -->
|
||||
<!-- <span>播放地址:{{ path }}</span> -->
|
||||
<video :id="props.videoKey" v-bind="videoOption"></video>
|
||||
|
||||
<!-- <video id="video" muted="" playsinline="" controls="" style="opacity: 1;"></video> -->
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, nextTick, onBeforeUnmount} from 'vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
import { initWebRtcStreamer, connect, closeWebRtcStreamer } from '/@/views/site/common/webRTC/util';
|
||||
|
||||
const props = defineProps({
|
||||
videoOption: propTypes.object.def({}),
|
||||
videoKey: propTypes.string.def('video'),
|
||||
});
|
||||
|
||||
const videoOption = reactive(Object.assign({
|
||||
// width: '800', //播放器高度
|
||||
// height: '450', //播放器高度
|
||||
color: "#409eff", //主题色
|
||||
title: '', //视频名称
|
||||
//src: "https://go.dreamwq.com/videos/IronMan.mp4", //视频源
|
||||
muted: true, //静音
|
||||
playsinline: true,
|
||||
webFullScreen: false,
|
||||
speedRate: ["0.75", "1.0", "1.25", "1.5", "2.0"], //播放倍速
|
||||
autoPlay: false, //自动播放
|
||||
loop: false, //循环播放
|
||||
mirror: false, //镜像画面
|
||||
ligthOff: false, //关灯模式
|
||||
volume: 0.3, //默认音量大小
|
||||
controls: true, //是否显示控制器
|
||||
},props.videoOption));
|
||||
|
||||
|
||||
let webRtcServer = ref(null);
|
||||
const path = ref('');
|
||||
|
||||
//获取webRtc服务
|
||||
nextTick(() => {
|
||||
//webRtcServer = initWebRtcStreamer('video');
|
||||
});
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
webRtcServer.value = initWebRtcStreamer(props.videoKey);
|
||||
})
|
||||
|
||||
function getVideo(){
|
||||
return document.querySelector('#'+props.videoKey)
|
||||
}
|
||||
function initWebRtcStreamerFn(){
|
||||
webRtcServer.value = initWebRtcStreamer(props.videoKey);
|
||||
return webRtcServer.value;
|
||||
}
|
||||
|
||||
function closeWebRtcStreamerFn(){
|
||||
closeWebRtcStreamer(webRtcServer.value);
|
||||
webRtcServer.value = null
|
||||
}
|
||||
|
||||
function connectFn(url:string,type?){
|
||||
let _path = `rtsp://${url}/stream/0?config.login=web`;
|
||||
if(type == 'external'){
|
||||
_path = url;
|
||||
}
|
||||
path.value = _path;
|
||||
connect(webRtcServer.value,path.value);
|
||||
return webRtcServer.value;
|
||||
}
|
||||
|
||||
//页面销毁时销毁webRtc
|
||||
onBeforeUnmount(() => {
|
||||
closeWebRtcStreamerFn()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
webRtcServer,
|
||||
getVideo,
|
||||
initWebRtcStreamerFn,
|
||||
closeWebRtcStreamerFn,
|
||||
connectFn,
|
||||
});
|
||||
// function onPlay(){
|
||||
// closeWebRtcStreamer(webRtcServer);
|
||||
// webRtcServer = initWebRtcStreamer('video');
|
||||
// if(webRtcServer){
|
||||
// connect(webRtcServer,'rtsp://176.139.87.16/axis-media/media.amp');
|
||||
// }
|
||||
// }
|
||||
|
||||
// function onPlay2(){
|
||||
// closeWebRtcStreamer(webRtcServer);
|
||||
// webRtcServer = initWebRtcStreamer('video');
|
||||
// if(webRtcServer){
|
||||
// connect(webRtcServer,'rtsp://77.110.228.219/axis-media/media.amp');
|
||||
// }
|
||||
// }
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
video {
|
||||
width: 100%;
|
||||
//height: 100%;
|
||||
//margin: auto;
|
||||
//left: 0;
|
||||
//right: 0;
|
||||
//position: relative;
|
||||
background: grey;
|
||||
}
|
||||
</style>
|
|
@ -130,7 +130,7 @@ const { createMessage } = useMessage();
|
|||
|
||||
const route = useRoute();
|
||||
onMounted(() => {
|
||||
console.log(route.query );
|
||||
console.log( route.query );
|
||||
let params = { pageSize: -1, status: 0, column: 'ordernum', order: 'asc' }
|
||||
let queryQue = defHttp.get({ url: Api.que, params: { ...params, evaluationver: route.query.type } });
|
||||
let queryAns = defHttp.get({ url: Api.ans, params });
|
||||
|
|
Loading…
Reference in New Issue