@@ -0,0 +1,70 @@ | |||||
* { | |||||
margin: 0; | |||||
padding: 0; | |||||
} | |||||
html, body { | |||||
height: 100%; | |||||
} | |||||
button { | |||||
padding: 1px 6px; | |||||
} | |||||
video { | |||||
background: #222; | |||||
margin: 0 0 0 0; | |||||
width: 320px; | |||||
height: 240px; | |||||
} | |||||
.container { | |||||
display: flex; | |||||
height: 100%; | |||||
flex-direction: column; | |||||
} | |||||
.top { | |||||
margin: 0.5em; | |||||
} | |||||
.middle { | |||||
flex: 1; | |||||
overflow: auto; | |||||
margin: 0 0.5em; | |||||
} | |||||
.log { | |||||
overflow: scroll; | |||||
width: 30em; | |||||
height: 100%; | |||||
background-color: #eeeeee; | |||||
float: left; | |||||
} | |||||
.preview { | |||||
width: auto; | |||||
height: 100%; | |||||
float: left; | |||||
} | |||||
.local { | |||||
margin: 0.5em 0.5em; | |||||
float: left; | |||||
} | |||||
.remote { | |||||
margin: 0.5em 0.5em 0.5em 0; | |||||
float: left; | |||||
} | |||||
.sdp { | |||||
width: 320px; | |||||
height: 200px; | |||||
margin: 0; | |||||
resize: none; | |||||
} | |||||
.bottom { | |||||
margin: 0.5em; | |||||
} |
@@ -0,0 +1,503 @@ | |||||
'use strict' | |||||
//本地视频预览窗口 | |||||
var localVideo = document.querySelector('video#localvideo'); | |||||
//远端视频预览窗口 | |||||
var remoteVideo = document.querySelector('video#remotevideo'); | |||||
//查看Offer文本窗口 | |||||
var offer = document.querySelector('textarea#offer'); | |||||
//查看Answer文本窗口 | |||||
var answer = document.querySelector('textarea#answer'); | |||||
var pcConfig = { | |||||
'iceServers': [{ | |||||
//TURN服务器地址 | |||||
'urls': 'turn:43.143.227.135:3478', | |||||
//TURN服务器用户名 | |||||
'username': "ghb", | |||||
//TURN服务器密码 | |||||
'credential': "moonshine" | |||||
}], | |||||
//默认使用relay方式传输数据 | |||||
"iceTransportPolicy": "relay", | |||||
"iceCandidatePoolSize": "0" | |||||
}; | |||||
//websocket连接 | |||||
var conn; | |||||
//会话描述 | |||||
var session; | |||||
//本地视频流 | |||||
var localStream = null; | |||||
//远端视频流 | |||||
var remoteStream = null; | |||||
//PeerConnection | |||||
var pc = null; | |||||
//offer描述 | |||||
var offerdesc = null; | |||||
/** | |||||
* 功能: 判断此浏览器是在PC端,还是移动端。 | |||||
* 返回值: false, 说明当前操作系统是移动端 | |||||
* true, 说明当前的操作系统是PC端。 | |||||
*/ | |||||
function IsPC() { | |||||
var userAgentInfo = navigator.userAgent; | |||||
var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; | |||||
var flag = true; | |||||
for (var v = 0; v < Agents.length; v++) { | |||||
if (userAgentInfo.indexOf(Agents[v]) > 0) { | |||||
flag = false; | |||||
break; | |||||
} | |||||
} | |||||
return flag; | |||||
} | |||||
/** | |||||
* 功能: 判断是Android端还是iOS端。 | |||||
* 返回值: true,说明是Android端 | |||||
* false,说明是iOS端。 | |||||
*/ | |||||
function IsAndroid() { | |||||
var u = navigator.userAgent, app = navigator, appVersion; | |||||
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; | |||||
var isIOS = !!u.match(/\(i[^;]+;( U;) ? CPU.+Mac OS X/); | |||||
if (isAndroid) { | |||||
//这个是Android系统 | |||||
return true; | |||||
} | |||||
if (isIOS) { | |||||
//这个是iOS系统 | |||||
return false; | |||||
} | |||||
} | |||||
/** | |||||
* 功能: 向对端发消息 | |||||
* 返回值: 无 | |||||
*/ | |||||
function sendMessage(roomid, data) { | |||||
console.log('send message to other end', roomid, data); | |||||
if (!socket) { | |||||
console.log('socket is null'); | |||||
} | |||||
socket.emit('message', roomid, data); | |||||
} | |||||
/** | |||||
*功能: 与信令服务器建立socket.io连接;并根据信令更新状态机。 | |||||
*返回值: 无 | |||||
*/ | |||||
function conn() { | |||||
//连接信令服务器 | |||||
socket = io.connect(); | |||||
//joined'消息处理函数 | |||||
socket.on('joined', (roomid, id) => { | |||||
console.log('receive joined message!', roomid, id); | |||||
//状态机变更为joined' | |||||
state = 'joined'; | |||||
/** | |||||
* 如果是Mesh方案,第一个人不该在这里创建 | |||||
* peerConnection,而是要等到所有端都收到一个'otherjoin'消息时再创建 | |||||
*/ | |||||
//创建PeerConnection 并绑定音视频轨 | |||||
createPeerConnection(); | |||||
bindTracks(); | |||||
//设置button状态 | |||||
btnConn.disabled = true; | |||||
btnLeave.disabled = false; | |||||
console.log('receive joined message, state = ', state); | |||||
}); | |||||
//otherjoin消息处理函数 | |||||
socket.on('other_join', (roomid) => { | |||||
console.log('receive joined message:', roomid, state); | |||||
//如果是多人,每加入一个人都要创建一个新的 PeerConnection | |||||
if (state === 'joined_unbind') { | |||||
createPeerConnection(); | |||||
bindTracks(); | |||||
} | |||||
//状态机变更为 joined_conn | |||||
state = 'joined_conn'; | |||||
//开始“呼叫”对方 | |||||
call1(); | |||||
console.log('receive other_join message, state = ', state); | |||||
}); | |||||
//full消息处理函数 | |||||
socket.on('full', (roomid, id) => { | |||||
console.log('receive full message', roomid, id); | |||||
//关闭gocket.io连接 | |||||
socket.disconnect(); | |||||
//挂断“呼叫” | |||||
hangup(); | |||||
//关闭本地媒体 | |||||
closeLocalMedia(); | |||||
//状态机变更为 leaved | |||||
state = 'leaved'; | |||||
console.log('receive full message, state = ', state); | |||||
alert('the room is full!'); | |||||
}); | |||||
//leaved消息处理函数 | |||||
socket.on('left', (roomid, id) => { | |||||
console.log('receive leaved message', roomid, id); | |||||
//状态机变更为leaved | |||||
state = 'leaved' | |||||
//关闭socket.io连接 | |||||
socket.disconnect(); | |||||
console.log('receive leaved message, state = ', state); | |||||
//改变button状态 | |||||
btnConn.disabled = false; | |||||
btnLeave.disabled = true; | |||||
}); | |||||
//bye消息处理函数 | |||||
socket.on('bye', (room, id) => { | |||||
console.log('receive bye message', roomid, id); | |||||
/** | |||||
* 当是Mesh方案时,应该带上当前房间的用户数, | |||||
* 如果当前房间用户数不小于 2,则不用修改状态, | |||||
* 并且关闭的应该是对应用户的PeerConnection。 | |||||
* 在客户端应该维护一张PeerConnection表,它是 | |||||
* key:value的格式,key=userid,value=peerconnection | |||||
*/ | |||||
//状态机变更为 joined_unbind | |||||
state = 'joined_unbind'; | |||||
//挂断“呼叫” | |||||
hangup(); | |||||
offer.value = ''; | |||||
answer.value = ''; | |||||
console.log('receive bye message, state=', state); | |||||
}); | |||||
//socket.io连接断开处理函数 | |||||
socket.on('disconnect', (socket) => { | |||||
console.log('receive disconnect message!', roomid); | |||||
if (!(state === 'leaved')) { | |||||
//挂断“呼叫” | |||||
hangup(); | |||||
//关闭本地媒体 | |||||
closeLocalMedia(); | |||||
} | |||||
//状态机变更为 leaved | |||||
state = 'leaved'; | |||||
}); | |||||
//收到对端消息处理函数 | |||||
socket.on('message', (roomid, data) => { | |||||
console.log('receive message!', roomid, data); | |||||
if( data === null || data === undefined) { | |||||
console.error('the message is invalid!'); | |||||
return; | |||||
} | |||||
//如果收到的SDP是offer | |||||
if (data.hasOwnProperty('type') && data.type === 'offer') { | |||||
offer.value = data.sdp; | |||||
//进行媒体协商 | |||||
pc.setRemoteDescription(new RTCSessionDescription(data)); | |||||
//创建answer | |||||
pc.createAnswer() | |||||
.then(getAnswer) | |||||
.catch(handleAnswerError); | |||||
//如果收到的SDP是answer | |||||
} else if (data.hasOwnProperty('type') && data.type == 'answer') { | |||||
answer.value = data.sdp; | |||||
//进行媒体协商 | |||||
pc.setRemoteDescription(new RTCSessionDescription(data)); | |||||
//如果收到的是Candidate消息 | |||||
} else if (data.hasOwnProperty('type') && data.type === 'candidate') { | |||||
var candidate = new RTCIceCandidate({ | |||||
sdpMLineIndex: data.label, | |||||
candidate: data.candidate | |||||
}); | |||||
//将远端Candidate消息添加到PeerConnection中 | |||||
pc.addIceCandidate(candidate); | |||||
} else { | |||||
console.log('the message is invalid!', data); | |||||
} | |||||
}); | |||||
//从url中获取roomid | |||||
roomid = getQueryVariable('room'); | |||||
//发送'join'消息 | |||||
socket.emit('join', roomid); | |||||
return true; | |||||
} | |||||
/** | |||||
* 功能: 打开音视频设备成功时的回调函数 | |||||
* 返回值: 永远为true | |||||
*/ | |||||
function getMediaStream(stream) { | |||||
//将从设备上获取到的音视频track添加到localStream中 | |||||
if (localStream) { | |||||
stream.getAudioTracks().forEach((track) => { | |||||
localStream.addTrack(track); | |||||
stream.removeTrack(track); | |||||
}); | |||||
} else { | |||||
localStream = stream; | |||||
} | |||||
//本地视频标签与本地流绑定 | |||||
localVideo.srcObject = localStream; | |||||
//创建PeerConnection 并绑定音视频轨 | |||||
createPeerConnection(); | |||||
bindTracks(); | |||||
} | |||||
/** | |||||
* 功能: 错误处理函数 | |||||
* | |||||
* 返回值: 无 | |||||
*/ | |||||
function handleError(err) { | |||||
console.error('Failed to get Media Stream!', err); | |||||
} | |||||
/** | |||||
*功能: 打开音视频设备 | |||||
*返回值: 无 | |||||
*/ | |||||
function startLocalMedia() { | |||||
if (!navigator.mediaDevices || | |||||
!navigator.mediaDevices.getUserMedia) { | |||||
console.error('the getUserMedia is not supported!'); | |||||
return; | |||||
} else { | |||||
var constraints; | |||||
constraints = { | |||||
video: true, | |||||
audio: { | |||||
echoCancellation: true, | |||||
noiseSuppression: true, | |||||
autoGainControl: true | |||||
} | |||||
}; | |||||
navigator.mediaDevices.getUserMedia(constraints) | |||||
.then(getMediaStream) | |||||
.catch(handleError); | |||||
} | |||||
} | |||||
/** | |||||
* 功能: 获得远端媒体流 | |||||
* 返回值: 无 | |||||
*/ | |||||
function getRemoteStream(e) { | |||||
//存放远端视频流 | |||||
remoteStream = e.streams[0]; | |||||
//远端视频标签与远端视频流绑定 | |||||
remoteVideo.srcObject = e.streams[0]; | |||||
} | |||||
/** | |||||
* 功能: 处理Offer错误 | |||||
* 返回值: 无 | |||||
*/ | |||||
function handleOfferError(err) { | |||||
console.error('Failed to create offer:', err); | |||||
} | |||||
/** | |||||
*功能:处理Answer错误 | |||||
*返回值:无 | |||||
*/ | |||||
function handleAnswerError(err) { | |||||
console.error('Failed to create answer;', err); | |||||
} | |||||
/** | |||||
*功能:获取Answer SDP 描述符的回调函数 | |||||
* | |||||
*返回值:无 | |||||
*/ | |||||
function getAnswer(desc) { | |||||
//设置Answer | |||||
pc.setLocalDescription(desc); | |||||
//将Answer显示出来 | |||||
answer.value = desc.sdp; | |||||
//将AnswerSDP发送给对端 | |||||
let req = new Object; | |||||
req.cmd = 'CMD_WEBRTC'; | |||||
req.data = desc; | |||||
let str = JSON.stringify(req); | |||||
console.log(str); | |||||
conn.send(str); | |||||
} | |||||
/** | |||||
*功能:获取Offer SDP 描述符的回调函数 | |||||
*返回值:无 | |||||
*/ | |||||
function getOffer(desc) { | |||||
//设置Offer | |||||
pc.setLocalDescription(desc); | |||||
//将Offer显示出来 | |||||
offer.value = desc.sdp; | |||||
offerdesc = desc; | |||||
console.log(offerdesc) | |||||
//将 OfferSDP发送给对端 | |||||
let req = new Object; | |||||
req.cmd = 'CMD_WEBRTC'; | |||||
req.data = desc; | |||||
let str = JSON.stringify(req); | |||||
console.log(str); | |||||
conn.send(str); | |||||
} | |||||
/** | |||||
* 功能: 创建PeerConnection对象 | |||||
* 返回值: 无 | |||||
*/ | |||||
function createPeerConnection() { | |||||
/* | |||||
* 如果是多人的话,在这里要创建一个新的连接 | |||||
* 新创建好的要放到一个映射表中 | |||||
*/ | |||||
//key=userid, value=peerconnection | |||||
console.log('create RTCPeerConnection!'); | |||||
if (!pc) { | |||||
//创建PeerConnection对象 | |||||
pc = new RTCPeerConnection(pcConfig); | |||||
//当收集到Candidate后 | |||||
pc.onicecandidate = (event) => { | |||||
if (event.candidate) { | |||||
console.log("candidate" + JSON.stringify(event.candidate.toJSON())); | |||||
//将Candidate发送给对端 | |||||
let req = new Object; | |||||
req.cmd = 'CMD_WEBRTC'; | |||||
req.data = { | |||||
type: 'candidate', | |||||
label: event.candidate.sdpMLineIndex, | |||||
id: event.candidate.sdpMid, | |||||
candidate: event.candidate.candidate | |||||
}; | |||||
let str = JSON.stringify(req); | |||||
console.log(str); | |||||
conn.send(str); | |||||
} else { | |||||
console.log('this is the end candidate'); | |||||
} | |||||
} | |||||
/** | |||||
* 当PeerConnection对象收到远端音视频流时 | |||||
* 触发ontrack事件,并回调getRemoteStream函数 | |||||
*/ | |||||
pc.ontrack = getRemoteStream; | |||||
} else { | |||||
console.log('the pc have be created!'); | |||||
} | |||||
return; | |||||
} | |||||
/** | |||||
* 功能: 将音视频track绑定到PeerConnection对象中 | |||||
* 返回值: 无 | |||||
*/ | |||||
function bindTracks() { | |||||
console.log('bind tracks into RTCPeerConnection!'); | |||||
if (pc === null && localStream === undefined) { | |||||
console.error('pc is null or undefined!'); | |||||
return; | |||||
} | |||||
if (localStream === null && localStream === undefined) { | |||||
console.error('localstream is null or undefined!'); | |||||
return; | |||||
} | |||||
//将本地音视频流中所有的track添加到PeerConnection对象中 | |||||
localStream.getTracks().forEach((track)=>{ | |||||
pc.addTrack(track, localStream); | |||||
}); | |||||
} | |||||
/** | |||||
* 功能: 开启“呼叫” | |||||
* 返回值: 无 | |||||
*/ | |||||
function videoCall() { | |||||
var offerOptions = { | |||||
offerToReceiveAudio: 1, | |||||
offerToReceiveVideo: 1 | |||||
}; | |||||
/** | |||||
* 创建Offer, | |||||
* 如果成功,则回调getoffer()方法 | |||||
* 如果失败,则回调handleofferError()方法 | |||||
*/ | |||||
pc.createOffer(offerOptions) | |||||
.then(getOffer) | |||||
.catch(handleOfferError); | |||||
} | |||||
/** | |||||
* 功能: 挂断“呼叫” | |||||
* 返回值: 无 | |||||
*/ | |||||
function hangup() { | |||||
if (!pc) { | |||||
return; | |||||
} | |||||
//发送“挂断”消息 | |||||
let r = new Object; | |||||
r.cmd = "CMD_HANGUP"; | |||||
let str = JSON.stringify(r); | |||||
conn.send(str); | |||||
/* | |||||
offerdesc = null; | |||||
//将PeerConnection连接关掉 | |||||
pc.close(); | |||||
pc = null; | |||||
*/ | |||||
} | |||||
/** | |||||
* 功能:关闭本地媒体 | |||||
* 返回值:无 | |||||
*/ | |||||
function closeLocalMedia() { | |||||
if (!(localStream === null || localStream === undefined)) { | |||||
//遍历每个track,并将其关闭 | |||||
localStream.getTracks().forEach((track) => { | |||||
track.stop(); | |||||
}); | |||||
} | |||||
localStream = null; | |||||
} | |||||
/** | |||||
*功能:离开房间 | |||||
*返回值:无 | |||||
*/ | |||||
function leave() { | |||||
//向信令服务器发送leave消息 | |||||
socket.emit('leave', roomid); | |||||
//挂断“呼叫” | |||||
hangup(); | |||||
//关闭本地媒体 | |||||
closeLocalMedia(); | |||||
offer.value = ''; | |||||
answer.value = ''; | |||||
} | |||||
@@ -0,0 +1,196 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<title>用户页面</title> | |||||
<link href="./css/client.css" rel="stylesheet"/> | |||||
<script type="text/javascript"> | |||||
var username; | |||||
var msg; | |||||
var log; | |||||
window.onload = function () { | |||||
username = document.getElementById("username"); | |||||
msg = document.getElementById("msg"); | |||||
log = document.getElementById("log"); | |||||
if (window["WebSocket"]) { | |||||
// 连接服务器 | |||||
conn = new WebSocket("wss://" + document.location.host + "/webrtc"); | |||||
conn.onclose = function (evt) { | |||||
appendLog("连接已断开."); | |||||
}; | |||||
conn.onmessage = function (e) { | |||||
res = JSON.parse(e.data) | |||||
if (res.cmd == 'CMD_USER_LOGIN_ACK') { | |||||
if (res.error === 0) { | |||||
appendLog("登录成功。"); | |||||
// 启动本地音视频 | |||||
startLocalMedia(); | |||||
} else { | |||||
appendLog("登录失败,error: " + res.error); | |||||
} | |||||
} else if (res.cmd === 'CMD_CALL_ACK') { | |||||
if (res.error === 0) | |||||
appendLog("呼叫成功。"); | |||||
else | |||||
appendLog("呼叫失败,error: " + res.error); | |||||
} else if (res.cmd === 'CMD_SESSION_BEGIN') { | |||||
session = new Object; | |||||
session.staffer = res.staffer; | |||||
session.user = res.user; | |||||
session.iceServers = res.iceServers | |||||
appendLog("开始由 " + res.staffer + " 为您服务"); | |||||
console.log(session); | |||||
if (!pc) { | |||||
createPeerConnection(); | |||||
bindTracks(); | |||||
} | |||||
videoCall(); | |||||
} else if (res.cmd === 'CMD_SESSION_END') { | |||||
if (res.error === 0) | |||||
appendLog("会话已结束。"); | |||||
else | |||||
appendLog("会话异常结束,error: " + res.error); | |||||
if (pc != null) { | |||||
pc.close(); | |||||
pc = null; | |||||
offerdesc = null; | |||||
offer.value = ""; | |||||
answer.value = ""; | |||||
} | |||||
} else if (res.cmd === 'CMD_CHAT') { | |||||
appendLog(session.staffer + " 说:" + res.text); | |||||
} else if (res.cmd === 'CMD_WEBRTC') { | |||||
//如果收到的SDP是offer | |||||
if (res.data.hasOwnProperty('type') && res.data.type === 'offer') { | |||||
offer.value = res.data.sdp; | |||||
//进行媒体协商 | |||||
pc.setRemoteDescription(new RTCSessionDescription(res.data)); | |||||
//创建answer | |||||
pc.createAnswer() | |||||
.then(getAnswer) | |||||
.catch(handleAnswerError); | |||||
//如果收到的SDP是answer | |||||
} else if (res.data.hasOwnProperty('type') && res.data.type == 'answer') { | |||||
answer.value = res.data.sdp; | |||||
//进行媒体协商 | |||||
pc.setRemoteDescription(new RTCSessionDescription(res.data)); | |||||
//如果收到的是Candidate消息 | |||||
} else if (res.data.hasOwnProperty('type') && res.data.type === 'candidate') { | |||||
var candidate = new RTCIceCandidate({ | |||||
sdpMLineIndex: res.data.label, | |||||
candidate: res.data.candidate | |||||
}); | |||||
//将远端Candidate消息添加到PeerConnection中 | |||||
pc.addIceCandidate(candidate); | |||||
} | |||||
} | |||||
}; | |||||
} else { | |||||
appendLog("Your browser does not support WebSockets."); | |||||
} | |||||
}; | |||||
function appendLog(text) { | |||||
let item = document.createElement("div"); | |||||
item.innerText = text; | |||||
let doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1; | |||||
log.appendChild(item); | |||||
if (doScroll) { | |||||
log.scrollTop = log.scrollHeight - log.clientHeight; | |||||
} | |||||
} | |||||
function login() { | |||||
let req = new Object; | |||||
req.cmd = "CMD_USER_LOGIN"; | |||||
req.username = username.value; | |||||
req.password = "123456"; | |||||
req.agent = "browser"; | |||||
let str = JSON.stringify(req); | |||||
console.log(str); | |||||
conn.send(str); | |||||
} | |||||
function chat() { | |||||
if (!conn) { | |||||
return false; | |||||
} | |||||
if (!msg.value) { | |||||
return false; | |||||
} | |||||
appendLog("我说:" + msg.value); | |||||
let req = new Object; | |||||
req.cmd = "CMD_CHAT"; | |||||
req.text = msg.value; | |||||
let str = JSON.stringify(req); | |||||
console.log(str); | |||||
conn.send(str); | |||||
msg.value = ""; | |||||
return false; | |||||
} | |||||
function call() { | |||||
let req = new Object; | |||||
req.cmd = "CMD_CALL"; | |||||
req.caller = username.value; | |||||
req.callee = ""; | |||||
let str = JSON.stringify(req); | |||||
console.log(str); | |||||
conn.send(str); | |||||
} | |||||
</script> | |||||
</head> | |||||
<body> | |||||
<div class="container"> | |||||
<div class="top"> | |||||
<label>用户账号</label> | |||||
<input id="username"></input> | |||||
<button id="login" onclick="login()">登录</button> | |||||
<button id="call" onclick="call()">呼叫</button> | |||||
<button id="call" onclick="hangup()">挂断</button> | |||||
</div> | |||||
<div class="middle"> | |||||
<div class="log" id="log"></div> | |||||
<div class="preview"> | |||||
<div class="local"> | |||||
<p>Local:</p> | |||||
<video id="localvideo" autoplay playsinline muted></video> | |||||
<p>Offer SDP:</p> | |||||
<textarea class="sdp" id="offer"></textarea> | |||||
</div> | |||||
<div class="remote"> | |||||
<p>Remote:</p> | |||||
<video id="remotevideo" autoplay playsinline></video> | |||||
<p>Answer SDP:</p> | |||||
<textarea class="sdp" id="answer"></textarea> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="bottom"> | |||||
<textarea id="msg" cols="70" rows="6" style="overflow: visible; resize: none"></textarea> | |||||
<button id="send" style="vertical-align: top;" onclick="chat()">发送</button> | |||||
</div> | |||||
</div> | |||||
<!-- | |||||
引用的JavaScript脚本库: | |||||
socket.io.js:用于连接信令服务器 | |||||
adapter-latest.js:用于浏览器适配Chrome,Firefox... | |||||
client.js:WebRTC客户端代码 | |||||
--> | |||||
<script src="js/adapter-latest.js"></script> | |||||
<script src="js/client.js"></script> | |||||
</body> | |||||
</html> |
@@ -5,15 +5,20 @@ | |||||
<div class="userInfo"> | <div class="userInfo"> | ||||
<div class="nickName" @click="go('/myPage')">用户名</div> | <div class="nickName" @click="go('/myPage')">用户名</div> | ||||
<div class="userModel"> | <div class="userModel"> | ||||
<img | |||||
<!-- <img | |||||
class="userImg" | class="userImg" | ||||
src="../../assets/img/user_black.png" | src="../../assets/img/user_black.png" | ||||
@click="go('/myPage')" | @click="go('/myPage')" | ||||
/> | |||||
<img | |||||
/> --> | |||||
<!-- <img | |||||
class="userImg" | class="userImg" | ||||
src="../../assets/img/loginOut.png" | src="../../assets/img/loginOut.png" | ||||
@click="loginOut()" | @click="loginOut()" | ||||
/> --> | |||||
<img | |||||
class="userImg" | |||||
src="../../assets/icon/icon-loginout.png" | |||||
@click="loginOut()" | |||||
/> | /> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -100,14 +105,15 @@ export default { | |||||
font-weight: 600; | font-weight: 600; | ||||
font-size: 13px; | font-size: 13px; | ||||
margin-right: 15px; | margin-right: 15px; | ||||
margin-top: 3px; | |||||
} | } | ||||
.userModel { | .userModel { | ||||
position: relative; | position: relative; | ||||
top: 3px; | top: 3px; | ||||
margin-right: 10px; | margin-right: 10px; | ||||
.userImg { | .userImg { | ||||
width: 30px; | |||||
height: 30px; | |||||
width: 24px; | |||||
height: 24px; | |||||
margin-right: 2px; | margin-right: 2px; | ||||
} | } | ||||
} | } | ||||
@@ -44,19 +44,24 @@ const routes = [ | |||||
name: 'chooseModel', | name: 'chooseModel', | ||||
component: () => import("../views/model/chooseModel"), | component: () => import("../views/model/chooseModel"), | ||||
}, | }, | ||||
// 模板预览 | |||||
// 模板预览new | |||||
{ | { | ||||
path: '/previewModel', | path: '/previewModel', | ||||
name: 'previewModel', | name: 'previewModel', | ||||
component: () => import("../views/model/previewModel"), | component: () => import("../views/model/previewModel"), | ||||
}, | }, | ||||
// 编辑模板 | |||||
// 编辑模板new | |||||
{ | { | ||||
path: '/editModel', | path: '/editModel', | ||||
name: 'editModel', | name: 'editModel', | ||||
component: () => import("../views/model/editModel"), | component: () => import("../views/model/editModel"), | ||||
}, | }, | ||||
// 聊天首页new | |||||
{ | |||||
path: '/chat', | |||||
name: 'chat', | |||||
component: () => import("@/views/chat/chat"), | |||||
}, | |||||
// 录入文案 | // 录入文案 | ||||
{ | { | ||||
path: '/getPaper', | path: '/getPaper', | ||||
@@ -0,0 +1,61 @@ | |||||
<template> | |||||
<div :class="inPage ? 'page active' : 'page'"> | |||||
<div id="top"></div> | |||||
<!-- 主体内容 --> | |||||
<div class="contnet"> | |||||
<!-- 头部 --> | |||||
<div class="head"> | |||||
<img src="" alt=""> | |||||
<div>退出聊天</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
// 组件 | |||||
import Vue from "vue"; | |||||
import { Toast } from "vant"; | |||||
import { mapState, mapActions } from "vuex"; | |||||
// 工具 | |||||
import { scrollToID } from "../../utils"; | |||||
Vue.use(Toast); | |||||
export default { | |||||
data() { | |||||
return { | |||||
inPage: false, | |||||
}; | |||||
}, | |||||
computed: { | |||||
...mapState({ | |||||
characters: (state) => state.publicObj.characters, | |||||
}), | |||||
}, | |||||
methods: { | |||||
...mapActions(["setCharacters"]), | |||||
go(url) { | |||||
this.$router.push(url); | |||||
}, | |||||
}, | |||||
created() {}, | |||||
mounted() { | |||||
this.inPage = true; | |||||
scrollToID("top"); | |||||
}, | |||||
}; | |||||
</script> | |||||
<style lang="scss" scoped> | |||||
.page { | |||||
transform: translateX(30%); | |||||
opacity: 0; | |||||
transition: all 0.5s; // global-transition | |||||
&.active { | |||||
opacity: 1; | |||||
transform: translateX(0); | |||||
} | |||||
} | |||||
</style> |
@@ -3,13 +3,14 @@ | |||||
<div id="top"></div> | <div id="top"></div> | ||||
<HeadTop /> | <HeadTop /> | ||||
<!-- 顶部tab栏 --> | <!-- 顶部tab栏 --> | ||||
<van-tabs animated color="#7264F5" line-width=46 line-height=6 title-inactive-color="#666" title-active-color="#333"> | |||||
<van-tab v-for="item in tabList" :key="item.id" :title="item.name"> | |||||
<van-tabs @click="chooseType1" class="typeOutSide" animated color="#7264F5" line-width=46 line-height=6 title-inactive-color="#666" title-active-color="#333"> | |||||
<van-tab v-for="item in tabList" :key="item" :title="item"> | |||||
</van-tab> | </van-tab> | ||||
</van-tabs> | </van-tabs> | ||||
<div class="title">选择模板</div> | |||||
<div class="typeOutSide"> | |||||
<!-- <div class="title">选择模板</div> --> | |||||
<!-- 老版吸顶tab栏 --> | |||||
<!-- <div class="typeOutSide"> | |||||
<div class="type"> | <div class="type"> | ||||
<div :class="type == 0 ? 'active' : ''" @click="chooseType(0)"> | <div :class="type == 0 ? 'active' : ''" @click="chooseType(0)"> | ||||
全部模板 | 全部模板 | ||||
@@ -21,7 +22,7 @@ | |||||
男性模板 | 男性模板 | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | |||||
</div> --> | |||||
<!-- 模板列表 --> | <!-- 模板列表 --> | ||||
<div class="choose"> | <div class="choose"> | ||||
<div class="modeList"> | <div class="modeList"> | ||||
@@ -67,15 +68,7 @@ export default { | |||||
type: 0, | type: 0, | ||||
currentId: "", | currentId: "", | ||||
modeList: [], // 模板列表 | modeList: [], // 模板列表 | ||||
tabList: [ | |||||
{ id: 1, name: '全部' }, | |||||
{ id: 2, name: '男性' }, | |||||
{ id: 3, name: '女性' }, | |||||
{ id: 4, name: '原P' }, | |||||
{ id: 5, name: '武装直升机' }, | |||||
{ id: 6, name: '粥P' }, | |||||
{ id: 7, name: '二刺猿' }, | |||||
] | |||||
tabList: ['全部','男性','女性','原P','武装直升机','粥P','二刺猿'] | |||||
}; | }; | ||||
}, | }, | ||||
computed: { | computed: { | ||||
@@ -86,7 +79,11 @@ export default { | |||||
methods: { | methods: { | ||||
...mapActions(["setCharacters"]), | ...mapActions(["setCharacters"]), | ||||
//点击tab栏选择模板列表 | |||||
chooseType1(name, title) { | |||||
this.type = this.tabList.findIndex(item => item==title) | |||||
this.loadModeList(this.type); | |||||
}, | |||||
globalWatcher(e) { | globalWatcher(e) { | ||||
console.log(e, "e"); | console.log(e, "e"); | ||||
}, | }, | ||||
@@ -99,10 +96,10 @@ export default { | |||||
this.phase = num; | this.phase = num; | ||||
}, | }, | ||||
chooseType(num) { | |||||
this.type = num; | |||||
this.loadModeList(num); | |||||
}, | |||||
// chooseType(num) { | |||||
// this.type = num; | |||||
// this.loadModeList(num); | |||||
// }, | |||||
selectModel(id) { | selectModel(id) { | ||||
if (this.currentId != id) { | if (this.currentId != id) { | ||||
@@ -235,9 +232,9 @@ export default { | |||||
} | } | ||||
.typeOutSide { | .typeOutSide { | ||||
position: sticky; | position: sticky; | ||||
top: 60px; | |||||
top: 0px; | |||||
z-index: 99; | z-index: 99; | ||||
.type { | .type { | ||||
display: flex; | display: flex; | ||||
justify-content: space-between; | justify-content: space-between; | ||||
@@ -265,6 +262,7 @@ export default { | |||||
} | } | ||||
.choose { | .choose { | ||||
padding-top: 20px; | |||||
margin-bottom: 40px; | margin-bottom: 40px; | ||||
.modeList { | .modeList { | ||||
@@ -7,15 +7,14 @@ | |||||
<!-- 展示图片 --> | <!-- 展示图片 --> | ||||
<div v-if="imgUrl" class="shouModelImg"> | <div v-if="imgUrl" class="shouModelImg"> | ||||
<img :src="imgUrl" alt=""> | <img :src="imgUrl" alt=""> | ||||
<div v-show="showSubtitle" class="shouModelImg-subtitle"> | |||||
中午吃啥? | |||||
</div> | |||||
<!-- 字幕 --> | |||||
<div v-html="textareaContent" v-show="showSubtitle" class="shouModelImg-subtitle"></div> | |||||
</div> | </div> | ||||
<!-- 文字 --> | <!-- 文字 --> | ||||
<div class="addText"> | <div class="addText"> | ||||
<div class="addText-title">添加文案</div> | <div class="addText-title">添加文案</div> | ||||
<div class="desalinate"> | <div class="desalinate"> | ||||
<span>已录入65字</span> | |||||
<span>已录入{{textareaContent.length}}字</span> | |||||
<span>视频时长: 00:16</span> | <span>视频时长: 00:16</span> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -23,7 +22,11 @@ | |||||
<div class="textInput"> | <div class="textInput"> | ||||
<div class="textInput-in"> | <div class="textInput-in"> | ||||
<!-- 标题 --> | <!-- 标题 --> | ||||
<el-input v-model="inputTitle" placeholder="请输入标题"></el-input> | |||||
<el-input v-model="inputTitle" placeholder="请输入标题"> | |||||
<template slot="append"> | |||||
<img src="../../assets/icon/icon-pencil.png" alt=""> | |||||
</template> | |||||
</el-input> | |||||
<div class="divider" /> | <div class="divider" /> | ||||
<!-- 语音内容 --> | <!-- 语音内容 --> | ||||
<el-input rows="4" type="textarea" v-model="textareaContent"></el-input> | <el-input rows="4" type="textarea" v-model="textareaContent"></el-input> | ||||
@@ -54,7 +57,7 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<!-- 底部导航栏 --> | <!-- 底部导航栏 --> | ||||
<div class="bottom"> | <div class="bottom"> | ||||
<van-tabbar class="bottom-tab"> | <van-tabbar class="bottom-tab"> | ||||
@@ -101,7 +104,7 @@ | |||||
<!-- 顶部文字 --> | <!-- 顶部文字 --> | ||||
<div class="tabSheet-title"> | <div class="tabSheet-title"> | ||||
<div><img class="img" src="../../assets/icon/icon-microphone.png" alt=""> 更改声音</div> | <div><img class="img" src="../../assets/icon/icon-microphone.png" alt=""> 更改声音</div> | ||||
<div><img src="../../assets/icon/icon-yes.png" alt=""></div> | |||||
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div> | |||||
</div> | </div> | ||||
<!-- 主要内容 --> | <!-- 主要内容 --> | ||||
<div class="tabSheet-content"> | <div class="tabSheet-content"> | ||||
@@ -124,7 +127,7 @@ | |||||
<!-- 顶部文字 --> | <!-- 顶部文字 --> | ||||
<div class="tabSheet-title"> | <div class="tabSheet-title"> | ||||
<div><img class="img" src="../../assets/icon/icon-background.png" alt=""> 更换背景</div> | <div><img class="img" src="../../assets/icon/icon-background.png" alt=""> 更换背景</div> | ||||
<div><img src="../../assets/icon/icon-yes.png" alt=""></div> | |||||
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div> | |||||
</div> | </div> | ||||
<!-- 主要内容 --> | <!-- 主要内容 --> | ||||
<div class="tabSheet-content"> | <div class="tabSheet-content"> | ||||
@@ -143,7 +146,18 @@ | |||||
<input type="file"> | <input type="file"> | ||||
</div> | </div> | ||||
<!-- 图片列表 --> | <!-- 图片列表 --> | ||||
<div @click="bjImgAction=index" class="myTab-show-list-item" :class="{'myTab-show-list-active':bjImgAction==index}" v-for="(item,index) in 7" :key="index" ></div> | |||||
<div @click="bjImgAction=index" class="myTab-show-list-item" v-for="(item,index) in 7" :key="index" > | |||||
<div :class=" {'myTab-show-list-active':bjImgAction==index}"> | |||||
<div class="whiteBox"> | |||||
</div> | |||||
</div> | |||||
<!-- 删除上传素材 --> | |||||
<div class="icon-error" v-if="bjAction==1&&bjImgAction==index"> | |||||
× | |||||
</div> | |||||
<!-- 中间图片 --> | |||||
<img src="" alt=""> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -153,7 +167,7 @@ | |||||
<!-- 顶部文字 --> | <!-- 顶部文字 --> | ||||
<div class="tabSheet-title"> | <div class="tabSheet-title"> | ||||
<div><img class="img" src="../../assets/icon/icon-material.png" alt=""> 更换素材</div> | <div><img class="img" src="../../assets/icon/icon-material.png" alt=""> 更换素材</div> | ||||
<div><img src="../../assets/icon/icon-yes.png" alt=""></div> | |||||
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div> | |||||
</div> | </div> | ||||
<!-- 主要内容 --> | <!-- 主要内容 --> | ||||
<div class="tabSheet-content"> | <div class="tabSheet-content"> | ||||
@@ -167,12 +181,23 @@ | |||||
<div class="myTab-show-title">图片<i></i></div> | <div class="myTab-show-title">图片<i></i></div> | ||||
<div class="myTab-show-list"> | <div class="myTab-show-list"> | ||||
<!-- 上传 --> | <!-- 上传 --> | ||||
<div v-if="bjAction==1" class="myTab-show-list-upload"> | |||||
<div v-if="scAction==1" class="myTab-show-list-upload"> | |||||
<img src="../../assets/icon/icon-upload@2x.png" alt=""> | <img src="../../assets/icon/icon-upload@2x.png" alt=""> | ||||
<input type="file"> | <input type="file"> | ||||
</div> | </div> | ||||
<!-- 图片列表 --> | <!-- 图片列表 --> | ||||
<div @click="scImgAction=index" class="myTab-show-list-item" :class="{'myTab-show-list-active':scImgAction==index}" v-for="(item,index) in 7" :key="index" ></div> | |||||
<div @click="scImgAction=index" class="myTab-show-list-item" v-for="(item,index) in 7" :key="index" > | |||||
<div :class=" {'myTab-show-list-active':scImgAction==index}"> | |||||
<div class="whiteBox"> | |||||
</div> | |||||
</div> | |||||
<!-- 删除上传素材 --> | |||||
<div class="icon-error" v-if="scAction==1&&scImgAction==index"> | |||||
× | |||||
</div> | |||||
<!-- 中间图片 --> | |||||
<img src="" alt=""> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -201,19 +226,19 @@ export default { | |||||
imgUrl:null, | imgUrl:null, | ||||
modelId: null, | modelId: null, | ||||
inputTitle: '', | inputTitle: '', | ||||
textareaContent: '', | |||||
textareaContent: '', // 输入框内容 | |||||
bottomTabActive: 2,// 底部导航高亮 | bottomTabActive: 2,// 底部导航高亮 | ||||
showSubtitle: true, // 是否展示字幕 | showSubtitle: true, // 是否展示字幕 | ||||
showSynthesisDialog: false,//合成进度条窗口显示隐藏 | showSynthesisDialog: false,//合成进度条窗口显示隐藏 | ||||
showTabSheet: true, //tab栏弹出框显隐 | |||||
showTabSheet: false, //tab栏弹出框显隐 | |||||
soundTabList: ['男生', '女生', '御姐', '萝莉'], // 声音tab栏标题 | soundTabList: ['男生', '女生', '御姐', '萝莉'], // 声音tab栏标题 | ||||
soundListAction:0,//声音tab栏标题高亮 | |||||
soundListAction:-1,//声音tab栏标题高亮 | |||||
soundItemList: ['普通话', '英语', '日语', '日语', '日语', '日语', '日语'],// 声音展示列表 | soundItemList: ['普通话', '英语', '日语', '日语', '日语', '日语', '日语'],// 声音展示列表 | ||||
soundItemListAction:0,// 声音展示列表高亮 | |||||
bjAction:0,// 背景:素材库和上传高亮 | |||||
bjImgAction:0,// 选中背景图高亮 | |||||
scAction:0,// 素材:素材库和上传高亮 | |||||
scImgAction:0,// 选中素材图高亮 | |||||
soundItemListAction:-1,// 声音展示列表高亮 | |||||
bjAction:-1,// 背景:素材库和上传高亮 | |||||
bjImgAction:-1,// 选中背景图高亮 | |||||
scAction:-1,// 素材:素材库和上传高亮 | |||||
scImgAction:-1,// 选中素材图高亮 | |||||
}; | }; | ||||
}, | }, | ||||
computed: { | computed: { | ||||
@@ -252,6 +277,7 @@ export default { | |||||
<style lang="scss" scoped> | <style lang="scss" scoped> | ||||
.page { | .page { | ||||
// transform会导致其子元素固定定位失效 | // transform会导致其子元素固定定位失效 | ||||
// transform: translateX(30%); | // transform: translateX(30%); | ||||
opacity: 0; | opacity: 0; | ||||
@@ -265,7 +291,7 @@ export default { | |||||
position: relative; | position: relative; | ||||
padding: 0 10px; | padding: 0 10px; | ||||
padding-bottom: 50px; | padding-bottom: 50px; | ||||
width: 375px; | |||||
.shouModelImg { | .shouModelImg { | ||||
display: flex; | display: flex; | ||||
@@ -275,6 +301,7 @@ export default { | |||||
img { | img { | ||||
width: 200px; | width: 200px; | ||||
height: 351px; | height: 351px; | ||||
border-radius: 12px; | |||||
}; | }; | ||||
&-subtitle { | &-subtitle { | ||||
position: absolute; | position: absolute; | ||||
@@ -312,6 +339,15 @@ export default { | |||||
height: 100%; | height: 100%; | ||||
background: #fff; | background: #fff; | ||||
border-radius: 6px; | border-radius: 6px; | ||||
::v-deep .el-input-group__append { | |||||
padding: 0; | |||||
} | |||||
img { | |||||
float: right; | |||||
width: 16px; | |||||
height: 16px; | |||||
margin: 10px; | |||||
} | |||||
} | } | ||||
.divider { | .divider { | ||||
transform: scaleY (0.25); | transform: scaleY (0.25); | ||||
@@ -331,7 +367,7 @@ export default { | |||||
} | } | ||||
.makeVideo { | .makeVideo { | ||||
margin: 15px 20px 0; | margin: 15px 20px 0; | ||||
width: 315px; | |||||
height: 50px; | height: 50px; | ||||
text-align: center; | text-align: center; | ||||
font-size: 20px; | font-size: 20px; | ||||
@@ -373,6 +409,7 @@ export default { | |||||
} | } | ||||
} | } | ||||
.bottom { | .bottom { | ||||
::v-deep .van-tabbar-item { | ::v-deep .van-tabbar-item { | ||||
color: rgba(51, 51, 51, 1)!important; | color: rgba(51, 51, 51, 1)!important; | ||||
font-size: 10px !important; | font-size: 10px !important; | ||||
@@ -428,6 +465,11 @@ export default { | |||||
width: 16px; | width: 16px; | ||||
height: 16px; | height: 16px; | ||||
} | } | ||||
.yesImg { | |||||
margin-top: -13px; | |||||
width: 24px; | |||||
height: 16px; | |||||
} | |||||
} | } | ||||
&-content { | &-content { | ||||
height: 220px; | height: 220px; | ||||
@@ -503,7 +545,6 @@ export default { | |||||
&-show { | &-show { | ||||
width: 100%; | width: 100%; | ||||
height: 190px; | height: 190px; | ||||
background-color: green; | |||||
&-title { | &-title { | ||||
font-size: 16px; | font-size: 16px; | ||||
i { | i { | ||||
@@ -518,6 +559,7 @@ export default { | |||||
} | } | ||||
&-list { | &-list { | ||||
height: 145px; | height: 145px; | ||||
padding-left: 4px; | |||||
overflow-y: scroll; | overflow-y: scroll; | ||||
display: flex; | display: flex; | ||||
justify-content: start; | justify-content: start; | ||||
@@ -526,24 +568,58 @@ export default { | |||||
width:0; | width:0; | ||||
} | } | ||||
&-item { | &-item { | ||||
margin-right: 10px; | |||||
position: relative; | |||||
margin-right: 9px; | |||||
margin-top: 10px; | margin-top: 10px; | ||||
width: 80px; | width: 80px; | ||||
height: 120px; | height: 120px; | ||||
background: red; | |||||
z-index: 9; | |||||
img { | |||||
background: red; | |||||
width: 80px; | |||||
height: 120px; | |||||
border-radius: 12px; | |||||
} | |||||
border-radius: 10px; | border-radius: 10px; | ||||
.icon-error { | |||||
float: left; | |||||
position: absolute; | |||||
top: 5px; | |||||
right: 5px; | |||||
z-index: 99999; | |||||
display: block!important; | |||||
float: right; | |||||
width: 12px; | |||||
height: 12px; | |||||
line-height: 12px; | |||||
text-align: center; | |||||
color: #fff; | |||||
background: #666666; | |||||
border-radius: 6px; | |||||
} | |||||
&:nth-child(4n) { | &:nth-child(4n) { | ||||
margin-right: 0px; | |||||
margin-right: 0px!important; | |||||
} | } | ||||
} | } | ||||
&-active { | &-active { | ||||
margin-right: 8px; | |||||
margin-top: 8px; | |||||
padding: 2px; | |||||
width: 84px; | |||||
height: 124px; | |||||
border: 1px solid; | |||||
border-image: linear-gradient(90deg, rgba(114, 100, 245, 1), rgba(81, 217, 202, 1)) 1 1; | |||||
position: absolute; | |||||
top: -3px; | |||||
left: -3px; | |||||
padding: 1px; | |||||
z-index: -1; | |||||
width: 86px; | |||||
height: 126px; | |||||
border-radius: 12px; | |||||
background: linear-gradient(225deg, rgba(81, 217, 202, 1), rgba(114, 100, 245, 1)); | |||||
.whiteBox { | |||||
transition: all 0.5s; | |||||
width: 100%; | |||||
height: 100%; | |||||
background-color: #fff; | |||||
border-radius: 12px; | |||||
} | |||||
} | } | ||||
&-upload { | &-upload { | ||||
position: relative; | position: relative; | ||||
@@ -14,6 +14,7 @@ | |||||
<!-- 具体内容 --> | <!-- 具体内容 --> | ||||
<van-swipe-cell v-for="(item, index) in videoList" :key="item.id?item.id:index"> | <van-swipe-cell v-for="(item, index) in videoList" :key="item.id?item.id:index"> | ||||
<div class="itemList"> | <div class="itemList"> | ||||
<!-- 每一项 --> | |||||
<div | <div | ||||
v-if="videoList.length > 0" | v-if="videoList.length > 0" | ||||
class="videoModel" | class="videoModel" | ||||
@@ -45,18 +46,24 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<!-- 右侧更多 --> | <!-- 右侧更多 --> | ||||
<div class="showMore" @click.stop="showDialog(item)"> | |||||
<div class="showMore" @click.stop="showDialog(item.id)"> | |||||
<div></div> | <div></div> | ||||
<div></div> | <div></div> | ||||
<div></div> | <div></div> | ||||
</div> | </div> | ||||
<!-- 弹出层 --> | <!-- 弹出层 --> | ||||
<van-dialog class="dialog" | |||||
<van-popup v-if="clickItem==item.id" v-model="showPopup"> | |||||
<div @click="closeDialog"><i class="el-icon-edit" /> 编辑</div> | |||||
<div @click="goDelete(item.id)"><i class="el-icon-delete" /> 删除</div> | |||||
</van-popup> | |||||
<!-- <van-dialog class="dialog" | |||||
v-model="clickItem==item?showTrue:showFalse"> | v-model="clickItem==item?showTrue:showFalse"> | ||||
<div @click="closeDialog"><i class="el-icon-edit" /> 编辑</div> | <div @click="closeDialog"><i class="el-icon-edit" /> 编辑</div> | ||||
<div><i class="el-icon-delete" /> 删除</div> | <div><i class="el-icon-delete" /> 删除</div> | ||||
</van-dialog> | |||||
</van-dialog> --> | |||||
</div> | </div> | ||||
<!-- 无数据处理 --> | |||||
<div v-if="videoList.length <= 0" class="noWork"> | <div v-if="videoList.length <= 0" class="noWork"> | ||||
您还没有视频作品 | 您还没有视频作品 | ||||
</div> | </div> | ||||
@@ -131,6 +138,7 @@ export default { | |||||
}, | }, | ||||
data() { | data() { | ||||
return { | return { | ||||
showPopup:false,// 编辑和删除显示隐藏 | |||||
clickItem: -1,// 弹出层显示隐藏 | clickItem: -1,// 弹出层显示隐藏 | ||||
showTrue: true,// 弹出层显示隐藏 | showTrue: true,// 弹出层显示隐藏 | ||||
showFalse: false,// 弹出层显示隐藏 | showFalse: false,// 弹出层显示隐藏 | ||||
@@ -155,8 +163,9 @@ export default { | |||||
Dialog.close | Dialog.close | ||||
}, | }, | ||||
// 点击显示编辑删除 | // 点击显示编辑删除 | ||||
showDialog(item) { | |||||
this.clickItem=item | |||||
showDialog(id) { | |||||
this.showPopup=true | |||||
this.clickItem=id | |||||
}, | }, | ||||
go(url) { | go(url) { | ||||
this.$router.push(url); | this.$router.push(url); | ||||
@@ -226,14 +235,14 @@ export default { | |||||
}); | }); | ||||
} | } | ||||
}, | }, | ||||
// 删除作品 | |||||
goDelete(id) { | goDelete(id) { | ||||
Dialog.confirm({ | Dialog.confirm({ | ||||
title: "删除作品", | title: "删除作品", | ||||
message: "您确定要删除该作品吗?", | message: "您确定要删除该作品吗?", | ||||
}) | }) | ||||
.then(() => { | |||||
this.deleteVideoById(id); | |||||
.then(async() => { | |||||
await this.deleteVideoById(id); | |||||
this.loadVideoList(this.currentPage, this.pageSize); | this.loadVideoList(this.currentPage, this.pageSize); | ||||
}) | }) | ||||
.catch(() => { | .catch(() => { | ||||
@@ -417,7 +426,7 @@ export default { | |||||
.createTime { | .createTime { | ||||
font-size: 11px; | font-size: 11px; | ||||
color: #999999; | color: #999999; | ||||
margin-top: 10px; | |||||
margin-top: 5px; | |||||
} | } | ||||
} | } | ||||
.showMore { | .showMore { | ||||
@@ -438,8 +447,9 @@ export default { | |||||
} | } | ||||
} | } | ||||
// 弹出层样式 | // 弹出层样式 | ||||
::v-deep .van-dialog { | |||||
::v-deep .van-popup { | |||||
display: flex; | display: flex; | ||||
flex-wrap: wrap; | |||||
align-items: center; | align-items: center; | ||||
justify-content: center; | justify-content: center; | ||||
top: 70%; | top: 70%; | ||||