congzc 2 лет назад
Родитель
Сommit
cd9f8b8404
12 измененных файлов: 997 добавлений и 72 удалений
  1. Двоичные данные
      src/assets/icon/icon-loginout.png
  2. Двоичные данные
      src/assets/icon/icon-pencil.png
  3. Двоичные данные
      src/assets/web(1).rar
  4. +70
    -0
      src/assets/web(1)/web/css/client.css
  5. +503
    -0
      src/assets/web(1)/web/js/client.js
  6. +196
    -0
      src/assets/web(1)/web/user.html
  7. +11
    -5
      src/components/common/head.vue
  8. +8
    -3
      src/router/index.js
  9. +61
    -0
      src/views/chat/chat.vue
  10. +19
    -21
      src/views/model/chooseModel.vue
  11. +109
    -33
      src/views/model/editModel.vue
  12. +20
    -10
      src/views/myPage/myPage.vue

Двоичные данные
src/assets/icon/icon-loginout.png Просмотреть файл

До После
Ширина: 48  |  Высота: 48  |  Размер: 1.7 KiB

Двоичные данные
src/assets/icon/icon-pencil.png Просмотреть файл

До После
Ширина: 32  |  Высота: 32  |  Размер: 952 B

Двоичные данные
src/assets/web(1).rar Просмотреть файл


+ 70
- 0
src/assets/web(1)/web/css/client.css Просмотреть файл

@@ -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;
}

+ 503
- 0
src/assets/web(1)/web/js/client.js Просмотреть файл

@@ -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 = '';
}


+ 196
- 0
src/assets/web(1)/web/user.html Просмотреть файл

@@ -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>

+ 11
- 5
src/components/common/head.vue Просмотреть файл

@@ -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;
}
}


+ 8
- 3
src/router/index.js Просмотреть файл

@@ -44,19 +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',


+ 61
- 0
src/views/chat/chat.vue Просмотреть файл

@@ -0,0 +1,61 @@
<template>
<div :class="inPage ? 'page active' : 'page'">
<div id="top"></div>
<!-- 主体内容 -->
<div class="contnet">
<!-- 头部 -->
<div class="head">
<img src="" alt="">
<div>退出聊天</div>
</div>
</div>
</div>
</template>

<script>
// 组件
import Vue from "vue";
import { Toast } from "vant";
import { mapState, mapActions } from "vuex";
// 工具
import { scrollToID } from "../../utils";

Vue.use(Toast);
export default {
data() {
return {
inPage: false,
};
},
computed: {
...mapState({
characters: (state) => state.publicObj.characters,
}),
},

methods: {
...mapActions(["setCharacters"]),

go(url) {
this.$router.push(url);
},
},
created() {},
mounted() {
this.inPage = true;
scrollToID("top");
},
};
</script>

<style lang="scss" scoped>
.page {
transform: translateX(30%);
opacity: 0;
transition: all 0.5s; // global-transition
&.active {
opacity: 1;
transform: translateX(0);
}
}
</style>

+ 19
- 21
src/views/model/chooseModel.vue Просмотреть файл

@@ -3,13 +3,14 @@
<div id="top"></div>
<HeadTop />
<!-- 顶部tab栏 -->
<van-tabs animated color="#7264F5" line-width=46 line-height=6 title-inactive-color="#666" title-active-color="#333">
<van-tab v-for="item in tabList" :key="item.id" :title="item.name">
<van-tabs @click="chooseType1" class="typeOutSide" animated color="#7264F5" line-width=46 line-height=6 title-inactive-color="#666" title-active-color="#333">
<van-tab v-for="item in tabList" :key="item" :title="item">
</van-tab>
</van-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,7 +22,7 @@
男性模板
</div>
</div>
</div>
</div> -->
<!-- 模板列表 -->
<div class="choose">
<div class="modeList">
@@ -67,15 +68,7 @@ export default {
type: 0,
currentId: "",
modeList: [], // 模板列表
tabList: [
{ id: 1, name: '全部' },
{ id: 2, name: '男性' },
{ id: 3, name: '女性' },
{ id: 4, name: '原P' },
{ id: 5, name: '武装直升机' },
{ id: 6, name: '粥P' },
{ id: 7, name: '二刺猿' },
]
tabList: ['全部','男性','女性','原P','武装直升机','粥P','二刺猿']
};
},
computed: {
@@ -86,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");
},
@@ -99,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) {
@@ -235,9 +232,9 @@ export default {
}
.typeOutSide {
position: sticky;
top: 60px;
top: 0px;
z-index: 99;
.type {
display: flex;
justify-content: space-between;
@@ -265,6 +262,7 @@ export default {
}

.choose {
padding-top: 20px;
margin-bottom: 40px;

.modeList {


+ 109
- 33
src/views/model/editModel.vue Просмотреть файл

@@ -7,15 +7,14 @@
<!-- 展示图片 -->
<div v-if="imgUrl" class="shouModelImg">
<img :src="imgUrl" alt="">
<div v-show="showSubtitle" class="shouModelImg-subtitle">
中午吃啥?
</div>
<!-- 字幕 -->
<div v-html="textareaContent" v-show="showSubtitle" class="shouModelImg-subtitle"></div>
</div>
<!-- 文字 -->
<div class="addText">
<div class="addText-title">添加文案</div>
<div class="desalinate">
<span>已录入65字</span>&nbsp;&nbsp;
<span>已录入{{textareaContent.length}}字</span>&nbsp;&nbsp;
<span>视频时长:&nbsp;00:16</span>
</div>
</div>
@@ -23,7 +22,11 @@
<div class="textInput">
<div class="textInput-in">
<!-- 标题 -->
<el-input v-model="inputTitle" placeholder="请输入标题"></el-input>
<el-input v-model="inputTitle" placeholder="请输入标题">
<template slot="append">
<img src="../../assets/icon/icon-pencil.png" alt="">
</template>
</el-input>
<div class="divider" />
<!-- 语音内容 -->
<el-input rows="4" type="textarea" v-model="textareaContent"></el-input>
@@ -54,7 +57,7 @@
</div>
</div>
</div>
<!-- 底部导航栏 -->
<div class="bottom">
<van-tabbar class="bottom-tab">
@@ -101,7 +104,7 @@
<!-- 顶部文字 -->
<div class="tabSheet-title">
<div><img class="img" src="../../assets/icon/icon-microphone.png" alt=""> 更改声音</div>
<div><img src="../../assets/icon/icon-yes.png" alt=""></div>
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div>
</div>
<!-- 主要内容 -->
<div class="tabSheet-content">
@@ -124,7 +127,7 @@
<!-- 顶部文字 -->
<div class="tabSheet-title">
<div><img class="img" src="../../assets/icon/icon-background.png" alt=""> 更换背景</div>
<div><img src="../../assets/icon/icon-yes.png" alt=""></div>
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div>
</div>
<!-- 主要内容 -->
<div class="tabSheet-content">
@@ -143,7 +146,18 @@
<input type="file">
</div>
<!-- 图片列表 -->
<div @click="bjImgAction=index" class="myTab-show-list-item" :class="{'myTab-show-list-active':bjImgAction==index}" v-for="(item,index) in 7" :key="index" ></div>
<div @click="bjImgAction=index" class="myTab-show-list-item" v-for="(item,index) in 7" :key="index" >
<div :class=" {'myTab-show-list-active':bjImgAction==index}">
<div class="whiteBox">
</div>
</div>
<!-- 删除上传素材 -->
<div class="icon-error" v-if="bjAction==1&&bjImgAction==index">
×
</div>
<!-- 中间图片 -->
<img src="" alt="">
</div>
</div>
</div>
</div>
@@ -153,7 +167,7 @@
<!-- 顶部文字 -->
<div class="tabSheet-title">
<div><img class="img" src="../../assets/icon/icon-material.png" alt=""> 更换素材</div>
<div><img src="../../assets/icon/icon-yes.png" alt=""></div>
<div><img class="yesImg" src="../../assets/icon/icon-yes.png" alt=""></div>
</div>
<!-- 主要内容 -->
<div class="tabSheet-content">
@@ -167,12 +181,23 @@
<div class="myTab-show-title">图片<i></i></div>
<div class="myTab-show-list">
<!-- 上传 -->
<div v-if="bjAction==1" class="myTab-show-list-upload">
<div v-if="scAction==1" class="myTab-show-list-upload">
<img src="../../assets/icon/icon-upload@2x.png" alt="">
<input type="file">
</div>
<!-- 图片列表 -->
<div @click="scImgAction=index" class="myTab-show-list-item" :class="{'myTab-show-list-active':scImgAction==index}" v-for="(item,index) in 7" :key="index" ></div>
<div @click="scImgAction=index" class="myTab-show-list-item" v-for="(item,index) in 7" :key="index" >
<div :class=" {'myTab-show-list-active':scImgAction==index}">
<div class="whiteBox">
</div>
</div>
<!-- 删除上传素材 -->
<div class="icon-error" v-if="scAction==1&&scImgAction==index">
×
</div>
<!-- 中间图片 -->
<img src="" alt="">
</div>
</div>
</div>
</div>
@@ -201,19 +226,19 @@ export default {
imgUrl:null,
modelId: null,
inputTitle: '',
textareaContent: '',
textareaContent: '', // 输入框内容
bottomTabActive: 2,// 底部导航高亮
showSubtitle: true, // 是否展示字幕
showSynthesisDialog: false,//合成进度条窗口显示隐藏
showTabSheet: true, //tab栏弹出框显隐
showTabSheet: false, //tab栏弹出框显隐
soundTabList: ['男生', '女生', '御姐', '萝莉'], // 声音tab栏标题
soundListAction:0,//声音tab栏标题高亮
soundListAction:-1,//声音tab栏标题高亮
soundItemList: ['普通话', '英语', '日语', '日语', '日语', '日语', '日语'],// 声音展示列表
soundItemListAction:0,// 声音展示列表高亮
bjAction:0,// 背景:素材库和上传高亮
bjImgAction:0,// 选中背景图高亮
scAction:0,// 素材:素材库和上传高亮
scImgAction:0,// 选中素材图高亮
soundItemListAction:-1,// 声音展示列表高亮
bjAction:-1,// 背景:素材库和上传高亮
bjImgAction:-1,// 选中背景图高亮
scAction:-1,// 素材:素材库和上传高亮
scImgAction:-1,// 选中素材图高亮
};
},
computed: {
@@ -252,6 +277,7 @@ export default {

<style lang="scss" scoped>
.page {

// transform会导致其子元素固定定位失效
// transform: translateX(30%);
opacity: 0;
@@ -265,7 +291,7 @@ export default {
position: relative;
padding: 0 10px;
padding-bottom: 50px;
width: 375px;
.shouModelImg {
display: flex;
@@ -275,6 +301,7 @@ export default {
img {
width: 200px;
height: 351px;
border-radius: 12px;
};
&-subtitle {
position: absolute;
@@ -312,6 +339,15 @@ export default {
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);
@@ -331,7 +367,7 @@ export default {
}
.makeVideo {
margin: 15px 20px 0;
width: 315px;
height: 50px;
text-align: center;
font-size: 20px;
@@ -373,6 +409,7 @@ export default {
}
}
.bottom {

::v-deep .van-tabbar-item {
color: rgba(51, 51, 51, 1)!important;
font-size: 10px !important;
@@ -428,6 +465,11 @@ export default {
width: 16px;
height: 16px;
}
.yesImg {
margin-top: -13px;
width: 24px;
height: 16px;
}
}
&-content {
height: 220px;
@@ -503,7 +545,6 @@ export default {
&-show {
width: 100%;
height: 190px;
background-color: green;
&-title {
font-size: 16px;
i {
@@ -518,6 +559,7 @@ export default {
}
&-list {
height: 145px;
padding-left: 4px;
overflow-y: scroll;
display: flex;
justify-content: start;
@@ -526,24 +568,58 @@ export default {
width:0;
}
&-item {
margin-right: 10px;
position: relative;
margin-right: 9px;
margin-top: 10px;
width: 80px;
height: 120px;
background: red;
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;
margin-right: 0px!important;
}
}
&-active {
margin-right: 8px;
margin-top: 8px;
padding: 2px;
width: 84px;
height: 124px;
border: 1px solid;
border-image: linear-gradient(90deg, rgba(114, 100, 245, 1), rgba(81, 217, 202, 1)) 1 1;
position: absolute;
top: -3px;
left: -3px;
padding: 1px;
z-index: -1;
width: 86px;
height: 126px;
border-radius: 12px;
background: linear-gradient(225deg, rgba(81, 217, 202, 1), rgba(114, 100, 245, 1));
.whiteBox {
transition: all 0.5s;
width: 100%;
height: 100%;
background-color: #fff;
border-radius: 12px;
}
}
&-upload {
position: relative;


+ 20
- 10
src/views/myPage/myPage.vue Просмотреть файл

@@ -14,6 +14,7 @@
<!-- 具体内容 -->
<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"
@@ -45,18 +46,24 @@
</div>
</div>
<!-- 右侧更多 -->
<div class="showMore" @click.stop="showDialog(item)">
<div class="showMore" @click.stop="showDialog(item.id)">
<div></div>
<div></div>
<div></div>
</div>
<!-- 弹出层 -->
<van-dialog class="dialog"
<van-popup v-if="clickItem==item.id" v-model="showPopup">
<div @click="closeDialog"><i class="el-icon-edit" />&nbsp;&nbsp;&nbsp;编辑</div>
<div @click="goDelete(item.id)"><i class="el-icon-delete" />&nbsp;&nbsp;&nbsp; 删除</div>
</van-popup>

<!-- <van-dialog class="dialog"
v-model="clickItem==item?showTrue:showFalse">
<div @click="closeDialog"><i class="el-icon-edit" />&nbsp;&nbsp;&nbsp;编辑</div>
<div><i class="el-icon-delete" />&nbsp;&nbsp;&nbsp; 删除</div>
</van-dialog>
</van-dialog> -->
</div>
<!-- 无数据处理 -->
<div v-if="videoList.length <= 0" class="noWork">
您还没有视频作品
</div>
@@ -131,6 +138,7 @@ export default {
},
data() {
return {
showPopup:false,// 编辑和删除显示隐藏
clickItem: -1,// 弹出层显示隐藏
showTrue: true,// 弹出层显示隐藏
showFalse: false,// 弹出层显示隐藏
@@ -155,8 +163,9 @@ export default {
Dialog.close
},
// 点击显示编辑删除
showDialog(item) {
this.clickItem=item
showDialog(id) {
this.showPopup=true
this.clickItem=id
},
go(url) {
this.$router.push(url);
@@ -226,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(() => {
@@ -417,7 +426,7 @@ export default {
.createTime {
font-size: 11px;
color: #999999;
margin-top: 10px;
margin-top: 5px;
}
}
.showMore {
@@ -438,8 +447,9 @@ export default {
}
}
// 弹出层样式
::v-deep .van-dialog {
::v-deep .van-popup {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
top: 70%;


Загрузка…
Отмена
Сохранить