@@ -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> |
@@ -0,0 +1,91 @@ | |||
<template> | |||
<div> | |||
<van-swipe-cell v-for="(item, index) in videoList"> | |||
<div class="itemList"> | |||
<div | |||
v-if="videoList.length > 0" | |||
class="videoModel" | |||
:key="index" | |||
@click="$parent.goDetail(item)" | |||
> | |||
<!-- 左侧图片 --> | |||
<div class="videoPrv"> | |||
<img src="@/assets/img/BG@2x.png" /> | |||
<div v-if="item.videoStatus == 0" class="cover">草稿</div> | |||
<div v-if="item.videoStatus == 1" class="cover">视频生成中</div> | |||
<div v-if="item.videoStatus == 3" class="cover">视频生成失败</div> | |||
<!-- <div v-if="item.videoStatus == ?" class="cover">排队中</div> --> | |||
</div> | |||
<!-- 中间文字 --> | |||
<div class="videoInfo"> | |||
<div class="videoName"> | |||
{{ item.title || "暂无" }} | |||
</div> | |||
<div class="createTime"> | |||
视频时长:{{ $parent.formatSeconds(item.videoTime) }} | |||
</div> | |||
<div class="createTime"> | |||
视频容量:{{ $parent.bytesToSize(item.videoSize) }} | |||
</div> | |||
<div class="createTime"> | |||
创建时间:{{ | |||
$parent.changeTime(item.createDate, "YYYY-MM-DD hh:mm:ss") | |||
}} | |||
</div> | |||
</div> | |||
<!-- 右侧更多 --> | |||
<div class="showMore" @click.stop="showDialog=!showDialog"> | |||
<div></div> | |||
<div></div> | |||
<div></div> | |||
</div> | |||
<van-dialog v-model="showDialog" title="标题" show-cancel-button> | |||
123123123123 | |||
</van-dialog> | |||
</div> | |||
<div v-if="videoList.length <= 0" class="noWork"> | |||
您还没有视频作品 | |||
</div> | |||
</div> | |||
<template #right> | |||
<van-button | |||
square | |||
text="删除" | |||
type="danger" | |||
class="delete-button" | |||
@click="$parent.goDelete(item.id)" | |||
/> | |||
</template> | |||
</van-swipe-cell> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'SrcMyVideoItem', | |||
props: { | |||
videoList: { | |||
type: Array, | |||
required: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
showDialog:false | |||
}; | |||
}, | |||
mounted() { | |||
}, | |||
methods: { | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -5,15 +5,20 @@ | |||
<div class="userInfo"> | |||
<div class="nickName" @click="go('/myPage')">用户名</div> | |||
<div class="userModel"> | |||
<img | |||
<!-- <img | |||
class="userImg" | |||
src="../../assets/img/user_black.png" | |||
@click="go('/myPage')" | |||
/> | |||
<img | |||
/> --> | |||
<!-- <img | |||
class="userImg" | |||
src="../../assets/img/loginOut.png" | |||
@click="loginOut()" | |||
/> --> | |||
<img | |||
class="userImg" | |||
src="../../assets/icon/icon-loginout.png" | |||
@click="loginOut()" | |||
/> | |||
</div> | |||
</div> | |||
@@ -100,14 +105,15 @@ export default { | |||
font-weight: 600; | |||
font-size: 13px; | |||
margin-right: 15px; | |||
margin-top: 3px; | |||
} | |||
.userModel { | |||
position: relative; | |||
top: 3px; | |||
margin-right: 10px; | |||
.userImg { | |||
width: 30px; | |||
height: 30px; | |||
width: 24px; | |||
height: 24px; | |||
margin-right: 2px; | |||
} | |||
} | |||
@@ -1,5 +1,5 @@ | |||
<template> | |||
<div class="tailBox"> | |||
<div v-if="false" class="tailBox"> | |||
<!-- <div class="title">联系我们</div> --> | |||
<!-- <div class="taillong"> | |||
<img src="../../assets/img/LOGO1.png" alt> | |||
@@ -10,6 +10,8 @@ import 'lib-flexible' | |||
import axios from "axios" | |||
import ElementUI from 'element-ui'; //引入js | |||
import 'element-ui/lib/theme-chalk/index.css';//引入css | |||
import "@/styles/reset.scss"; // 引入清除默认样式 | |||
import "@/styles/index.scss"; // 引入全局样式 | |||
Vue.use(ElementUI); | |||
Vue.use(Vant) | |||
@@ -44,6 +44,24 @@ const routes = [ | |||
name: 'chooseModel', | |||
component: () => import("../views/model/chooseModel"), | |||
}, | |||
// 模板预览new | |||
{ | |||
path: '/previewModel', | |||
name: 'previewModel', | |||
component: () => import("../views/model/previewModel"), | |||
}, | |||
// 编辑模板new | |||
{ | |||
path: '/editModel', | |||
name: 'editModel', | |||
component: () => import("../views/model/editModel"), | |||
}, | |||
// 聊天首页new | |||
{ | |||
path: '/chat', | |||
name: 'chat', | |||
component: () => import("@/views/chat/chat"), | |||
}, | |||
// 录入文案 | |||
{ | |||
path: '/getPaper', | |||
@@ -0,0 +1,16 @@ | |||
.floatRight { | |||
float: right; | |||
} | |||
// 淡化文字 | |||
.desalinate { | |||
font-size: 12px; | |||
font-weight: 400; | |||
color: #666; | |||
} | |||
.redWord { | |||
font-weight: normal; | |||
color: red; | |||
} | |||
img { | |||
vertical-align: middle; | |||
} |
@@ -0,0 +1,284 @@ | |||
/*! | |||
* ress.css • v4.0.0 | |||
* MIT License | |||
* github.com/filipelinhares/ress | |||
*/ | |||
/* # ================================================================= | |||
# Global selectors | |||
# ================================================================= */ | |||
html { | |||
box-sizing: border-box; | |||
-webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS */ | |||
word-break: normal; | |||
-moz-tab-size: 4; | |||
tab-size: 4; | |||
} | |||
*, | |||
::before, | |||
::after { | |||
box-sizing: inherit; | |||
background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements and pseudo elements */ | |||
} | |||
::before, | |||
::after { | |||
text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */ | |||
vertical-align: inherit; | |||
} | |||
* { | |||
padding: 0; /* Reset `padding` and `margin` of all elements */ | |||
margin: 0; | |||
} | |||
/* # ================================================================= | |||
# General elements | |||
# ================================================================= */ | |||
hr { | |||
height: 0; /* Add the correct box sizing in Firefox */ | |||
overflow: visible; /* Show the overflow in Edge and IE */ | |||
color: inherit; /* Correct border color in Firefox. */ | |||
} | |||
details, | |||
main { | |||
display: block; /* Render the `main` element consistently in IE. */ | |||
} | |||
summary { | |||
display: list-item; /* Add the correct display in all browsers */ | |||
} | |||
small { | |||
font-size: 80%; /* Set font-size to 80% in `small` elements */ | |||
} | |||
[hidden] { | |||
display: none; /* Add the correct display in IE */ | |||
} | |||
abbr[title] { | |||
/* Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari */ | |||
text-decoration: underline; | |||
text-decoration: underline dotted; | |||
border-bottom: none; /* Remove the bottom border in Chrome 57 */ | |||
} | |||
a { | |||
background-color: transparent; /* Remove the gray background on active links in IE 10 */ | |||
} | |||
a:active, | |||
a:hover { | |||
outline-width: 0; /* Remove the outline when hovering in all browsers */ | |||
} | |||
code, | |||
kbd, | |||
pre, | |||
samp { | |||
font-family: monospace, monospace; /* Specify the font family of code elements */ | |||
} | |||
pre { | |||
font-size: 1em; /* Correct the odd `em` font sizing in all browsers */ | |||
} | |||
b, | |||
strong { | |||
font-weight: bolder; /* Add the correct font weight in Chrome, Edge, and Safari */ | |||
} | |||
/* https://gist.github.com/unruthless/413930 */ | |||
sub, | |||
sup { | |||
position: relative; | |||
font-size: 75%; | |||
line-height: 0; | |||
vertical-align: baseline; | |||
} | |||
sub { | |||
bottom: -.25em; | |||
} | |||
sup { | |||
top: -.5em; | |||
} | |||
table { | |||
text-indent: 0; /* Remove text indentation in Chrome, Edge, and Safari */ | |||
border-color: inherit; /* Correct border color in all Chrome, Edge, and Safari. */ | |||
} | |||
/* # ================================================================= | |||
# Forms | |||
# ================================================================= */ | |||
input { | |||
border-radius: 0; | |||
} | |||
/* Replace pointer cursor in disabled elements */ | |||
[disabled] { | |||
cursor: default; | |||
} | |||
[type='number']::-webkit-inner-spin-button, | |||
[type='number']::-webkit-outer-spin-button { | |||
height: auto; /* Correct the cursor style of increment and decrement buttons in Chrome */ | |||
} | |||
[type='search'] { | |||
-webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */ | |||
outline-offset: -2px; /* Correct the outline style in Safari */ | |||
} | |||
[type='search']::-webkit-search-decoration { | |||
-webkit-appearance: none; /* Remove the inner padding in Chrome and Safari on macOS */ | |||
} | |||
textarea { | |||
overflow: auto; /* Internet Explorer 11+ */ | |||
resize: vertical; /* Specify textarea resizability */ | |||
} | |||
button, | |||
input, | |||
optgroup, | |||
select, | |||
textarea { | |||
font: inherit; /* Specify font inheritance of form elements */ | |||
} | |||
optgroup { | |||
font-weight: bold; /* Restore the font weight unset by the previous rule */ | |||
} | |||
button { | |||
overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */ | |||
} | |||
button, | |||
select { | |||
text-transform: none; /* Firefox 40+, Internet Explorer 11- */ | |||
} | |||
/* Apply cursor pointer to button elements */ | |||
button, | |||
[type='button'], | |||
[type='reset'], | |||
[type='submit'], | |||
[role='button'] { | |||
color: inherit; | |||
cursor: pointer; | |||
} | |||
/* Remove inner padding and border in Firefox 4+ */ | |||
button::-moz-focus-inner, | |||
[type='button']::-moz-focus-inner, | |||
[type='reset']::-moz-focus-inner, | |||
[type='submit']::-moz-focus-inner { | |||
padding: 0; | |||
border-style: none; | |||
} | |||
/* Replace focus style removed in the border reset above */ | |||
button:-moz-focusring, | |||
[type='button']::-moz-focus-inner, | |||
[type='reset']::-moz-focus-inner, | |||
[type='submit']::-moz-focus-inner { | |||
outline: 1px dotted ButtonText; | |||
} | |||
button, | |||
html [type='button'], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */ | |||
[type='reset'], | |||
[type='submit'] { | |||
-webkit-appearance: button; /* Correct the inability to style clickable types in iOS */ | |||
} | |||
/* Remove the default button styling in all browsers */ | |||
button, | |||
input, | |||
select, | |||
textarea { | |||
background-color: transparent; | |||
border-style: none; | |||
} | |||
a:focus, | |||
button:focus, | |||
input:focus, | |||
select:focus, | |||
textarea:focus { | |||
outline-width: 0; | |||
} | |||
/* Style select like a standard input */ | |||
select { | |||
-moz-appearance: none; /* Firefox 36+ */ | |||
-webkit-appearance: none; /* Chrome 41+ */ | |||
} | |||
select::-ms-expand { | |||
display: none; /* Internet Explorer 11+ */ | |||
} | |||
select::-ms-value { | |||
color: currentColor; /* Internet Explorer 11+ */ | |||
} | |||
legend { | |||
display: table; /* Correct the text wrapping in Edge and IE */ | |||
max-width: 100%; /* Correct the text wrapping in Edge and IE */ | |||
max-width: 100%; /* Correct the text wrapping in Edge 18- and IE */ | |||
color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */ | |||
white-space: normal; /* Correct the text wrapping in Edge and IE */ | |||
border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */ | |||
} | |||
::-webkit-file-upload-button { | |||
font: inherit; /* Change font properties to `inherit` in Chrome and Safari */ | |||
color: inherit; | |||
/* Correct the inability to style clickable types in iOS and Safari */ | |||
-webkit-appearance: button; | |||
} | |||
/* # ================================================================= | |||
# Specify media element style | |||
# ================================================================= */ | |||
img { | |||
border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */ | |||
} | |||
/* Add the correct vertical alignment in Chrome, Firefox, and Opera */ | |||
progress { | |||
vertical-align: baseline; | |||
} | |||
/* # ================================================================= | |||
# Accessibility | |||
# ================================================================= */ | |||
/* Specify the progress cursor of updating elements */ | |||
[aria-busy='true'] { | |||
cursor: progress; | |||
} | |||
/* Specify the pointer cursor of trigger elements */ | |||
[aria-controls] { | |||
cursor: pointer; | |||
} | |||
/* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */ | |||
[aria-disabled='true'] { | |||
cursor: default; | |||
} |
@@ -5,7 +5,9 @@ import { Toast } from 'vant' | |||
// 基础地址 | |||
const baseURL = 'https://smapitest.malls.iformall.com/C' | |||
// 创建axios实例 | |||
/** | |||
* @description:发送Axios请求 | |||
*/ | |||
const request = axios.create({ | |||
baseURL, | |||
timeout: 5000 | |||
@@ -56,7 +58,6 @@ request.interceptors.response.use( | |||
return response.data | |||
} | |||
}, | |||
function (error) { | |||
} | |||
@@ -0,0 +1,100 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<!-- 主体内容 --> | |||
<div class="contnet"> | |||
<!-- 头部 --> | |||
<div class="head"> | |||
<img src="../../assets/icon/icon-loginout.png" alt=""> | |||
<div class="head-exit"> | |||
<img src="../../assets/icon/icon-loginoutwhite.png" alt=""> | |||
退出聊天 | |||
</div> | |||
</div> | |||
<!-- 显示区 --> | |||
<div class="showBody"> | |||
</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); | |||
} | |||
} | |||
.contnet { | |||
.head { | |||
display: flex; | |||
justify-content: space-between; | |||
padding: 4px 10px; | |||
>img { | |||
width: 36px; | |||
height: 36px; | |||
} | |||
&-exit { | |||
margin-top: 4px; | |||
width: 97px; | |||
height: 28px; | |||
text-align: center; | |||
line-height: 28px; | |||
background: #24A9FF; | |||
color: #fff; | |||
box-shadow: 0px 2px 4px 0px rgba(0,0,0,0.2); | |||
border-radius: 33px; | |||
>img { | |||
margin-top: -2px; | |||
margin-right: 4px; | |||
} | |||
} | |||
} | |||
.showBody { | |||
width: 100%; | |||
height: calc(100vh - 44px) ; | |||
background-color: red; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,441 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<div class="logoBox" @click="go('/')"> | |||
<img src="../../assets/img/logoh.png" alt class="logo" /> | |||
<!-- <div class="logoTitle">metavatar</div> --> | |||
</div> | |||
<!-- 短信验证码登录 --> | |||
<div class="loginBox" v-if="loginType == 1"> | |||
<div class="welcome">登录邃芒</div> | |||
<el-form ref="form" :rules="rules" :model="form"> | |||
<el-form-item class="phone" prop="phone"> | |||
<el-input class="phoneInput" v-model="form.phone"></el-input> | |||
<div class="inputPhone">请输入您的手机号</div> | |||
<el-button class="getCode" type="text" @click="getCode">{{ | |||
codeInfo | |||
}}</el-button> | |||
</el-form-item> | |||
<el-form-item class="code" prop="code"> | |||
<el-input | |||
class="codeInput" | |||
v-model="form.code" | |||
maxlength="10" | |||
clearable | |||
></el-input> | |||
<div class="inputCode">请输入短信验证码</div> | |||
</el-form-item> | |||
<el-form-item> | |||
<el-button class="submitBtn" @click="loginByPhoneCode" | |||
>登录/注册</el-button | |||
> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
<!-- 用户名密码登录 --> | |||
<div class="loginBox" v-if="loginType == 2"> | |||
<div class="welcome">登录邃芒</div> | |||
<el-form ref="form" :rules="rulesII" :model="formII"> | |||
<el-form-item class="phone" prop="phone"> | |||
<el-input class="phoneInput" v-model="formII.phone"></el-input> | |||
<div class="inputPhone">请输入您的手机号</div> | |||
<!-- <el-button class="getCode" type="text" @click="getCode">{{ | |||
codeInfo | |||
}}</el-button> --> | |||
</el-form-item> | |||
<el-form-item class="code" prop="code"> | |||
<el-input | |||
class="codeInput" | |||
v-model="form.pwd" | |||
maxlength="10" | |||
clearable | |||
></el-input> | |||
<div class="inputCode">请输入密码</div> | |||
</el-form-item> | |||
<el-form-item class="code" prop="code"> | |||
<el-input | |||
class="codeInput" | |||
v-model="form.code" | |||
maxlength="10" | |||
clearable | |||
></el-input> | |||
<div class="inputCode">请输入验证码</div> | |||
</el-form-item> | |||
<span class="forgetPwd">忘记密码?</span> | |||
<el-form-item> | |||
<el-button class="submitBtn inside" @click="loginByPhoneCode" | |||
>登录/注册</el-button | |||
> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
<!-- 用户名密码登录 --> | |||
<div class="loginBox" v-if="loginType == 3"> | |||
<div class="welcome">登录邃芒</div> | |||
<el-form ref="form" :model="formIII"> | |||
<el-form-item class="code" prop="code"> | |||
<el-input | |||
class="codeInput" | |||
v-model="formIII.inviteCode" | |||
maxlength="10" | |||
clearable | |||
></el-input> | |||
<div class="inputCode">请输入您的邀请码</div> | |||
</el-form-item> | |||
<el-form-item> | |||
<el-button class="skipBtn skip" @click="skip">跳过</el-button> | |||
<el-button class="skipBtn submit" @click="submitInviteCode" | |||
>确定</el-button | |||
> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
// 组件 | |||
import Vue from "vue"; | |||
import { Toast } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
// 工具 | |||
import { scrollToID } from "../../utils"; | |||
// 接口 | |||
import { | |||
getCodeByPhone, | |||
doLoginByPhone, | |||
doFindInviteCode, | |||
doUpdateInviteCode, | |||
} from "../../api/login"; | |||
Vue.use(Toast); | |||
export default { | |||
data() { | |||
return { | |||
codeInfo: "获取验证码", | |||
inPage: false, | |||
loginType: 1, | |||
// 短信验证码登录 | |||
form: { | |||
phone: "", | |||
code: "", | |||
}, | |||
rules: { | |||
phone: [ | |||
{ | |||
required: true, | |||
trigger: "blur", | |||
message: "请先输手机号", | |||
}, | |||
{ | |||
validator(rule, value, callback, source, options) { | |||
var errors = []; | |||
// var mobile = /^1[0|1|2|3|4|5|6|7|8|9]\d{9}$/, | |||
let phone = /^1[35789]\d{9}$/; | |||
if (value && !phone.test(value)) { | |||
callback("抱歉,请输入正确的手机号"); | |||
} | |||
callback(errors); | |||
}, | |||
}, | |||
], | |||
}, | |||
// 账号密码登录 | |||
formII: { | |||
phone: "", | |||
pwd: "", | |||
code: "", | |||
}, | |||
rulesII: { | |||
phone: [ | |||
{ | |||
required: true, | |||
trigger: "blur", | |||
message: "请先输手机号", | |||
}, | |||
{ | |||
validator(rule, value, callback, source, options) { | |||
var errors = []; | |||
// var mobile = /^1[0|1|2|3|4|5|6|7|8|9]\d{9}$/, | |||
let phone = /^1[35789]\d{9}$/; | |||
if (value && !phone.test(value)) { | |||
callback("抱歉,请输入正确的手机号"); | |||
} | |||
callback(errors); | |||
}, | |||
}, | |||
], | |||
}, | |||
// 邀请码 | |||
formIII: { | |||
inviteCode: "", | |||
}, | |||
}; | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
// 跳过填写邀请码 | |||
skip() { | |||
this.$router.push("/myPage"); | |||
}, | |||
// 获取手机短信验证码 | |||
getCode() { | |||
if (this.codeInfo != "获取验证码") { | |||
Toast.fail("请求过于频繁,请稍后再试"); | |||
return; | |||
} | |||
const phoneValue = this.form.phone; | |||
// 将手机号置入本地存储 | |||
localStorage.setItem("phoneValue", phoneValue); | |||
let phoneReg = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/; | |||
let phoneValide = phoneReg.test(phoneValue); | |||
if (!phoneValide) { | |||
Toast.fail("请输入正确的手机号!"); | |||
} else { | |||
this.getPhoneCode(phoneValue); | |||
} | |||
}, | |||
// 手机验证码登录 | |||
loginByPhoneCode() { | |||
const data = { | |||
phone: this.form.phone, | |||
code: this.form.code, | |||
}; | |||
this.LoginByPhone(data); | |||
}, | |||
// 提交邀请码 | |||
submitInviteCode() { | |||
const data = { | |||
inviteCode: this.formIII.inviteCode, | |||
}; | |||
this.updateInviteCode(data); | |||
}, | |||
/** | |||
* @description:获取手机验证码 | |||
*/ | |||
async getPhoneCode(phone) { | |||
try { | |||
const res = await getCodeByPhone(phone); | |||
Toast.success("短信验证码发送成功!"); | |||
this.codeInfo = 60; | |||
const timer = setInterval(() => { | |||
if (this.codeInfo != 1) { | |||
this.codeInfo--; | |||
} else { | |||
clearInterval(timer); | |||
this.codeInfo = "获取验证码"; | |||
} | |||
}, 1000); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description:短信验证码登录 | |||
*/ | |||
async LoginByPhone(loginParams) { | |||
try { | |||
const res = await doLoginByPhone(loginParams); | |||
console.log(res, "登录数据"); | |||
const token = res.data.token; | |||
// 将token置入本地存储 | |||
localStorage.setItem("AccessToken", token); | |||
this.checkInviteCode(); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description:查询用户邀请码状态 | |||
*/ | |||
async checkInviteCode() { | |||
try { | |||
const res = await doFindInviteCode(); | |||
console.log(res, "查询用户邀请码数据"); | |||
const status = res.data.status; | |||
// 当没有邀请码时,切换输入验证码页面 | |||
if (status == 0) { | |||
this.loginType = 3; | |||
// 有邀请码直接前往我的页面 | |||
} else if (status == 1) { | |||
Toast.success("登录成功!"); | |||
this.$router.push("/myPage"); | |||
} | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description:更新邀请码 | |||
*/ | |||
async updateInviteCode(inviteCodeData) { | |||
try { | |||
const res = await doUpdateInviteCode(inviteCodeData); | |||
console.log(res, "更新邀请码数据"); | |||
Toast.success("登录成功!"); | |||
this.$router.push("/myPage"); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
}, | |||
created() { | |||
// 手机号记忆回填 | |||
const phoneValue = localStorage.getItem("phoneValue"); | |||
this.form.phone = phoneValue ? phoneValue : ""; | |||
}, | |||
mounted() { | |||
this.inPage = true; | |||
scrollToID("top"); | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
.page { | |||
opacity: 0; | |||
transition: all 0.5s; // global-transition | |||
&.active { | |||
opacity: 1; | |||
} | |||
.logoBox { | |||
// margin-top: 10px; | |||
margin-left: 5px; | |||
margin-top: 10px; | |||
margin-bottom: 30px; | |||
overflow: hidden; | |||
cursor: pointer; | |||
text-align: center; | |||
.logo { | |||
width: 240px; | |||
height: 70px; | |||
} | |||
} | |||
.loginBox { | |||
height: 500px; | |||
// background: linear-gradient(140deg, #6e60e9, #2867d4, #1ec2ae); | |||
margin: auto; | |||
text-align: center; | |||
padding-left: 30px; | |||
padding-right: 30px; | |||
.welcome { | |||
font-size: 18px; | |||
color: #000; | |||
font-weight: 600; | |||
margin-bottom: 40px; | |||
} | |||
.phone { | |||
position: relative; | |||
margin-bottom: 25px; | |||
.inputPhone { | |||
position: absolute; | |||
background-color: #fff; | |||
top: -5px; | |||
left: 12px; | |||
font-size: 12px; | |||
height: 10px; | |||
line-height: 10px; | |||
color: #0068b7; | |||
} | |||
.getCode { | |||
position: absolute; | |||
top: 1px; | |||
right: 10px; | |||
color: #0068b7; | |||
} | |||
} | |||
.code { | |||
position: relative; | |||
.inputCode { | |||
position: absolute; | |||
background-color: #fff; | |||
top: -5px; | |||
left: 12px; | |||
font-size: 12px; | |||
height: 10px; | |||
line-height: 10px; | |||
color: #0068b7; | |||
} | |||
} | |||
.submitBtn { | |||
position: relative; | |||
right: 10px; | |||
float: right; | |||
color: #fff; | |||
font-size: 15px; | |||
line-height: 15px; | |||
border: none; | |||
padding: 9px; | |||
background-color: #0068b7; | |||
border-radius: 5px; | |||
&.inside { | |||
top: 10px; | |||
} | |||
} | |||
.skipBtn { | |||
position: relative; | |||
top: 10px; | |||
left: 70px; | |||
right: 10px; | |||
width: 78px; | |||
height: 30px; | |||
color: #fff; | |||
font-size: 15px; | |||
line-height: 15px; | |||
border: none; | |||
padding: 9px; | |||
background-color: #0068b7; | |||
border-radius: 3px; | |||
margin-top: 160px; | |||
&.skip { | |||
background-color: #a0a0a0; | |||
} | |||
} | |||
.forgetPwd { | |||
position: relative; | |||
top: -10px; | |||
float: left; | |||
font-size: 12px; | |||
color: #0068b7; | |||
} | |||
} | |||
} | |||
::v-deep .el-input__inner { | |||
height: 42px; | |||
border: 1px solid #0068b7; | |||
} | |||
</style> |
@@ -1,4 +1,5 @@ | |||
<template> | |||
<!-- 手机号13797188591 --> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<div class="logoBox" @click="go('/')"> | |||
@@ -6,214 +7,347 @@ | |||
<!-- <div class="logoTitle">metavatar</div> --> | |||
</div> | |||
<!-- 短信验证码登录 --> | |||
<!-- 首页登录 loginType=1 --> | |||
<div class="loginBox" v-if="loginType == 1"> | |||
<div class="welcome">登录邃芒</div> | |||
<el-form ref="form" :rules="rules" :model="form"> | |||
<!-- 手机号 --> | |||
<el-form-item class="phone" prop="phone"> | |||
<el-input class="phoneInput" v-model="form.phone"></el-input> | |||
<div class="inputPhone">请输入您的手机号</div> | |||
<el-button class="getCode" type="text" @click="getCode">{{ | |||
codeInfo | |||
}}</el-button> | |||
<el-input | |||
v-model="form.phone" | |||
placeholder="请输入您的手机号" | |||
></el-input> | |||
</el-form-item> | |||
<el-form-item class="code" prop="code"> | |||
<!-- 密码 --> | |||
<el-form-item class="code" prop="password"> | |||
<el-input | |||
class="codeInput" | |||
v-model="form.code" | |||
maxlength="10" | |||
clearable | |||
v-model="form.code" | |||
placeholder="请输入密码" | |||
></el-input> | |||
<div class="inputCode">请输入短信验证码</div> | |||
</el-form-item> | |||
<!-- 随机验证码 --> | |||
<el-form-item class="yzCode" prop="yzCode"> | |||
<el-col :span="12"> | |||
<el-input v-model="form.code" placeholder="请输入验证码"></el-input> | |||
</el-col> | |||
</el-form-item> | |||
<!-- 短信登录和忘记密码 --> | |||
<div class="morePage greenColor"> | |||
<span @click="loginType=2">短信登录</span> | |||
<span @click="loginType=3">忘记密码?</span> | |||
</div> | |||
<!-- 登录 --> | |||
<el-form-item> | |||
<el-button class="submitBtn" @click="loginByPhoneCode" | |||
>登录/注册</el-button | |||
> | |||
>登录/注册</el-button> | |||
</el-form-item> | |||
<!-- 没有账号 --> | |||
<div> | |||
<span>没有账号?</span> | |||
<span class="greenColor" @click="loginType=5">立即注册</span> | |||
</div> | |||
</el-form> | |||
</div> | |||
<!-- 用户名密码登录 --> | |||
<!-- 短信登录 loginType=2 --> | |||
<div class="loginBox" v-if="loginType == 2"> | |||
<div class="welcome">登录邃芒</div> | |||
<el-form ref="form" :rules="rulesII" :model="formII"> | |||
<el-form ref="form" :rules="rules" :model="form"> | |||
<!-- 手机号 --> | |||
<el-form-item class="phone" prop="phone"> | |||
<el-input class="phoneInput" v-model="formII.phone"></el-input> | |||
<div class="inputPhone">请输入您的手机号</div> | |||
<!-- <el-button class="getCode" type="text" @click="getCode">{{ | |||
codeInfo | |||
}}</el-button> --> | |||
<el-input | |||
v-model="form.phone" | |||
placeholder="请输入您的手机号" | |||
></el-input> | |||
</el-form-item> | |||
<el-form-item class="code" prop="code"> | |||
<!-- 短信验证码 --> | |||
<el-form-item class="code" prop="phoneCode"> | |||
<el-input | |||
class="codeInput" | |||
v-model="form.pwd" | |||
maxlength="10" | |||
clearable | |||
v-model="form.code" | |||
placeholder="请输入短信验证码" | |||
> | |||
<div v-if="!countdownState" @click="triggerCountdown" class="greenColor" slot="suffix" type="primary">获取验证码</div> | |||
<div v-else slot="suffix" type="primary">验证码已发送{{ countNum }}s</div> | |||
</el-input> | |||
</el-form-item> | |||
<!-- 短信登录和忘记密码 --> | |||
<div class="morePage greenColor inBoxEnd"> | |||
<span @click="loginType=1">账号登录</span> | |||
</div> | |||
<!-- 登录 --> | |||
<el-form-item> | |||
<el-button class="submitBtn" @click="loginByPhoneCode" | |||
>登录</el-button> | |||
</el-form-item> | |||
<!-- 没有账号 --> | |||
<div> | |||
<span>没有账号?</span> | |||
<span class="greenColor" @click="loginType=5">立即注册</span> | |||
</div> | |||
</el-form> | |||
</div> | |||
<!-- 忘记密码 loginType=3--> | |||
<div class="loginBox" v-if="loginType == 3"> | |||
<el-form ref="form" :rules="rules" :model="form"> | |||
<!-- 手机号 --> | |||
<el-form-item class="phone" prop="phone"> | |||
<el-input | |||
v-model="form.phone" | |||
placeholder="请输入您的手机号" | |||
></el-input> | |||
<div class="inputCode">请输入密码</div> | |||
</el-form-item> | |||
<!-- 随机验证码 --> | |||
<el-form-item class="code" prop="yzCode"> | |||
<el-col :span="12"> | |||
<el-input | |||
maxlength="10" | |||
clearable | |||
v-model="form.code" | |||
placeholder="请输入验证码" | |||
></el-input> | |||
</el-col> | |||
</el-form-item> | |||
<!-- 登录 --> | |||
<el-form-item> | |||
<el-button class="submitBtn" @click="loginByPhoneCode" | |||
>获取短信验证码</el-button> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
<el-form-item class="code" prop="code"> | |||
<!-- 重设密码 loginType=4 --> | |||
<div class="loginBox" v-if="loginType == 4"> | |||
<el-form ref="form" :rules="rules" :model="form"> | |||
<!-- 短信验证码 --> | |||
<el-form-item class="phone" prop="phoneCode"> | |||
<el-input | |||
v-model="form.phone" | |||
placeholder="请输入短信验证码" | |||
></el-input> | |||
</el-form-item> | |||
<!-- 新密码1 --> | |||
<el-form-item class="code" prop="newPassword1"> | |||
<el-input | |||
class="codeInput" | |||
maxlength="10" | |||
clearable | |||
v-model="form.code" | |||
placeholder="请输入您的新密码" | |||
> | |||
</el-input> | |||
</el-form-item> | |||
<!-- 新密码2 --> | |||
<el-form-item class="code" prop="newPassword2"> | |||
<el-input | |||
maxlength="10" | |||
clearable | |||
></el-input> | |||
<div class="inputCode">请输入验证码</div> | |||
v-model="form.code" | |||
placeholder="请再次确认您的新密码" | |||
> | |||
</el-input> | |||
</el-form-item> | |||
<span class="forgetPwd">忘记密码?</span> | |||
<!-- 登录 --> | |||
<el-form-item> | |||
<el-button class="submitBtn inside" @click="loginByPhoneCode" | |||
>登录/注册</el-button | |||
> | |||
<el-button class="submitBtn" @click="loginByPhoneCode" | |||
>保存密码</el-button> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
<!-- 用户名密码登录 --> | |||
<div class="loginBox" v-if="loginType == 3"> | |||
<div class="welcome">登录邃芒</div> | |||
<el-form ref="form" :model="formIII"> | |||
<el-form-item class="code" prop="code"> | |||
<!-- 注册 loginType=5 --> | |||
<div class="loginBox" v-if="loginType == 5"> | |||
<el-form ref="form" :rules="rules" :model="form"> | |||
<!-- 短信验证码 --> | |||
<el-form-item class="phone" prop="phone"> | |||
<el-input | |||
v-model="form.phone" | |||
placeholder="请输入您的手机号" | |||
></el-input> | |||
</el-form-item> | |||
<!-- 新密码1 --> | |||
<el-form-item class="code" prop="newPassword1"> | |||
<el-input | |||
class="codeInput" | |||
v-model="formIII.inviteCode" | |||
maxlength="10" | |||
clearable | |||
></el-input> | |||
<div class="inputCode">请输入您的邀请码</div> | |||
v-model="form.code" | |||
placeholder="请输入您的密码" | |||
> | |||
</el-input> | |||
</el-form-item> | |||
<el-form-item> | |||
<el-button class="skipBtn skip" @click="skip">跳过</el-button> | |||
<el-button class="skipBtn submit" @click="submitInviteCode" | |||
>确定</el-button | |||
<!-- 新密码2 --> | |||
<el-form-item class="code" prop="newPassword2"> | |||
<el-input | |||
maxlength="10" | |||
clearable | |||
v-model="form.code" | |||
placeholder="请再次确认您的密码" | |||
> | |||
</el-input> | |||
</el-form-item> | |||
<!-- 注册 --> | |||
<el-form-item> | |||
<el-button class="submitBtn" @click="loginByPhoneCode" | |||
>注册</el-button> | |||
</el-form-item> | |||
</el-form> | |||
</div> | |||
<!-- 弹出层 --> | |||
<van-popup v-model="showDialog">内容</van-popup> | |||
</div> | |||
</template> | |||
<script> | |||
// 组件 | |||
import Vue from "vue"; | |||
import { Toast } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
import Vue from 'vue' | |||
import { Toast } from 'vant' | |||
import { mapState, mapActions } from 'vuex' | |||
// 工具 | |||
import { scrollToID } from "../../utils"; | |||
import { scrollToID } from '../../utils' | |||
// 接口 | |||
import { | |||
getCodeByPhone, | |||
doLoginByPhone, | |||
doFindInviteCode, | |||
doUpdateInviteCode, | |||
} from "../../api/login"; | |||
doUpdateInviteCode | |||
} from '../../api/login' | |||
Vue.use(Toast); | |||
Vue.use(Toast) | |||
export default { | |||
data() { | |||
return { | |||
codeInfo: "获取验证码", | |||
showDialog:false,//弹出层状态 | |||
countNum: 60,// 倒计时 | |||
countdownState:false,//倒计时状态 | |||
codeInfo: '获取验证码', | |||
inPage: false, | |||
loginType: 1, | |||
loginType: 1, //显示页面字符 | |||
// 短信验证码登录 | |||
form: { | |||
phone: "", | |||
code: "", | |||
phone: '', | |||
password: '', | |||
newPassword1:'', | |||
newPassword2:'', | |||
phoneCode:'', | |||
code: '', | |||
yzCode: '' | |||
}, | |||
rules: { | |||
phone: [ | |||
{ | |||
required: true, | |||
trigger: "blur", | |||
message: "请先输手机号", | |||
trigger: 'blur', | |||
message: '请先输手机号' | |||
}, | |||
{ | |||
validator(rule, value, callback, source, options) { | |||
var errors = []; | |||
var errors = [] | |||
// var mobile = /^1[0|1|2|3|4|5|6|7|8|9]\d{9}$/, | |||
let phone = /^1[35789]\d{9}$/; | |||
let phone = /^1[35789]\d{9}$/ | |||
if (value && !phone.test(value)) { | |||
callback("抱歉,请输入正确的手机号"); | |||
callback('抱歉,请输入正确的手机号') | |||
} | |||
callback(errors); | |||
}, | |||
}, | |||
], | |||
callback(errors) | |||
} | |||
} | |||
] | |||
}, | |||
// 账号密码登录 | |||
formII: { | |||
phone: "", | |||
pwd: "", | |||
code: "", | |||
phone: '', | |||
pwd: '', | |||
code: '' | |||
}, | |||
rulesII: { | |||
phone: [ | |||
{ | |||
required: true, | |||
trigger: "blur", | |||
message: "请先输手机号", | |||
trigger: 'blur', | |||
message: '请先输手机号' | |||
}, | |||
{ | |||
validator(rule, value, callback, source, options) { | |||
var errors = []; | |||
var errors = [] | |||
// var mobile = /^1[0|1|2|3|4|5|6|7|8|9]\d{9}$/, | |||
let phone = /^1[35789]\d{9}$/; | |||
let phone = /^1[35789]\d{9}$/ | |||
if (value && !phone.test(value)) { | |||
callback("抱歉,请输入正确的手机号"); | |||
callback('抱歉,请输入正确的手机号') | |||
} | |||
callback(errors); | |||
}, | |||
}, | |||
], | |||
callback(errors) | |||
} | |||
} | |||
] | |||
}, | |||
// 短信登录 | |||
// 忘记密码 | |||
// 重设密码 | |||
// 注册 | |||
// 邀请码 | |||
formIII: { | |||
inviteCode: "", | |||
}, | |||
}; | |||
inviteCode: '' | |||
} | |||
} | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
characters: (state) => state.publicObj.characters | |||
}) | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
...mapActions(['setCharacters']), | |||
// 发送短信验证码 | |||
triggerCountdown() { | |||
//调用接口 | |||
this.getCode() | |||
// 切换文字 | |||
this.countdownState = !this.countdownState; | |||
// 定时器 | |||
let intervalBtn = setInterval(() => { | |||
// 倒数自减 | |||
this.countNum--; | |||
if(this.countNum < 0) { | |||
// 清除定时器 | |||
clearInterval(intervalBtn) | |||
// 更改状态 | |||
this.countdownState =! this.countdownState; | |||
// 重置倒计时状态 | |||
this.countNum = 60; | |||
}; | |||
// 倒计时 | |||
}, 1000); | |||
}, | |||
go(url) { | |||
this.$router.push(url); | |||
this.$router.push(url) | |||
}, | |||
// 跳过填写邀请码 | |||
skip() { | |||
this.$router.push("/myPage"); | |||
this.$router.push('/myPage') | |||
}, | |||
// 获取手机短信验证码 | |||
getCode() { | |||
if (this.codeInfo != "获取验证码") { | |||
Toast.fail("请求过于频繁,请稍后再试"); | |||
return; | |||
if (this.codeInfo != '获取验证码') { | |||
Toast.fail('请求过于频繁,请稍后再试') | |||
return | |||
} | |||
const phoneValue = this.form.phone; | |||
const phoneValue = this.form.phone | |||
// 将手机号置入本地存储 | |||
localStorage.setItem("phoneValue", phoneValue); | |||
let phoneReg = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/; | |||
let phoneValide = phoneReg.test(phoneValue); | |||
localStorage.setItem('phoneValue', phoneValue) | |||
let phoneReg = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/ | |||
let phoneValide = phoneReg.test(phoneValue) | |||
if (!phoneValide) { | |||
Toast.fail("请输入正确的手机号!"); | |||
Toast.fail('请输入正确的手机号!') | |||
} else { | |||
this.getPhoneCode(phoneValue); | |||
this.getPhoneCode(phoneValue) | |||
} | |||
}, | |||
@@ -221,17 +355,17 @@ export default { | |||
loginByPhoneCode() { | |||
const data = { | |||
phone: this.form.phone, | |||
code: this.form.code, | |||
}; | |||
this.LoginByPhone(data); | |||
code: this.form.code | |||
} | |||
this.LoginByPhone(data) | |||
}, | |||
// 提交邀请码 | |||
submitInviteCode() { | |||
const data = { | |||
inviteCode: this.formIII.inviteCode, | |||
}; | |||
this.updateInviteCode(data); | |||
inviteCode: this.formIII.inviteCode | |||
} | |||
this.updateInviteCode(data) | |||
}, | |||
/** | |||
@@ -239,20 +373,20 @@ export default { | |||
*/ | |||
async getPhoneCode(phone) { | |||
try { | |||
const res = await getCodeByPhone(phone); | |||
Toast.success("短信验证码发送成功!"); | |||
this.codeInfo = 60; | |||
const res = await getCodeByPhone(phone) | |||
Toast.success('短信验证码发送成功!') | |||
this.codeInfo = 60 | |||
const timer = setInterval(() => { | |||
if (this.codeInfo != 1) { | |||
this.codeInfo--; | |||
this.codeInfo-- | |||
} else { | |||
clearInterval(timer); | |||
this.codeInfo = "获取验证码"; | |||
clearInterval(timer) | |||
this.codeInfo = '获取验证码' | |||
} | |||
}, 1000); | |||
}, 1000) | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
console.log(error, 'error') | |||
Toast.fail(error.message) | |||
} | |||
}, | |||
@@ -261,15 +395,15 @@ export default { | |||
*/ | |||
async LoginByPhone(loginParams) { | |||
try { | |||
const res = await doLoginByPhone(loginParams); | |||
console.log(res, "登录数据"); | |||
const token = res.data.token; | |||
const res = await doLoginByPhone(loginParams) | |||
console.log(res, '登录数据') | |||
const token = res.data.token | |||
// 将token置入本地存储 | |||
localStorage.setItem("AccessToken", token); | |||
this.checkInviteCode(); | |||
localStorage.setItem('AccessToken', token) | |||
this.checkInviteCode() | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
console.log(error, 'error') | |||
Toast.fail(error.message) | |||
} | |||
}, | |||
@@ -278,20 +412,20 @@ export default { | |||
*/ | |||
async checkInviteCode() { | |||
try { | |||
const res = await doFindInviteCode(); | |||
console.log(res, "查询用户邀请码数据"); | |||
const status = res.data.status; | |||
const res = await doFindInviteCode() | |||
console.log(res, '查询用户邀请码数据') | |||
const status = res.data.status | |||
// 当没有邀请码时,切换输入验证码页面 | |||
if (status == 0) { | |||
this.loginType = 3; | |||
this.loginType = 3 | |||
// 有邀请码直接前往我的页面 | |||
} else if (status == 1) { | |||
Toast.success("登录成功!"); | |||
this.$router.push("/myPage"); | |||
Toast.success('登录成功!') | |||
this.$router.push('/myPage') | |||
} | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
console.log(error, 'error') | |||
Toast.fail(error.message) | |||
} | |||
}, | |||
@@ -300,29 +434,32 @@ export default { | |||
*/ | |||
async updateInviteCode(inviteCodeData) { | |||
try { | |||
const res = await doUpdateInviteCode(inviteCodeData); | |||
console.log(res, "更新邀请码数据"); | |||
Toast.success("登录成功!"); | |||
this.$router.push("/myPage"); | |||
const res = await doUpdateInviteCode(inviteCodeData) | |||
console.log(res, '更新邀请码数据') | |||
Toast.success('登录成功!') | |||
this.$router.push('/myPage') | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
console.log(error, 'error') | |||
Toast.fail(error.message) | |||
} | |||
}, | |||
} | |||
}, | |||
created() { | |||
// 手机号记忆回填 | |||
const phoneValue = localStorage.getItem("phoneValue"); | |||
this.form.phone = phoneValue ? phoneValue : ""; | |||
const phoneValue = localStorage.getItem('phoneValue') | |||
this.form.phone = phoneValue ? phoneValue : '' | |||
}, | |||
mounted() { | |||
this.inPage = true; | |||
scrollToID("top"); | |||
}, | |||
}; | |||
this.inPage = true | |||
scrollToID('top') | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
.greenColor { | |||
color: #5ed5c8; | |||
} | |||
.page { | |||
opacity: 0; | |||
transition: all 0.5s; // global-transition | |||
@@ -332,8 +469,8 @@ export default { | |||
.logoBox { | |||
// margin-top: 10px; | |||
margin-left: 5px; | |||
margin-top: 10px; | |||
margin-bottom: 30px; | |||
margin-top: 25px; | |||
margin-bottom: 25px; | |||
overflow: hidden; | |||
cursor: pointer; | |||
text-align: center; | |||
@@ -356,20 +493,14 @@ export default { | |||
font-weight: 600; | |||
margin-bottom: 40px; | |||
} | |||
.inBoxEnd { | |||
display: flex; | |||
justify-content: end !important; | |||
} | |||
.phone { | |||
position: relative; | |||
margin-bottom: 25px; | |||
.inputPhone { | |||
position: absolute; | |||
background-color: #fff; | |||
top: -5px; | |||
left: 12px; | |||
font-size: 12px; | |||
height: 10px; | |||
line-height: 10px; | |||
color: #0068b7; | |||
} | |||
.getCode { | |||
position: absolute; | |||
top: 1px; | |||
@@ -390,18 +521,34 @@ export default { | |||
color: #0068b7; | |||
} | |||
} | |||
.morePage { | |||
display: flex; | |||
justify-content: space-between; | |||
margin-top: -9px; | |||
} | |||
.submitBtn { | |||
position: relative; | |||
right: 10px; | |||
float: right; | |||
// right: 10px; | |||
// float: right; | |||
color: #fff; | |||
font-size: 15px; | |||
line-height: 15px; | |||
border: none; | |||
margin-top: 25px; | |||
// margin-bottom: 7.5px; | |||
padding: 9px; | |||
background-color: #0068b7; | |||
border-radius: 5px; | |||
background-image: linear-gradient( | |||
316deg, | |||
rgba(110, 228, 215, 1) 0, | |||
rgba(55, 196, 241, 1) 31.975823%, | |||
rgba(164, 154, 252, 1) 100% | |||
); | |||
border-radius: 25px; | |||
width: 315px; | |||
height: 50px; | |||
// padding: 12px 138px 12px 138px; | |||
&.inside { | |||
top: 10px; | |||
} | |||
@@ -436,6 +583,10 @@ export default { | |||
} | |||
::v-deep .el-input__inner { | |||
height: 42px; | |||
border: 1px solid #0068b7; | |||
border: 0px; | |||
background-color: #f6f6f6; | |||
} | |||
::v-deep .el-form-item { | |||
margin-bottom: 25px; | |||
} | |||
</style> |
@@ -0,0 +1,312 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- <div class="chooseModel"> | |||
<div :class="phase == 1 ? 'active' : ''">选择模板</div> | |||
<div :class="phase == 2 ? 'active' : ''">录入文案</div> | |||
<div :class="phase == 3 ? 'active' : ''">合成视频</div> | |||
</div> --> | |||
<div class="title">选择模板</div> | |||
<div class="typeOutSide"> | |||
<div class="type"> | |||
<div :class="type == 0 ? 'active' : ''" @click="chooseType(0)"> | |||
全部模板 | |||
</div> | |||
<div :class="type == 2 ? 'active' : ''" @click="chooseType(2)"> | |||
女性模板 | |||
</div> | |||
<div :class="type == 1 ? 'active' : ''" @click="chooseType(1)"> | |||
男性模板 | |||
</div> | |||
</div> | |||
</div> | |||
<div class="choose"> | |||
<div class="modeList"> | |||
<div | |||
class="model" | |||
:class="currentId == item.id ? 'active' : ''" | |||
v-if="modeList.length >= 1" | |||
v-for="(item, index) in modeList" | |||
:key="index" | |||
@click="selectModel(item.id)" | |||
> | |||
<img :src="item.coverImg" alt="" /> | |||
<div class="modelName">{{ item.title }}</div> | |||
<div class="setPaper">录入文案</div> | |||
</div> | |||
<div class="alt" v-if="modeList.length == 0">暂无可用模板</div> | |||
</div> | |||
</div> | |||
<div class="blank" v-if="modeList.length <= 2"></div> | |||
</div> | |||
</template> | |||
<script> | |||
import Vue from "vue"; | |||
import { mapState, mapActions } from "vuex"; | |||
import HeadTop from "./../../components/common/head.vue"; | |||
import { Toast } from "vant"; | |||
import { scrollToID } from "../../utils"; | |||
import { | |||
getModeList, | |||
getModeDetailById, | |||
saveOrUpdateUserVideo, | |||
} from "../../api/chooseModel"; | |||
Vue.use(Toast); | |||
export default { | |||
components: { | |||
HeadTop, | |||
}, | |||
data() { | |||
return { | |||
inPage: false, | |||
type: 0, | |||
currentId: "", | |||
modeList: [], // 模板列表 | |||
}; | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
globalWatcher(e) { | |||
console.log(e, "e"); | |||
}, | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
chooseModel(num) { | |||
this.phase = num; | |||
}, | |||
chooseType(num) { | |||
this.type = num; | |||
this.loadModeList(num); | |||
}, | |||
selectModel(id) { | |||
if (this.currentId != id) { | |||
this.currentId = id; | |||
} else { | |||
this.getModeDetail(id); | |||
} | |||
}, | |||
// 获取模板列表 | |||
async loadModeList(sex) { | |||
try { | |||
sex = sex == 0 ? "" : sex; | |||
const res = await getModeList(sex); | |||
console.log(res, "获取模板列表"); | |||
this.modeList = res.data.list; | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
async getModeDetail(id) { | |||
try { | |||
const res = await getModeDetailById(id); | |||
console.log(res, "根据id获取模板详情"); | |||
const modelDetail = res.data; | |||
this.goSetPaperWork(modelDetail); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
async saveToSetPaper(data, modelDetail) { | |||
try { | |||
const res = await saveOrUpdateUserVideo(data); | |||
console.log(res, "保存或修改用户作品"); | |||
this.$router.push({ | |||
path: "/getPaper", | |||
query: { | |||
modelDetail: JSON.stringify(modelDetail), | |||
saveID: res.data.id, | |||
}, | |||
}); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description:根据模板详情跳转至选择文案 | |||
* @param {type}modelDetail | |||
* @event:Go page => setPaperWork | |||
*/ | |||
goSetPaperWork(modelDetail) { | |||
const data = { | |||
// id: "", | |||
type: 1, | |||
personMouldId: modelDetail.id, //模板id | |||
personMouldSmId: modelDetail.mouldSmId, //模板标识 | |||
// voiceMouldId: "", | |||
// voiceMouldId: "", | |||
}; | |||
this.saveToSetPaper(data, modelDetail); | |||
}, | |||
}, | |||
mounted() { | |||
this.inPage = true; | |||
scrollToID("top"); | |||
}, | |||
created() { | |||
this.loadModeList(2); | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
.page { | |||
transform: translateX(30%); | |||
opacity: 0; | |||
transition: all 0.5s; // global-transition | |||
padding-bottom: 25px; | |||
&.active { | |||
opacity: 1; | |||
transform: translateX(0); | |||
} | |||
.chooseModel { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
width: 350px; | |||
height: 32px; | |||
margin: auto; | |||
margin-top: 15px; | |||
margin-bottom: 15px; | |||
div { | |||
width: 33.33%; | |||
height: 100%; | |||
line-height: 32px; | |||
text-align: center; | |||
border: 1px solid #46464661; | |||
&.active { | |||
background-color: #00ccff; | |||
color: #fff; | |||
} | |||
} | |||
} | |||
.title { | |||
text-align: center; | |||
width: 70px; | |||
font-size: 16px; | |||
font-weight: 600; | |||
border-bottom: 3px solid #0068b7; | |||
padding-bottom: 7px; | |||
margin: 10px auto; | |||
margin-bottom: 25px; | |||
} | |||
.typeOutSide { | |||
position: sticky; | |||
top: 60px; | |||
z-index: 99; | |||
.type { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
padding: 0 25px; | |||
margin-bottom: 20px; | |||
div { | |||
width: 94px; | |||
height: 35px; | |||
line-height: 35px; | |||
color: #fff; | |||
border: 1px solid #bfbfbf; | |||
background-color: #bfbfbf; | |||
border-radius: 5px; | |||
text-align: center; | |||
transition: all 0.3s; | |||
&.active { | |||
color: #fff; | |||
border: 1px solid #0068b7; | |||
background-color: #0068b7; | |||
} | |||
} | |||
} | |||
} | |||
.choose { | |||
margin-bottom: 40px; | |||
.modeList { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
flex-wrap: wrap; | |||
margin: auto; | |||
padding: 0 10px; | |||
.model { | |||
position: relative; | |||
text-align: left; | |||
margin-bottom: 25px; | |||
border: 1px solid #00000028; | |||
padding: 8px; | |||
border-radius: 8px; | |||
transition: all 0.3s; | |||
opacity: 0.9; | |||
img { | |||
width: 150px; | |||
height: 220px; | |||
} | |||
.modelName { | |||
margin-top: 4px; | |||
font-size: 13px; | |||
} | |||
.setPaper { | |||
position: absolute; | |||
top: 80%; | |||
left: 50%; | |||
transform: translate(-50%, -50%); | |||
color: #fff; | |||
font-size: 14px; | |||
font-weight: 600; | |||
background-color: #00000042; | |||
padding: 3px 5px; | |||
border-radius: 5px; | |||
transition: all 0.3s; | |||
opacity: 0; | |||
} | |||
&.active { | |||
transform: translateY(-3px); | |||
box-shadow: #60606082 0px 2px 2px 1px; | |||
opacity: 1; | |||
.setPaper { | |||
opacity: 1; | |||
} | |||
} | |||
} | |||
.alt { | |||
font-size: 18px; | |||
margin: 80px auto; | |||
height: 450px; | |||
line-height: 450px; | |||
} | |||
} | |||
} | |||
.blank { | |||
width: 1px; | |||
height: 1px; | |||
margin-bottom: 240px; | |||
} | |||
} | |||
</style> |
@@ -2,14 +2,15 @@ | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- <div class="chooseModel"> | |||
<div :class="phase == 1 ? 'active' : ''">选择模板</div> | |||
<div :class="phase == 2 ? 'active' : ''">录入文案</div> | |||
<div :class="phase == 3 ? 'active' : ''">合成视频</div> | |||
</div> --> | |||
<!-- 顶部tab栏 --> | |||
<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-tabs> | |||
<div class="title">选择模板</div> | |||
<div class="typeOutSide"> | |||
<!-- <div class="title">选择模板</div> --> | |||
<!-- 老版吸顶tab栏 --> | |||
<!-- <div class="typeOutSide"> | |||
<div class="type"> | |||
<div :class="type == 0 ? 'active' : ''" @click="chooseType(0)"> | |||
全部模板 | |||
@@ -21,8 +22,8 @@ | |||
男性模板 | |||
</div> | |||
</div> | |||
</div> | |||
</div> --> | |||
<!-- 模板列表 --> | |||
<div class="choose"> | |||
<div class="modeList"> | |||
<div | |||
@@ -67,6 +68,7 @@ export default { | |||
type: 0, | |||
currentId: "", | |||
modeList: [], // 模板列表 | |||
tabList: ['全部','男性','女性','原P','武装直升机','粥P','二刺猿'] | |||
}; | |||
}, | |||
computed: { | |||
@@ -77,7 +79,11 @@ export default { | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
//点击tab栏选择模板列表 | |||
chooseType1(name, title) { | |||
this.type = this.tabList.findIndex(item => item==title) | |||
this.loadModeList(this.type); | |||
}, | |||
globalWatcher(e) { | |||
console.log(e, "e"); | |||
}, | |||
@@ -90,10 +96,10 @@ export default { | |||
this.phase = num; | |||
}, | |||
chooseType(num) { | |||
this.type = num; | |||
this.loadModeList(num); | |||
}, | |||
// chooseType(num) { | |||
// this.type = num; | |||
// this.loadModeList(num); | |||
// }, | |||
selectModel(id) { | |||
if (this.currentId != id) { | |||
@@ -115,13 +121,20 @@ export default { | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
// 根据id获取模板详情 | |||
async getModeDetail(id) { | |||
try { | |||
const res = await getModeDetailById(id); | |||
console.log(res, "根据id获取模板详情"); | |||
const modelDetail = res.data; | |||
this.goSetPaperWork(modelDetail); | |||
this.$router.push({ | |||
path: '/previewModel', | |||
query: { | |||
imgUrl: res.data.coverImg, | |||
modelId:res.data.id } | |||
}) | |||
// this.goSetPaperWork(modelDetail); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
@@ -132,13 +145,13 @@ export default { | |||
try { | |||
const res = await saveOrUpdateUserVideo(data); | |||
console.log(res, "保存或修改用户作品"); | |||
this.$router.push({ | |||
path: "/getPaper", | |||
query: { | |||
modelDetail: JSON.stringify(modelDetail), | |||
saveID: res.data.id, | |||
}, | |||
}); | |||
// this.$router.push({ | |||
// path: "/getPaper", | |||
// query: { | |||
// modelDetail: JSON.stringify(modelDetail), | |||
// saveID: res.data.id, | |||
// }, | |||
// }); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
@@ -173,6 +186,10 @@ export default { | |||
</script> | |||
<style lang="scss" scoped> | |||
::v-deep .van-tab--active { | |||
font-size: 17px; | |||
font-weight: 700; | |||
} | |||
.page { | |||
transform: translateX(30%); | |||
opacity: 0; | |||
@@ -215,9 +232,9 @@ export default { | |||
} | |||
.typeOutSide { | |||
position: sticky; | |||
top: 60px; | |||
top: 0px; | |||
z-index: 99; | |||
.type { | |||
display: flex; | |||
justify-content: space-between; | |||
@@ -245,6 +262,7 @@ export default { | |||
} | |||
.choose { | |||
padding-top: 20px; | |||
margin-bottom: 40px; | |||
.modeList { | |||
@@ -253,24 +271,27 @@ export default { | |||
align-items: center; | |||
flex-wrap: wrap; | |||
margin: auto; | |||
padding: 0 10px; | |||
padding: 0 23px; | |||
.model { | |||
position: relative; | |||
text-align: left; | |||
margin-bottom: 25px; | |||
border: 1px solid #00000028; | |||
padding: 8px; | |||
border-radius: 8px; | |||
margin-bottom: 15px; | |||
// border: 1px solid #00000028; | |||
// padding: 8px; | |||
width: 154px; | |||
height: 305px; | |||
transition: all 0.3s; | |||
opacity: 0.9; | |||
img { | |||
width: 150px; | |||
height: 220px; | |||
border-radius: 8px; | |||
width: 100%; | |||
height: 273px; | |||
} | |||
.modelName { | |||
margin-top: 4px; | |||
font-size: 13px; | |||
margin-top: 10px; | |||
font-weight: 700; | |||
font-size: 16px; | |||
} | |||
.setPaper { | |||
position: absolute; | |||
@@ -0,0 +1,649 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- 中间内容区 --> | |||
<div class="content"> | |||
<!-- 展示图片 --> | |||
<div v-if="imgUrl" class="shouModelImg"> | |||
<img :src="imgUrl" alt=""> | |||
<!-- 字幕 --> | |||
<div v-html="textareaContent" v-show="showSubtitle" class="shouModelImg-subtitle"></div> | |||
</div> | |||
<!-- 文字 --> | |||
<div class="addText"> | |||
<div class="addText-title">添加文案</div> | |||
<div class="desalinate"> | |||
<span>已录入{{textareaContent.length}}字</span> | |||
<span>视频时长: 00:16</span> | |||
</div> | |||
</div> | |||
<!-- 标题框 --> | |||
<div class="textInput"> | |||
<div class="textInput-in"> | |||
<!-- 标题 --> | |||
<el-input v-model="inputTitle" placeholder="请输入标题"> | |||
<template slot="append"> | |||
<img src="../../assets/icon/icon-pencil.png" alt=""> | |||
</template> | |||
</el-input> | |||
<div class="divider" /> | |||
<!-- 语音内容 --> | |||
<el-input rows="4" type="textarea" v-model="textareaContent"></el-input> | |||
</div> | |||
</div> | |||
<!-- 播报声音 --> | |||
<div class="addText"> | |||
<div class="addText-title">播报声音<span class="redWord">(必选)</span></div> | |||
<div> | |||
英语_女性_安娜 | |||
</div> | |||
</div> | |||
<!-- 合成按钮 --> | |||
<div class="makeVideo" @click="showSynthesisDialog=true">立即制作</div> | |||
<!-- 上移下移 --> | |||
<div class="upAndBottomBtn"> | |||
<div class="moveLayer"> | |||
<div class="btnContent"> | |||
<img src="https://lanhu.oss-cn-beijing.aliyuncs.com/SketchPngf9bff6080bf29c6886ad614d479aebfb5fc41429c11c0bea78ca695a0f0e7388" alt=""> | |||
上移一层 | |||
</div> | |||
</div> | |||
<div class="moveLayer below"> | |||
<div class="btnContent active"> | |||
<img src="https://lanhu.oss-cn-beijing.aliyuncs.com/SketchPngde1a7c84fc47b26095186ff9ee7ec07d7fe7bcd2cde90f3178f4edb8cdb08bcd" alt=""> | |||
下移一层 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 底部导航栏 --> | |||
<div class="bottom"> | |||
<van-tabbar class="bottom-tab"> | |||
<van-tabbar-item @click="clickBottomTab(1)"> | |||
<span>更改声音</span> | |||
<template #icon="props"> | |||
<img src="../../assets/icon/icon-microphone.png" /> | |||
</template> | |||
</van-tabbar-item> | |||
<van-tabbar-item @click="clickBottomTab(2)"> | |||
<span>更改背景</span> | |||
<template #icon="props"> | |||
<img src="../../assets/icon/icon-background.png" /> | |||
</template> | |||
</van-tabbar-item> | |||
<van-tabbar-item @click="clickBottomTab(3)"> | |||
<span>添加素材</span> | |||
<template #icon="props"> | |||
<img src="../../assets/icon/icon-material.png" /> | |||
</template> | |||
</van-tabbar-item> | |||
<van-tabbar-item @click="clickBottomTab(4)"> | |||
<span v-show="showSubtitle">取消字幕</span> | |||
<span v-show="!showSubtitle">添加字幕</span> | |||
<template #icon="props"> | |||
<img src="../../assets/icon/icon-subtitle.png" /> | |||
</template> | |||
</van-tabbar-item> | |||
</van-tabbar> | |||
</div> | |||
<!-- 合成视频进度条 --> | |||
<div v-if="showSynthesisDialog" class="synthesisDialog"> | |||
<div class="synthesisDialog-in"> | |||
<el-progress color="#7264F5" define-back-color="#f0effe" stroke-width="12" class="progress" :percentage="26"></el-progress> | |||
<div class="synthesisDialog-text">正在生成视频,请耐心等待!</div> | |||
</div> | |||
</div> | |||
<!-- 点击tab栏弹出框 --> | |||
<van-action-sheet class="tabSheet" v-model="showTabSheet" > | |||
<!-- 声音 --> | |||
<div v-show="bottomTabActive==1"> | |||
<!-- 顶部文字 --> | |||
<div class="tabSheet-title"> | |||
<div><img class="img" src="../../assets/icon/icon-microphone.png" alt=""> 更改声音</div> | |||
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div> | |||
</div> | |||
<!-- 主要内容 --> | |||
<div class="tabSheet-content"> | |||
<!-- tab栏 --> | |||
<van-tabs color="#7264F5" line-width=46 line-height=6 title-inactive-color="#666" title-active-color="#333" v-model="soundListAction" swipeable> | |||
<van-tab v-for="(item,index) in soundTabList" :title="item" :key="index"> | |||
<!-- 展示页 --> | |||
<div class="list"> | |||
<div @click="soundItemListAction=index" class="listItem" :class="{ 'active': soundItemListAction ===index }" v-for="(item,index) in soundItemList" :key="index"> | |||
<img src="../../assets/icon/icon-earphone.png" alt=""> | |||
<div >{{ item }}</div> | |||
</div> | |||
</div> | |||
</van-tab> | |||
</van-tabs> | |||
</div> | |||
</div> | |||
<!-- 背景 --> | |||
<div v-show="bottomTabActive==2"> | |||
<!-- 顶部文字 --> | |||
<div class="tabSheet-title"> | |||
<div><img class="img" src="../../assets/icon/icon-background.png" alt=""> 更换背景</div> | |||
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div> | |||
</div> | |||
<!-- 主要内容 --> | |||
<div class="tabSheet-content"> | |||
<!-- tab栏 --> | |||
<div class="myTab"> | |||
<div @click="bjAction=0" class="myTab-item" :class="{'active':bjAction==0}"><img src="../../assets/icon/icon-folder@2x.png" alt=""> 背景库</div> | |||
<div @click="bjAction=1" class="myTab-item" :class="{'active':bjAction==1}"><img src="../../assets/icon/icon-upload@2x.png" alt="">我上传的</div> | |||
</div> | |||
<!-- 展示 --> | |||
<div class="myTab-show"> | |||
<div class="myTab-show-title">图片<i></i></div> | |||
<div class="myTab-show-list"> | |||
<!-- 上传 --> | |||
<div v-if="bjAction==1" class="myTab-show-list-upload"> | |||
<img src="../../assets/icon/icon-upload@2x.png" alt=""> | |||
<input type="file"> | |||
</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 v-show="bottomTabActive==3"> | |||
<!-- 顶部文字 --> | |||
<div class="tabSheet-title"> | |||
<div><img class="img" src="../../assets/icon/icon-material.png" alt=""> 更换素材</div> | |||
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div> | |||
</div> | |||
<!-- 主要内容 --> | |||
<div class="tabSheet-content"> | |||
<!-- tab栏 --> | |||
<div class="myTab"> | |||
<div @click="scAction=0" class="myTab-item" :class="{'active':scAction==0}"><img src="../../assets/icon/icon-folder@2x.png" alt=""> 背景库</div> | |||
<div @click="scAction=1" class="myTab-item" :class="{'active':scAction==1}"><img src="../../assets/icon/icon-upload@2x.png" alt="">我上传的</div> | |||
</div> | |||
<!-- 展示 --> | |||
<div class="myTab-show"> | |||
<div class="myTab-show-title">图片<i></i></div> | |||
<div class="myTab-show-list"> | |||
<!-- 上传 --> | |||
<div v-if="scAction==1" class="myTab-show-list-upload"> | |||
<img src="../../assets/icon/icon-upload@2x.png" alt=""> | |||
<input type="file"> | |||
</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> | |||
</van-action-sheet> | |||
</div> | |||
</template> | |||
<script> | |||
// 组件 | |||
import Vue from "vue"; | |||
import { Toast } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
import HeadTop from "./../../components/common/head.vue"; | |||
// 工具 | |||
import { scrollToID } from "../../utils"; | |||
Vue.use(Toast); | |||
export default { | |||
components: { | |||
HeadTop, | |||
}, | |||
data() { | |||
return { | |||
inPage: false, | |||
imgUrl:null, | |||
modelId: null, | |||
inputTitle: '', | |||
textareaContent: '', // 输入框内容 | |||
bottomTabActive: 2,// 底部导航高亮 | |||
showSubtitle: true, // 是否展示字幕 | |||
showSynthesisDialog: false,//合成进度条窗口显示隐藏 | |||
showTabSheet: false, //tab栏弹出框显隐 | |||
soundTabList: ['男生', '女生', '御姐', '萝莉'], // 声音tab栏标题 | |||
soundListAction:-1,//声音tab栏标题高亮 | |||
soundItemList: ['普通话', '英语', '日语', '日语', '日语', '日语', '日语'],// 声音展示列表 | |||
soundItemListAction:-1,// 声音展示列表高亮 | |||
bjAction:-1,// 背景:素材库和上传高亮 | |||
bjImgAction:-1,// 选中背景图高亮 | |||
scAction:-1,// 素材:素材库和上传高亮 | |||
scImgAction:-1,// 选中素材图高亮 | |||
}; | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
// 点击底部tab栏 | |||
clickBottomTab(index) { | |||
if (index != 4) { | |||
this.bottomTabActive = index | |||
console.log(this.bottomTabActive); | |||
this.showTabSheet=!this.showTabSheet | |||
} else { | |||
//字幕切换 | |||
this.showSubtitle=!this.showSubtitle | |||
} | |||
}, | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
}, | |||
created() { | |||
this.imgUrl=this.$route.query.imgUrl | |||
this.modelId=this.$route.query.modelId | |||
}, | |||
mounted() { | |||
this.inPage = true; | |||
scrollToID("top"); | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
.page { | |||
// transform会导致其子元素固定定位失效 | |||
// transform: translateX(30%); | |||
opacity: 0; | |||
transition: all 0.5s; // global-transition | |||
&.active { | |||
opacity: 1; | |||
// transform: translateX(0); | |||
} | |||
} | |||
.content { | |||
position: relative; | |||
padding: 0 10px; | |||
padding-bottom: 50px; | |||
.shouModelImg { | |||
display: flex; | |||
position: relative; | |||
justify-content: center ; | |||
width: 100%; | |||
img { | |||
width: 200px; | |||
height: 351px; | |||
border-radius: 12px; | |||
}; | |||
&-subtitle { | |||
position: absolute; | |||
top: 220px; | |||
left: 50%; | |||
transform: translateX(-50%); | |||
padding: 2px 6px; | |||
width: 160px; | |||
height: 24px; | |||
line-height: 20px; | |||
background: rgba(0,0,0,0.5); | |||
font-size: 14px; | |||
color: #fff; | |||
border-radius: 3px; | |||
} | |||
} | |||
.addText { | |||
display: flex; | |||
justify-content: space-between; | |||
margin-top: 20px; | |||
&-title { | |||
font-size: 16px; | |||
font-weight: 600; | |||
color: #000; | |||
} | |||
} | |||
.textInput { | |||
margin-top: 10px; | |||
padding: 1px; | |||
width: 100%; | |||
background: linear-gradient(225deg, rgba(81, 217, 202, 1), rgba(114, 100, 245, 1)); | |||
border-radius: 6px; | |||
&-in { | |||
width: 100%; | |||
height: 100%; | |||
background: #fff; | |||
border-radius: 6px; | |||
::v-deep .el-input-group__append { | |||
padding: 0; | |||
} | |||
img { | |||
float: right; | |||
width: 16px; | |||
height: 16px; | |||
margin: 10px; | |||
} | |||
} | |||
.divider { | |||
transform: scaleY (0.25); | |||
width: 100%; | |||
height: 1px; | |||
border-top: 1px solid; | |||
border-image: linear-gradient(225deg, rgba(81, 217, 202, 1), rgba(114, 100, 245, 1)) 1 1; | |||
} | |||
::v-deep .el-input__inner { | |||
border: none; | |||
background-color: #faf9fe; | |||
} | |||
::v-deep .el-textarea__inner { | |||
border: none; | |||
background-color: #faf9fe; | |||
} | |||
} | |||
.makeVideo { | |||
margin: 15px 20px 0; | |||
height: 50px; | |||
text-align: center; | |||
font-size: 20px; | |||
line-height: 50px; | |||
color: #fff; | |||
background: linear-gradient(316deg, #6EE4D7 0%, #37C4F1 32%, #A49AFC 100%); | |||
border-radius: 25px; | |||
} | |||
.upAndBottomBtn { | |||
.moveLayer { | |||
position: absolute; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
top: 20%; | |||
left: 290px; | |||
padding-left: 10px; | |||
width: 80px; | |||
height: 22px; | |||
font-size: 10px; | |||
line-height: 28px; | |||
text-align: center; | |||
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1); | |||
background-color: rgba(255, 255, 255, 1); | |||
border-radius: 11px; | |||
} | |||
img { | |||
vertical-align: middle; | |||
width: 10px; | |||
height: 11px; | |||
margin: 1px 0 2px 0; | |||
} | |||
.below { | |||
top: 25%; | |||
} | |||
.active { | |||
opacity: 0.5; | |||
} | |||
} | |||
} | |||
.bottom { | |||
::v-deep .van-tabbar-item { | |||
color: rgba(51, 51, 51, 1)!important; | |||
font-size: 10px !important; | |||
} | |||
} | |||
.synthesisDialog { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
width: 100vw; | |||
height: 110vh; | |||
z-index: 99999; | |||
color:#333; | |||
background: rgba(0,0,0,0.5); | |||
&-in { | |||
position: absolute; | |||
top: 40%; | |||
left: 50%; | |||
transform: translate(-50%); | |||
padding: 0 15px; | |||
padding-top: 40px; | |||
width: 315px; | |||
height: 95px; | |||
background-color: #fff; | |||
border-radius: 12px; | |||
.progress { | |||
width: 300px; | |||
::v-deep .el-progress__text { | |||
margin-left: 15px; | |||
} | |||
} | |||
} | |||
&-text { | |||
margin-top: 10px; | |||
font-size: 12px; | |||
} | |||
} | |||
.tabSheet { | |||
height: 280px; | |||
padding: 10px; | |||
padding-bottom: 0px; | |||
background-color: #f9f9f9; | |||
&-title { | |||
display: flex; | |||
justify-content: space-between; | |||
height: 40px; | |||
font-size: 13px; | |||
line-height: 40px; | |||
background-color: #fff; | |||
.img { | |||
margin-top: -3px; | |||
margin-right: 3px; | |||
width: 16px; | |||
height: 16px; | |||
} | |||
.yesImg { | |||
margin-top: -13px; | |||
width: 24px; | |||
height: 16px; | |||
} | |||
} | |||
&-content { | |||
height: 220px; | |||
background-color: #f9f9f9; | |||
::v-deep .van-tab{ | |||
background-color: #f9f9f9; | |||
} | |||
::v-deep .van-tab--active { | |||
font-size: 17px; | |||
font-weight: 700; | |||
} | |||
.list { | |||
height: 170px; | |||
overflow-y: scroll; | |||
// scrollbar-width: none; | |||
// -ms-overflow-style: none; | |||
display: flex; | |||
justify-content: space-between; | |||
flex-wrap: wrap; | |||
margin-top: 10px; | |||
.listItem { | |||
display: flex; | |||
justify-content: space-between; | |||
padding: 12px; | |||
margin-bottom: 10px; | |||
width: 170px; | |||
height: 50px; | |||
overflow: hidden; | |||
background: rgba(186,186,189,0.5); | |||
border-radius: 24px; | |||
color: #fff; | |||
img { | |||
width: 32px; | |||
} | |||
>div { | |||
flex: 1; | |||
margin-left: 12px; | |||
} | |||
} | |||
.active { | |||
background: #7264F5; | |||
} | |||
} | |||
.list::-webkit-scrollbar { | |||
width:0; | |||
} | |||
} | |||
.myTab { | |||
display: flex; | |||
padding: 5px; | |||
width: 100%; | |||
height: 40px; | |||
background-color: #EEEEEE; | |||
border-radius: 6px; | |||
&-item { | |||
width: 50%; | |||
height: 30px; | |||
font-size: 14px; | |||
line-height: 30px; | |||
text-align: center; | |||
background-color: #EEEEEE; | |||
border-radius: 6px; | |||
img { | |||
width: 20px; | |||
height: 16px; | |||
margin-right: 8px; | |||
} | |||
} | |||
.active { | |||
background-color: #fff; | |||
} | |||
&-show { | |||
width: 100%; | |||
height: 190px; | |||
&-title { | |||
font-size: 16px; | |||
i { | |||
display: block; | |||
margin-top: 4px; | |||
margin-left: 3px; | |||
width: 23px; | |||
height: 3px; | |||
background: #7264F5; | |||
border-radius: 2px; | |||
} | |||
} | |||
&-list { | |||
height: 145px; | |||
padding-left: 4px; | |||
overflow-y: scroll; | |||
display: flex; | |||
justify-content: start; | |||
flex-wrap: wrap; | |||
&-list::-webkit-scrollbar { | |||
width:0; | |||
} | |||
&-item { | |||
position: relative; | |||
margin-right: 9px; | |||
margin-top: 10px; | |||
width: 80px; | |||
height: 120px; | |||
z-index: 9; | |||
img { | |||
background: red; | |||
width: 80px; | |||
height: 120px; | |||
border-radius: 12px; | |||
} | |||
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) { | |||
margin-right: 0px!important; | |||
} | |||
} | |||
&-active { | |||
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 { | |||
position: relative; | |||
margin-right: 10px; | |||
margin-top: 10px; | |||
width: 80px; | |||
height: 120px; | |||
background: #ddd; | |||
border-radius: 10px; | |||
img { | |||
background-color: #fff; | |||
position: absolute; | |||
top: 50%; | |||
left: 50%; | |||
transform: translate(-50%,-50%); | |||
} | |||
input { | |||
width: 100%; | |||
height: 100%; | |||
opacity: 0; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,98 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- 展示图片 --> | |||
<div v-if="imgUrl" class="shouModelImg"> | |||
<img :src="imgUrl" alt=""> | |||
</div> | |||
<div class="makeMpdel" @click="handleMakeMpdel">立即制作</div> | |||
</div> | |||
</template> | |||
<script> | |||
// 组件 | |||
import Vue from "vue"; | |||
import { Toast } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
// 工具 | |||
import { scrollToID } from "../../utils"; | |||
import HeadTop from "./../../components/common/head.vue"; | |||
Vue.use(Toast); | |||
export default { | |||
components: { | |||
HeadTop, | |||
}, | |||
data() { | |||
return { | |||
inPage: false, | |||
imgUrl: null, | |||
modelId:null | |||
}; | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
// 点击立即制作 | |||
handleMakeMpdel() { | |||
this.$router.push({ | |||
path: '/editModel', | |||
query: { | |||
imgUrl: this.imgUrl, | |||
modelId: this.modelId | |||
} | |||
}) | |||
}, | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
}, | |||
created() { | |||
this.imgUrl=this.$route.query.imgUrl | |||
this.modelId=this.$route.query.modelId | |||
}, | |||
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); | |||
} | |||
} | |||
.shouModelImg { | |||
padding: 20px 30px; | |||
img { | |||
width: 315px; | |||
height: 518px; | |||
border-radius: 10px; | |||
} | |||
} | |||
.makeMpdel { | |||
margin-left: 30px; | |||
width: 315px; | |||
height: 50px; | |||
text-align: center; | |||
font-size: 20px; | |||
line-height: 50px; | |||
color: #fff; | |||
background: linear-gradient(316deg, #6EE4D7 0%, #37C4F1 32%, #A49AFC 100%); | |||
border-radius: 25px; | |||
} | |||
</style> |
@@ -0,0 +1,489 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- 顶部图 --> | |||
<img class="showIMG" src="../../assets/img/BG-Page.png" alt="" /> | |||
<!-- 展示列表 --> | |||
<div class="item"> | |||
<div class="title"> | |||
<img src="../../assets/img/myVideo.png" alt=""> | |||
我的视频作品 | |||
<span class="floatRight" style="float: right;">草稿箱</span> | |||
</div> | |||
<!-- 具体内容 --> | |||
<MyVideoItem :videoList="videoList"/> | |||
</div> | |||
<!-- 分页 --> | |||
<van-pagination | |||
v-model="currentPage" | |||
:total-items="total" | |||
:items-per-page="4" | |||
@change="pageChange" | |||
/> | |||
<div class="goAdd" @click="checkInviteCode">+ 创作新作品</div> | |||
<div | |||
class="messageCover" | |||
:class="isShowMessageCover ? 'active' : ''" | |||
@click="exitCover" | |||
> | |||
<div class="message" id="message"> | |||
<div class="connect" id="message">请添加微信与我们联系</div> | |||
<div class="qrCode" id="message"> | |||
<img src="../../assets/img/SuiMangQRCode.png" alt="" /> | |||
</div> | |||
<div class="phoneNum" id="message">或致电13260039498</div> | |||
<div class="clickToClose">点击空白处关闭</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
// 组件 | |||
import Vue from "vue"; | |||
import { Toast, Dialog } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
import HeadTop from "./../../components/common/head.vue"; | |||
import MyVideoItem from "./../../components/MyVideoItem.vue"; | |||
// 工具 | |||
import { | |||
bytesToSize, | |||
timestampToTime, | |||
scrollToID, | |||
formatSeconds, | |||
} from "../../utils"; | |||
// 接口 | |||
import { | |||
getVideoList, | |||
doGetVideoDetialById, | |||
doFindInviteCode, | |||
deleteVideoById, | |||
} from "../../api/myPage"; | |||
Vue.use(Toast); | |||
export default { | |||
components: { | |||
HeadTop, | |||
MyVideoItem | |||
}, | |||
data() { | |||
return { | |||
showDialog: true,// 弹出层显示隐藏 | |||
inPage: false, | |||
isShowMessageCover: false, | |||
videoList: [], | |||
currentPage: 1, | |||
pageSize: 4, | |||
prePage: "", | |||
total: "", | |||
}; | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
changeTime(timestamp, form) { | |||
return timestampToTime(timestamp, form); | |||
}, | |||
pageChange() { | |||
this.loadVideoList(this.currentPage, this.pageSize); | |||
}, | |||
// 点击空白区域退出提示 | |||
exitCover(e) { | |||
if (e.srcElement.id != "message") { | |||
this.isShowMessageCover = false; | |||
} | |||
}, | |||
// 秒转化时分秒 | |||
formatSeconds(seconds) { | |||
if (seconds) { | |||
return formatSeconds(seconds); | |||
} else { | |||
return "无"; | |||
} | |||
}, | |||
// 将bytes转化 | |||
bytesToSize(bytes) { | |||
if (bytes) { | |||
return bytesToSize(bytes); | |||
} else { | |||
return "无"; | |||
} | |||
}, | |||
// 查看详情 | |||
goDetail(item) { | |||
const status = item.videoStatus; | |||
if (status == 0) { | |||
this.$router.push({ | |||
path: "/getPaper", | |||
query: { | |||
id: item.id, | |||
}, | |||
}); | |||
} else if (status == 1) { | |||
Toast.fail("视频生成中"); | |||
return; | |||
} else if (status == 2) { | |||
const url = item.videoPathUri + item.videoPath; | |||
this.$router.push({ | |||
path: "/downloadVideo", | |||
query: { | |||
videoId: item.videoId, | |||
videoUrl: url, | |||
}, | |||
}); | |||
} else if (status == 3) { | |||
this.$router.push({ | |||
path: "/getPaper", | |||
query: { | |||
id: item.id, | |||
}, | |||
}); | |||
} | |||
}, | |||
goDelete(id) { | |||
Dialog.confirm({ | |||
title: "删除作品", | |||
message: "您确定要删除该作品吗?", | |||
}) | |||
.then(() => { | |||
this.deleteVideoById(id); | |||
this.loadVideoList(this.currentPage, this.pageSize); | |||
}) | |||
.catch(() => { | |||
return; | |||
}); | |||
}, | |||
/** | |||
* @description:获取作品列表 | |||
*/ | |||
async loadVideoList(pageNum, pageSize) { | |||
try { | |||
const res = await getVideoList(pageNum, pageSize); | |||
console.log(res, "获取作品列表"); | |||
this.videoList = res.data.list; | |||
this.total = res.data.total; | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description 通过视频id获取视频详情 | |||
*/ | |||
async getVideoDetialById(id) { | |||
try { | |||
const res = await doGetVideoDetialById(id); | |||
console.log(res, "通过视频id获取视频详情"); | |||
return res.data; | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description 根据id删除作品 | |||
*/ | |||
async deleteVideoById(id) { | |||
try { | |||
const res = await deleteVideoById(id); | |||
console.log(res, "根据id删除作品"); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description:查询用户邀请码状态 | |||
*/ | |||
async checkInviteCode() { | |||
try { | |||
const res = await doFindInviteCode(); | |||
console.log(res, "查询用户邀请码数据"); | |||
const status = res.data.status; | |||
// 当没有邀请码时 | |||
if (status == 0) { | |||
scrollToID("top"); | |||
this.isShowMessageCover = true; | |||
} else if (status == 1) { | |||
this.goCreateNew(); | |||
} | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
goCreateNew() { | |||
this.$router.push("/chooseModel"); | |||
}, | |||
}, | |||
mounted() { | |||
this.inPage = true; | |||
scrollToID("top"); | |||
}, | |||
created() { | |||
this.loadVideoList(1, this.pageSize); | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
.page { | |||
transform: translateX(30%); | |||
opacity: 0; | |||
transition: all 0.5s; // global-transition | |||
padding-bottom: 25px; | |||
&.active { | |||
opacity: 1; | |||
transform: translateX(0); | |||
} | |||
.logoBox { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
overflow: hidden; | |||
cursor: pointer; | |||
background-color: #606060; | |||
.logo { | |||
float: left; | |||
width: 140px; | |||
height: 40px; | |||
} | |||
.userInfo { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
color: #fff; | |||
.userImg { | |||
float: right; | |||
width: 30px; | |||
height: 30px; | |||
margin-top: 3px; | |||
margin-right: 10px; | |||
margin-left: 15px; | |||
} | |||
} | |||
} | |||
.showIMG { | |||
padding: 10px; | |||
width: 100%; | |||
height: 140px; | |||
border-radius: 16px; | |||
} | |||
.item { | |||
padding: 15px 10px; | |||
.title { | |||
font-size: 16px; | |||
font-weight: 500; | |||
// margin-left: 15px; | |||
margin-bottom: 20px; | |||
img { | |||
margin-top: -5px; | |||
vertical-align: middle; | |||
width: 24px; | |||
height: 18px; | |||
background-size: 100% 100%; | |||
} | |||
} | |||
::v-deep .itemList { | |||
.videoModel { | |||
position: relative; | |||
display: flex; | |||
justify-content: space-between; | |||
margin-bottom: 20px; | |||
.videoPrv { | |||
width: 150px; | |||
height: 100px; | |||
// background-color: #979797; | |||
img { | |||
position: absolute; | |||
width: 150px; | |||
height: 100px; | |||
border-radius: 6px; | |||
} | |||
.cover { | |||
position: absolute; | |||
width: 150px; | |||
height: 100px; | |||
line-height: 100px; | |||
background-color: #00000034; | |||
text-align: center; | |||
border-radius: 6px; | |||
color: #fff; | |||
} | |||
} | |||
.videoInfo { | |||
flex: 1; | |||
margin-left: 12px; | |||
.videoName { | |||
font-size: 16px; | |||
font-weight: 600; | |||
} | |||
.createTime { | |||
font-size: 11px; | |||
color: #999999; | |||
margin-top: 10px; | |||
} | |||
} | |||
.showMore { | |||
width: 20px; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
// margin-left: 10px; | |||
div { | |||
width: 4px; | |||
height: 4px; | |||
background-color: #51D9CA; | |||
} | |||
} | |||
.tag { | |||
position: absolute; | |||
right: 10px; | |||
top: 10px; | |||
padding: 1px 5px; | |||
border-radius: 5px; | |||
&.text { | |||
color: #fff; | |||
background-color: #a2a2a290; | |||
} | |||
&.generating { | |||
background-color: #ffa600c2; | |||
} | |||
&.finish { | |||
color: #ffffff; | |||
background-color: #00ff88; | |||
} | |||
&.fail { | |||
color: #ffffff; | |||
background-color: #ff0000c3; | |||
} | |||
} | |||
} | |||
.noWork { | |||
width: 100%; | |||
height: 450px; | |||
line-height: 450px; | |||
font-size: 14px; | |||
font-weight: 600; | |||
text-align: center; | |||
} | |||
} | |||
} | |||
.goAdd { | |||
position: sticky; | |||
bottom: 25px; | |||
left: 10px; | |||
width: 300px; | |||
height: 42px; | |||
line-height: 42px; | |||
font-size: 15px; | |||
color: #fff; | |||
text-align: center; | |||
transform: translateX(-50%); | |||
background-color: #0068b7; | |||
border-radius: 5px; | |||
box-shadow: #60606082 1px 1px 1px 1px; | |||
margin-top: 25px; | |||
margin-left: 190px; | |||
} | |||
.messageCover { | |||
position: fixed; | |||
top: 0; | |||
width: 100%; | |||
height: 100%; | |||
background-color: #00000091; | |||
transition: all 0.3s; | |||
opacity: 0; | |||
z-index: -1; | |||
.message { | |||
position: sticky; | |||
top: 48%; | |||
transform: translate(-50%, -50%); | |||
width: 272px; | |||
height: 290px; | |||
border-radius: 5px; | |||
background-color: #fff; | |||
text-align: center; | |||
transition: all 0.3s; | |||
margin-left: 50%; | |||
padding: 10px; | |||
.connect { | |||
font-size: 12px; | |||
margin: 19px 0 10px 0; | |||
} | |||
.qrCode { | |||
width: 203px; | |||
height: 213px; | |||
background-color: #ffffff; | |||
border-radius: 5px; | |||
border: solid 1px #595959; | |||
margin: auto; | |||
img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
.phoneNum { | |||
float: left; | |||
font-size: 11px; | |||
margin-top: 8px; | |||
margin-left: 34px; | |||
} | |||
.close { | |||
position: absolute; | |||
top: 8px; | |||
right: 15px; | |||
font-size: 18px; | |||
color: #000; | |||
} | |||
.clickToClose { | |||
position: absolute; | |||
font-size: 14px; | |||
color: #fff; | |||
left: 50%; | |||
bottom: -20%; | |||
transform: translateX(-50%); | |||
} | |||
} | |||
&.active { | |||
z-index: 100; | |||
opacity: 1; | |||
} | |||
} | |||
.delete-button { | |||
position: relative; | |||
left: 1%; | |||
height: 100%; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,520 @@ | |||
<template> | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- 顶部图 --> | |||
<img class="showIMG" src="../../assets/img/BG-Page.png" alt="" /> | |||
<!-- 展示列表 --> | |||
<div class="item"> | |||
<div class="title"> | |||
<img src="../../assets/img/myVideo.png" alt=""> | |||
我的视频作品 | |||
</div> | |||
<van-swipe-cell v-for="(item, index) in videoList"> | |||
<div class="itemList"> | |||
<div | |||
v-if="videoList.length > 0" | |||
class="videoModel" | |||
:key="index" | |||
@click="goDetail(item)" | |||
> | |||
<div class="videoPrv"> | |||
<img src="../../assets/img/BG@2x.png" /> | |||
<div v-if="item.videoStatus == 0" class="cover">草稿</div> | |||
<div v-if="item.videoStatus == 1" class="cover">视频生成中</div> | |||
<div v-if="item.videoStatus == 3" class="cover">视频生成失败</div> | |||
<!-- <div v-if="item.videoStatus == ?" class="cover">排队中</div> --> | |||
</div> | |||
<div class="videoInfo"> | |||
<div class="videoName"> | |||
{{ item.title || "暂无" }} | |||
</div> | |||
<div class="createTime"> | |||
视频时长:{{ formatSeconds(item.videoTime) }} | |||
</div> | |||
<div class="createTime"> | |||
视频容量:{{ bytesToSize(item.videoSize) }} | |||
</div> | |||
<div class="createTime"> | |||
创建时间:{{ | |||
changeTime(item.createDate, "YYYY-MM-DD hh:mm:ss") | |||
}} | |||
</div> | |||
</div> | |||
</div> | |||
<div v-if="videoList.length <= 0" class="noWork"> | |||
您还没有视频作品 | |||
</div> | |||
</div> | |||
<template #right> | |||
<van-button | |||
square | |||
text="删除" | |||
type="danger" | |||
class="delete-button" | |||
@click="goDelete(item.id)" | |||
/> | |||
</template> | |||
</van-swipe-cell> | |||
</div> | |||
<!-- 分页 --> | |||
<van-pagination | |||
v-model="currentPage" | |||
:total-items="total" | |||
:items-per-page="4" | |||
@change="pageChange" | |||
/> | |||
<div class="goAdd" @click="checkInviteCode">+ 创作新作品</div> | |||
<div | |||
class="messageCover" | |||
:class="isShowMessageCover ? 'active' : ''" | |||
@click="exitCover" | |||
> | |||
<div class="message" id="message"> | |||
<div class="connect" id="message">请添加微信与我们联系</div> | |||
<div class="qrCode" id="message"> | |||
<img src="../../assets/img/SuiMangQRCode.png" alt="" /> | |||
</div> | |||
<div class="phoneNum" id="message">或致电13260039498</div> | |||
<div class="clickToClose">点击空白处关闭</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
// 组件 | |||
import Vue from "vue"; | |||
import { Toast, Dialog } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
import HeadTop from "./../../components/common/head.vue"; | |||
// 工具 | |||
import { | |||
bytesToSize, | |||
timestampToTime, | |||
scrollToID, | |||
formatSeconds, | |||
} from "../../utils"; | |||
// 接口 | |||
import { | |||
getVideoList, | |||
doGetVideoDetialById, | |||
doFindInviteCode, | |||
deleteVideoById, | |||
} from "../../api/myPage"; | |||
Vue.use(Toast); | |||
export default { | |||
components: { | |||
HeadTop, | |||
}, | |||
data() { | |||
return { | |||
inPage: false, | |||
isShowMessageCover: false, | |||
videoList: [], | |||
currentPage: 1, | |||
pageSize: 4, | |||
prePage: "", | |||
total: "", | |||
}; | |||
}, | |||
computed: { | |||
...mapState({ | |||
characters: (state) => state.publicObj.characters, | |||
}), | |||
}, | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
changeTime(timestamp, form) { | |||
return timestampToTime(timestamp, form); | |||
}, | |||
pageChange() { | |||
this.loadVideoList(this.currentPage, this.pageSize); | |||
}, | |||
// 点击空白区域退出提示 | |||
exitCover(e) { | |||
if (e.srcElement.id != "message") { | |||
this.isShowMessageCover = false; | |||
} | |||
}, | |||
// 秒转化时分秒 | |||
formatSeconds(seconds) { | |||
if (seconds) { | |||
return formatSeconds(seconds); | |||
} else { | |||
return "无"; | |||
} | |||
}, | |||
// 将bytes转化 | |||
bytesToSize(bytes) { | |||
if (bytes) { | |||
return bytesToSize(bytes); | |||
} else { | |||
return "无"; | |||
} | |||
}, | |||
// 查看详情 | |||
goDetail(item) { | |||
const status = item.videoStatus; | |||
if (status == 0) { | |||
this.$router.push({ | |||
path: "/getPaper", | |||
query: { | |||
id: item.id, | |||
}, | |||
}); | |||
} else if (status == 1) { | |||
Toast.fail("视频生成中"); | |||
return; | |||
} else if (status == 2) { | |||
const url = item.videoPathUri + item.videoPath; | |||
this.$router.push({ | |||
path: "/downloadVideo", | |||
query: { | |||
videoId: item.videoId, | |||
videoUrl: url, | |||
}, | |||
}); | |||
} else if (status == 3) { | |||
this.$router.push({ | |||
path: "/getPaper", | |||
query: { | |||
id: item.id, | |||
}, | |||
}); | |||
} | |||
}, | |||
goDelete(id) { | |||
Dialog.confirm({ | |||
title: "删除作品", | |||
message: "您确定要删除该作品吗?", | |||
}) | |||
.then(() => { | |||
this.deleteVideoById(id); | |||
this.loadVideoList(this.currentPage, this.pageSize); | |||
}) | |||
.catch(() => { | |||
return; | |||
}); | |||
}, | |||
/** | |||
* @description:获取作品列表 | |||
*/ | |||
async loadVideoList(pageNum, pageSize) { | |||
try { | |||
const res = await getVideoList(pageNum, pageSize); | |||
console.log(res, "获取作品列表"); | |||
this.videoList = res.data.list; | |||
this.total = res.data.total; | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description 通过视频id获取视频详情 | |||
*/ | |||
async getVideoDetialById(id) { | |||
try { | |||
const res = await doGetVideoDetialById(id); | |||
console.log(res, "通过视频id获取视频详情"); | |||
return res.data; | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description 根据id删除作品 | |||
*/ | |||
async deleteVideoById(id) { | |||
try { | |||
const res = await deleteVideoById(id); | |||
console.log(res, "根据id删除作品"); | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
/** | |||
* @description:查询用户邀请码状态 | |||
*/ | |||
async checkInviteCode() { | |||
try { | |||
const res = await doFindInviteCode(); | |||
console.log(res, "查询用户邀请码数据"); | |||
const status = res.data.status; | |||
// 当没有邀请码时 | |||
if (status == 0) { | |||
scrollToID("top"); | |||
this.isShowMessageCover = true; | |||
} else if (status == 1) { | |||
this.goCreateNew(); | |||
} | |||
} catch (error) { | |||
console.log(error, "error"); | |||
Toast.fail(error.message); | |||
} | |||
}, | |||
goCreateNew() { | |||
this.$router.push("/chooseModel"); | |||
}, | |||
}, | |||
mounted() { | |||
this.inPage = true; | |||
scrollToID("top"); | |||
}, | |||
created() { | |||
this.loadVideoList(1, this.pageSize); | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
.page { | |||
transform: translateX(30%); | |||
opacity: 0; | |||
transition: all 0.5s; // global-transition | |||
padding-bottom: 25px; | |||
&.active { | |||
opacity: 1; | |||
transform: translateX(0); | |||
} | |||
.logoBox { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
overflow: hidden; | |||
cursor: pointer; | |||
background-color: #606060; | |||
.logo { | |||
float: left; | |||
width: 140px; | |||
height: 40px; | |||
} | |||
.userInfo { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
color: #fff; | |||
.userImg { | |||
float: right; | |||
width: 30px; | |||
height: 30px; | |||
margin-top: 3px; | |||
margin-right: 10px; | |||
margin-left: 15px; | |||
} | |||
} | |||
} | |||
.showIMG { | |||
padding: 10px; | |||
width: 355px; | |||
height: 130px; | |||
border-radius: 16px; | |||
} | |||
.item { | |||
padding-top: 15px; | |||
padding-bottom: 15px; | |||
padding-left: 10px; | |||
padding-right: 10px; | |||
.title { | |||
font-size: 16px; | |||
font-weight: 500; | |||
// margin-left: 15px; | |||
margin-bottom: 20px; | |||
img { | |||
margin-top: -5px; | |||
vertical-align: middle; | |||
width: 24px; | |||
height: 18px; | |||
background-size: 100% 100%; | |||
} | |||
} | |||
.itemList { | |||
.videoModel { | |||
position: relative; | |||
display: flex; | |||
justify-content: space-between; | |||
margin-bottom: 25px; | |||
border-bottom: 1px solid #00000010; | |||
padding: 0 20px 25px 25px; | |||
.videoPrv { | |||
width: 150px; | |||
height: 100px; | |||
// background-color: #979797; | |||
img { | |||
position: absolute; | |||
width: 150px; | |||
height: 100px; | |||
} | |||
.cover { | |||
position: absolute; | |||
width: 150px; | |||
height: 100px; | |||
line-height: 100px; | |||
background-color: #00000034; | |||
text-align: center; | |||
color: #fff; | |||
} | |||
} | |||
.videoInfo { | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: space-between; | |||
margin-left: 10px; | |||
.videoName { | |||
font-size: 16px; | |||
font-weight: 600; | |||
} | |||
.createTime { | |||
font-size: 11px; | |||
} | |||
} | |||
.tag { | |||
position: absolute; | |||
right: 10px; | |||
top: 10px; | |||
padding: 1px 5px; | |||
border-radius: 5px; | |||
&.text { | |||
color: #fff; | |||
background-color: #a2a2a290; | |||
} | |||
&.generating { | |||
background-color: #ffa600c2; | |||
} | |||
&.finish { | |||
color: #ffffff; | |||
background-color: #00ff88; | |||
} | |||
&.fail { | |||
color: #ffffff; | |||
background-color: #ff0000c3; | |||
} | |||
} | |||
} | |||
.noWork { | |||
width: 100%; | |||
height: 450px; | |||
line-height: 450px; | |||
font-size: 14px; | |||
font-weight: 600; | |||
text-align: center; | |||
} | |||
} | |||
} | |||
.goAdd { | |||
position: sticky; | |||
bottom: 25px; | |||
left: 10px; | |||
width: 300px; | |||
height: 42px; | |||
line-height: 42px; | |||
font-size: 15px; | |||
color: #fff; | |||
text-align: center; | |||
transform: translateX(-50%); | |||
background-color: #0068b7; | |||
border-radius: 5px; | |||
box-shadow: #60606082 1px 1px 1px 1px; | |||
margin-top: 25px; | |||
margin-left: 190px; | |||
} | |||
.messageCover { | |||
position: fixed; | |||
top: 0; | |||
width: 100%; | |||
height: 100%; | |||
background-color: #00000091; | |||
transition: all 0.3s; | |||
opacity: 0; | |||
z-index: -1; | |||
.message { | |||
position: sticky; | |||
top: 48%; | |||
transform: translate(-50%, -50%); | |||
width: 272px; | |||
height: 290px; | |||
border-radius: 5px; | |||
background-color: #fff; | |||
text-align: center; | |||
transition: all 0.3s; | |||
margin-left: 50%; | |||
padding: 10px; | |||
.connect { | |||
font-size: 12px; | |||
margin: 19px 0 10px 0; | |||
} | |||
.qrCode { | |||
width: 203px; | |||
height: 213px; | |||
background-color: #ffffff; | |||
border-radius: 5px; | |||
border: solid 1px #595959; | |||
margin: auto; | |||
img { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
} | |||
.phoneNum { | |||
float: left; | |||
font-size: 11px; | |||
margin-top: 8px; | |||
margin-left: 34px; | |||
} | |||
.close { | |||
position: absolute; | |||
top: 8px; | |||
right: 15px; | |||
font-size: 18px; | |||
color: #000; | |||
} | |||
.clickToClose { | |||
position: absolute; | |||
font-size: 14px; | |||
color: #fff; | |||
left: 50%; | |||
bottom: -20%; | |||
transform: translateX(-50%); | |||
} | |||
} | |||
&.active { | |||
z-index: 100; | |||
opacity: 1; | |||
} | |||
} | |||
.delete-button { | |||
position: relative; | |||
left: 1%; | |||
height: 100%; | |||
} | |||
} | |||
</style> |
@@ -2,25 +2,34 @@ | |||
<div :class="inPage ? 'page active' : 'page'"> | |||
<div id="top"></div> | |||
<HeadTop /> | |||
<!-- 顶部图 --> | |||
<img class="showIMG" src="../../assets/img/BG-Page.png" alt="" /> | |||
<!-- 展示列表 --> | |||
<div class="item"> | |||
<div class="title">我的视频作品</div> | |||
<van-swipe-cell v-for="(item, index) in videoList"> | |||
<div class="title"> | |||
<img src="../../assets/img/myVideo.png" alt=""> | |||
我的视频作品 | |||
<span class="floatRight" style="float: right;">草稿箱</span> | |||
</div> | |||
<!-- 具体内容 --> | |||
<van-swipe-cell v-for="(item, index) in videoList" :key="item.id?item.id:index"> | |||
<div class="itemList"> | |||
<!-- 每一项 --> | |||
<div | |||
v-if="videoList.length > 0" | |||
class="videoModel" | |||
:key="index" | |||
@click="goDetail(item)" | |||
> | |||
<div class="videoPrv"> | |||
> | |||
<!-- 左侧图片 --> | |||
<div class="videoPrv" @click="goDetail(item)"> | |||
<img src="../../assets/img/BG@2x.png" /> | |||
<div v-if="item.videoStatus == 0" class="cover">草稿</div> | |||
<div v-if="item.videoStatus == 1" class="cover">视频生成中</div> | |||
<div v-if="item.videoStatus == 3" class="cover">视频生成失败</div> | |||
<!-- <div v-if="item.videoStatus == ?" class="cover">排队中</div> --> | |||
</div> | |||
<div class="videoInfo"> | |||
<!-- 中间文字 --> | |||
<div class="videoInfo" @click="goDetail(item)"> | |||
<div class="videoName"> | |||
{{ item.title || "暂无" }} | |||
</div> | |||
@@ -36,13 +45,31 @@ | |||
}} | |||
</div> | |||
</div> | |||
<!-- 右侧更多 --> | |||
<div class="showMore" @click.stop="showDialog(item.id)"> | |||
<div></div> | |||
<div></div> | |||
<div></div> | |||
</div> | |||
<!-- 弹出层 --> | |||
<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"> | |||
<div @click="closeDialog"><i class="el-icon-edit" /> 编辑</div> | |||
<div><i class="el-icon-delete" /> 删除</div> | |||
</van-dialog> --> | |||
</div> | |||
<!-- 无数据处理 --> | |||
<div v-if="videoList.length <= 0" class="noWork"> | |||
您还没有视频作品 | |||
</div> | |||
</div> | |||
<template #right> | |||
<!-- 右滑 --> | |||
<!-- <template #right> | |||
<van-button | |||
square | |||
text="删除" | |||
@@ -50,17 +77,21 @@ | |||
class="delete-button" | |||
@click="goDelete(item.id)" | |||
/> | |||
</template> | |||
</template> --> | |||
</van-swipe-cell> | |||
</div> | |||
<!-- 分页 --> | |||
<van-pagination | |||
v-model="currentPage" | |||
:total-items="total" | |||
:items-per-page="4" | |||
@change="pageChange" | |||
/> | |||
<div class="goAdd" @click="checkInviteCode">+ 创作新作品</div> | |||
<!-- 创建新作品 --> | |||
<!-- <div class="goAdd" @click="checkInviteCode">+ 创作新作品</div> --> | |||
<div class="goNewAdd" @click="checkInviteCode"> | |||
<i class="el-icon-plus"></i> | |||
</div> | |||
<div | |||
class="messageCover" | |||
@@ -84,7 +115,7 @@ | |||
import Vue from "vue"; | |||
import { Toast, Dialog } from "vant"; | |||
import { mapState, mapActions } from "vuex"; | |||
import HeadTop from "./../../components/common/head.vue"; | |||
import HeadTop from "@/components/common/head.vue"; | |||
// 工具 | |||
import { | |||
bytesToSize, | |||
@@ -107,6 +138,10 @@ export default { | |||
}, | |||
data() { | |||
return { | |||
showPopup:false,// 编辑和删除显示隐藏 | |||
clickItem: -1,// 弹出层显示隐藏 | |||
showTrue: true,// 弹出层显示隐藏 | |||
showFalse: false,// 弹出层显示隐藏 | |||
inPage: false, | |||
isShowMessageCover: false, | |||
videoList: [], | |||
@@ -124,7 +159,14 @@ export default { | |||
methods: { | |||
...mapActions(["setCharacters"]), | |||
closeDialog() { | |||
Dialog.close | |||
}, | |||
// 点击显示编辑删除 | |||
showDialog(id) { | |||
this.showPopup=true | |||
this.clickItem=id | |||
}, | |||
go(url) { | |||
this.$router.push(url); | |||
}, | |||
@@ -193,14 +235,14 @@ export default { | |||
}); | |||
} | |||
}, | |||
// 删除作品 | |||
goDelete(id) { | |||
Dialog.confirm({ | |||
title: "删除作品", | |||
message: "您确定要删除该作品吗?", | |||
}) | |||
.then(() => { | |||
this.deleteVideoById(id); | |||
.then(async() => { | |||
await this.deleteVideoById(id); | |||
this.loadVideoList(this.currentPage, this.pageSize); | |||
}) | |||
.catch(() => { | |||
@@ -326,26 +368,33 @@ export default { | |||
} | |||
} | |||
.showIMG { | |||
padding: 10px; | |||
width: 100%; | |||
height: 130px; | |||
height: 140px; | |||
border-radius: 16px; | |||
} | |||
.item { | |||
padding-top: 15px; | |||
padding-bottom: 15px; | |||
padding: 15px 10px; | |||
.title { | |||
font-size: 15px; | |||
font-weight: 600; | |||
margin-left: 15px; | |||
font-size: 16px; | |||
font-weight: 500; | |||
// margin-left: 15px; | |||
margin-bottom: 20px; | |||
img { | |||
margin-top: -5px; | |||
vertical-align: middle; | |||
width: 24px; | |||
height: 18px; | |||
background-size: 100% 100%; | |||
} | |||
} | |||
.itemList { | |||
.videoModel { | |||
position: relative; | |||
display: flex; | |||
justify-content: space-between; | |||
margin-bottom: 25px; | |||
border-bottom: 1px solid #00000010; | |||
padding: 0 20px 25px 25px; | |||
margin-bottom: 20px; | |||
.videoPrv { | |||
width: 150px; | |||
height: 100px; | |||
@@ -354,6 +403,7 @@ export default { | |||
position: absolute; | |||
width: 150px; | |||
height: 100px; | |||
border-radius: 6px; | |||
} | |||
.cover { | |||
position: absolute; | |||
@@ -362,22 +412,63 @@ export default { | |||
line-height: 100px; | |||
background-color: #00000034; | |||
text-align: center; | |||
border-radius: 6px; | |||
color: #fff; | |||
} | |||
} | |||
.videoInfo { | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: space-between; | |||
margin-left: 10px; | |||
flex: 1; | |||
margin-left: 12px; | |||
.videoName { | |||
font-size: 16px; | |||
font-weight: 600; | |||
} | |||
.createTime { | |||
font-size: 11px; | |||
color: #999999; | |||
margin-top: 5px; | |||
} | |||
} | |||
.showMore { | |||
width: 20px; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
// margin-left: 10px; | |||
div { | |||
width: 4px; | |||
height: 4px; | |||
background-color: #51D9CA; | |||
} | |||
} | |||
.dialog { | |||
i { | |||
margin-left: -10px; | |||
} | |||
} | |||
// 弹出层样式 | |||
::v-deep .van-popup { | |||
display: flex; | |||
flex-wrap: wrap; | |||
align-items: center; | |||
justify-content: center; | |||
top: 70%; | |||
left: 290px; | |||
padding-left: 10px; | |||
width: 80px; | |||
height: 60px; | |||
border-radius: 12px; | |||
font-size: 14px; | |||
line-height: 28px; | |||
text-align: center; | |||
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1); | |||
} | |||
::v-deep .van-overlay { | |||
background-color: rgba(0,0,0,0); | |||
} | |||
::v-deep .van-button { | |||
display: none; | |||
} | |||
.tag { | |||
position: absolute; | |||
right: 10px; | |||
@@ -428,6 +519,20 @@ export default { | |||
margin-top: 25px; | |||
margin-left: 190px; | |||
} | |||
.goNewAdd { | |||
position: sticky; | |||
left: 300px; | |||
bottom: 25px; | |||
width: 60px; | |||
height: 60px; | |||
border-radius: 50%; | |||
font-size: 40px; | |||
line-height: 60px; | |||
text-align: center; | |||
color: #ffffff; | |||
background: linear-gradient(316deg, #6EE4D7 0%, #37C4F1 32%, #A49AFC 100%); | |||
box-shadow: 0px 2px 6px 0px rgba(114,100,245,0.25); | |||
} | |||
.messageCover { | |||
position: fixed; | |||
top: 0; | |||