天天看點

直播間,開發心得

準備活動

由于不想,花很多精力在字元串的拼湊上,心裡還是更傾心vue的條件渲染,整體看起來思路更清晰,代碼更加整齊一緻,于是就開幹了。沒有用到npm,或者其他子產品化生産工具,就從最簡單的html頁面開始撸起。

首先頁面是這樣布局的:

直播間,開發心得

經實際操作,發現熬點雲的播放器會跟vue起沖突。是以采用 iframe 方法,将其分離開,這就是我最開始的想法。

途中遇到的一些問題

1.vue跟奧點雲點播播放器不相容,播放器顯示不出來的問題

解決方法:用iframe架構引入,這樣兩者就不沖突了。

上代碼:

html部分:

<div id="app" v-cloak>
	<div class="img-box" v-show="showPop" @click="closePop">
		<img v-if="showType===1" :src="showImgSrc">
		<audio v-else-if="showType===2" :src="showAudioUrl" controls="controls" controlsList="nodownload"
			autoplay="autoplay">
			您的浏覽器不支援 audio 标簽。
		</audio>
		<video v-else="showType===3" :src="showVideoUrl" controls="controls" controlsList="nodownload"
			autoplay="autoplay">
			您的浏覽器不支援 video 标簽。
		</video>
	</div>
	<ul class="tabs">
		<li class="li-tab" v-for="(item,index) in tab.tabsParam" @click="toggleTabs(index)"
			:class="{active:index!=tab.nowIndex}">{{item}}</li>
	</ul>
	<div class="divTab scroll-first" v-show="tab.nowIndex===0">
		<div class="line-box" v-if="lineShow">
			<iframe frame style="width: 100%;height: 200px;"
				:src="isAdvancedProducts?'pages/drawChart.html?sss=1':'pages/drawChart.html?sss=0'">
			</iframe>
			<img src="./images/up.png" @click="lineShow=false" style="width: 18px;" />
		</div>
		<div class="line-box" v-else="!lineShow">
			<iframe frame style="width: 100%;height: 60px;" src="pages/drawChartMini.html">
			</iframe>
			<img src="./images/down.png" @click="lineShow=true" style="width: 18px;" />
		</div>
		<div id="middle">
			<div class="reflash" @click="getLiveData()">點選重新整理</div>
			<ul class="timeLine">
				<li v-for="(item,keys) in livePart.livePartList">
					<p>{{item.formatTime}} </p>
					<div class="card" v-if="item.type===1">
						<div class="text">{{item.text}}</div>
					</div>
					<div class="card" v-if="item.type===2">
						<div v-if="item.text">
							<div class="text">{{item.text}}</div>
						</div>
						<img class="img-btn" :key="keys" :src="item.imgUrl" @click="enlargeImg(keys)">
					</div>
					<div class="card" v-if="item.type===3">
						<div class="text">
							<div class="text-div">&nbsp;&nbsp;{{item.text}} </div>
							<a :href="item.articleUrl" target="_blank" rel="external nofollow" >
								<div class="link-div">
									<image :src="item.imgUrl" style="width: 40px;height:30px;float: left;">
									</image>
									<span style="line-height: 30px;font-weight: bolder">{{item.articleTitle}}</span>
								</div>
							</a>
						</div>
					</div>
					<div class="card" v-if="item.type===4">
						<div class="width-whole cardd">
							<div class="text-center head1" :class="{'pink':item.benchMark=='否'}">買入政策</div>
							<div class="body2">
								<div class="td1 text-center">
									<h3>{{item.strockName}}</h3>
									<h5>{{item.strockCode}}</h5>
								</div>
								<div class="td2">
									<div>成交價格:{{item.operationPrice}}</div>
									<div>占用資金:{{item.occupancyPrice}}</div>
									<div v-if="item.isPlus" class="red">加倉</div>
								</div>
							</div>
							<div :key="keys" class="buttom text-center" @click="showConst(keys)"
								v-if="!item.display">
								<img class="open-img" src="./images/open.png">
							</div>
							<div class="buttom" v-if="item.display">
								<div class="buttom-item">
									<div class="width-middle">止盈價:{{item.fullPrice}}</div>
									<div class="width-middle">止損價:{{item.lossPrice}}</div>
									<div>操作理由:{{item.operationReason}}</div>
									<div>投顧編号:{{item.adviserNumber}}</div>
								</div>
							</div>
						</div>
					</div>
					<div class="card" v-if="item.type===5">
						<div class="width-whole cardd">
							<div class="text-center head2" :class="{'blue':item.benchMark=='否'}">賣出政策</div>
							<div class="body2">
								<div class="td1 text-center">
									<h3>{{item.strockName}}</h3>
									<h5>{{item.strockCode}}</h5>
								</div>
								<div class="td2">
									<div>成交價格:{{item.operationPrice}}</div>
									<div>賣出比例:{{item.sellingRatio}}</div>
									<div v-if="item.isReduce" class="red">減倉</div>
									<div v-else class="red" :class="{'green':item.profitRatio.charAt(0)==='-'}">
										收益率:{{item.profitRatio}}</div>
								</div>
							</div>
							<div :key="keys" class="buttom text-center" @click="showConst(keys)"
								v-if="!item.display">
								<img class="open-img" src="./images/open.png">
							</div>
							<div class="buttom" v-if="item.display">
								<div class="buttom-item">
									<div>剩餘持倉:{{item.remainingPositions}}</div>
									<div>操作理由:{{item.operationReason}}</div>
									<div>投顧編号:{{item.adviserNumber}}</div>
								</div>
							</div>
						</div>
					</div>
					<div class="card" v-if="item.type===6">
						<img class="img-btn" style="border: none;" :key="keys" src="./images/audio.png"
							@click="showAudio(keys)">
					</div>
					<div class="card" v-if="item.type===7">
						<img class="img-btn" :key="keys" :src="item.imgUrl" @click="showVideo(keys)">
					</div>
				</li>
				<li>
					<p>start</p>
				</li>
				<li class="bottom-tip" id="buttom1">
					<a class="history-btn" href="./pages/history.html" target="_blank" rel="external nofollow" > 檢視曆史資訊</a>
				</li>
			</ul>
			<div class="span-buttom">
				服務到期時間:{{deadline}}
			</div>
		</div>
	</div>
	<div class="divTab scroll-second" v-show="tab.nowIndex===1">
		<iframe frame class="chart-player" style="width: 100%;" :style="{'height':playerHeight}"
			:src="isLiveTime?'pages/liveVideo.html':'pages/recordVideo.html'">
		</iframe>
		<ul class="chart-div">
			<li v-for="(item,key) in interactionPart.chatList">
				<!-- 使用者留言 -->
				<div v-if="item.openid!=1" class="chat-card">
					<img src="./images/user.jpg" class="user-img">
					<div class="chat-content">
						<span class="chart-nickname">{{item.nickname}}</span>&nbsp;
						<span class="chart-time">{{item.formatTime}}</span>
						<div class="chart-sentence"> {{item.chatContent}} </div>
						<div v-if="item.replyStatus==1" style="position: relative;left: -10px;">
							<img class="user-img" src="./images/shilei1.png">
							<div class="chat-content">
								<span class="chart-nickname">特訓營</span>&nbsp;
								<span class="chart-time">{{item.replyFormatTime}}</span>
								<div class="chart-sentence">{{item.replyContent}}</div>
							</div>
						</div>
					</div>
				</div>
				<!-- S軍留言 -->
				<div v-else class="chat-card">
					<img :src="item.imgUrl" class="user-img">
					<div class="chat-content">
						<span class="chart-nickname">{{item.nickname}}</span>&nbsp;
						<span class="chart-time">{{item.formatTime}}</span>
						<div class="chart-sentence"> {{item.chatContent}} </div>
					</div>
				</div>
			</li>
			<li class="bottom-tip" id="buttom2">已經到最低了</li>
		</ul>
		<div class="input-buttom">
			<img src="./images/send.png" class="send-img" />
			<input v-model="interactionPart.submitSentence" @blur="viewDefault" placeholder="我也來說兩句。。。">
			<span @click="submitBtn" :class="{'butt-active':interactionPart.active}">發送</span>
		</div>
	</div>
</div>
           

js部分:

let app = new Vue({
el: '#app',
	data: {
		openId: "",
		userId: "",
		userGoodsId: "",
		userNickName: "",
		nowTime: "", //目前時間
		playerHeight: '', //視訊播放器高度
		showPop: false, //彈窗是否顯示
		showType: "1", //1為圖檔,2為音頻,3為小視訊
		showImgSrc: '', //圖檔放大位址
		showAudioUrl: '', //點選語音位址
		showVideoUrl: '', //視訊播放器位址
		lineShow: false, //折線圖是否顯示
		isLiveTime: false, //是否直播時間
		deadline: "----年--月--日", //服務到期時間
		isAdvancedProducts: true, //是否為進階産品
		tab: { //切換
			tabsParam: ['盤中直播', '課堂互動'], //tab清單
			nowIndex: 0, //預設第一個tab為激活狀态
		},
		livePart: { //盤中直播闆塊
			stTime: "", //預存上一次的時間
			livePartList: [],//資料格式很重要
		},
		interactionPart: { //聊天子產品
			stTime: "", //預存上一次的時間
			chatList: [],//資料格式很重要
			submitSentence: '', //送出聊天内容
			active: false,
		},
		timer: '', //定時器,做用于聊天室定時發送請求
	},
	created() { //尚未挂載,這裡初始化一些資料
		var This = this;
		window.addEventListener('resize', This.setPlayerSize);
		This.getVideoType(); //判斷視訊類型
		This.setPlayerSize(); //根據螢幕寬高度,設定布局
		This.getAllData(); //擷取所有頁面資料

	},
	mounted() { //挂載成功,開始第一個業務邏輯
		var This = this;
		This.timer = setInterval(this.getChatData, 300000); //設定定時器
	},
	methods: {
		goChatBottom: function () { //聊天跳轉到最底部
			let anchor = this.$el.querySelector('#buttom2');
			this.$nextTick(() => {
				document.querySelector(".scroll-second").scrollTop = anchor.offsetTop;
			});
		},
		getVideoType: function () { //判斷視訊類型
			var This = this;
			axios.post(`http://api..`)
				.then(res => {
					// console.log("類型:" + res.data.status);
					var s = res.data.status;
					if (s == 1) {
						This.isLiveTime = true;
						console.log("直播時間");
					} else if (s == 0) {
						This.isLiveTime = false;
						console.log("錄播時間");
					}
				})
				.catch(function (error) {
					console.log("擷取類型失敗");
				});
		},
		getAllData: function () { //頁面初始化,擷取所有資料
			var This = this;
			This.getLiveData(); //擷取直播室資料
			This.getChatData(); //擷取聊天室資料
		},
		getLiveData: function () { //擷取新增的直播間内容,每次擷取,隻擷取新增的
			var This = this;
			This.getNowTime(); //擷取目前時間
			let data = new FormData();
			data.append("startTime", This.livePart.stTime);
			data.append("endTime", This.nowTime);
			data.append("goodsId", This.userGoodsId);
			axios.post(`http://api..`, data)
				.then(res => {
					if (res.data.length == 0) {
						alert("盤中直播已加載至最新");
					} else {
						This.livePart.livePartList = res.data.concat(This.livePart.livePartList);
						// alert("加載成功");
					}
					This.livePart.stTime = This.nowTime;
				})
				.catch(function (error) {
					// alert("操作失敗");
				});
		},
		getChatData: function () { //擷取新增的聊天内容
			var This = this;
			This.getNowTime(); //擷取目前時間
			let data = new FormData();
			data.append("openid", This.openId);
			data.append("startTime", This.interactionPart.stTime);
			data.append("endTime", This.nowTime);
			axios.post(`http://api..`, data)
				.then(res => {
					if (res.data.length == 0) {
						console.log("暫無更多聊天資料");
					} else {
						This.interactionPart.chatList.push.apply(This.interactionPart.chatList, res
							.data);
						console.log("定時重新整理聊天内容成功");
						This.goChatBottom();
					}
					This.interactionPart.stTime = This.nowTime;
				})
				.catch(function (error) {
					// alert("操作失敗");
				});
		},
		showConst: function (index) { //卡片展開點選事件送出
			var This = this;
			let data = new FormData();
			data.append("userid", This.userId);
			data.append("informationId", This.livePart.livePartList[index].id);
			axios.post(`http://api..`, data)
				.then(res => {
					console.log("送出點選事件成功")
				})
				.catch(function (error) {
					// alert("操作失敗");
				});
			this.livePart.livePartList[index].display = true;
		},
		toggleTabs: function (index) { //tab切換
			this.tab.nowIndex = index;
		},
		setPlayerSize: function () { // 擷取浏覽器高度,按照視訊比例作計算
			this.playerHeight = 183 / 320 * window.innerWidth + 'px';
		},
		submitBtn: function () { //送出聊天内容
			var This = this;
			if (This.interactionPart.submitSentence.trim()) {
				axios.post(`http://api..`, {
						"openid": This.openId,
						"nickname": This.userNickName,
						"chatContent": This.interactionPart.submitSentence,
					})
					.then(res => {
						alert("送出成功");
						This.getChatData();
					})
					.catch(function (error) {
						alert("送出失敗");
					});
				console.log("重新整理成功");
				This.interactionPart.submitSentence = "";
			} else {
				alert("不能送出空資料")
			}
		},
		enlargeImg: function (index) { // 放大圖檔
			var This = this;
			this.showPop = true;
			this.showType = 1;
			this.showImgSrc = this.livePart.livePartList[index].imgUrl;
		},
		showAudio: function (index) { //展示音頻
			var This = this;
			This.showPop = true;
			this.showType = 2;
			this.showAudioUrl = This.livePart.livePartList[index].audioUrl
		},
		showVideo: function (index) { //展示視訊
			var This = this;
			This.showPop = true;
			this.showType = 3;
			this.showVideoUrl = This.livePart.livePartList[index].videoUrl
		},
		closePop: function () { //清空展示位址
			var This = this;
			this.showPop = false;
			this.showImgSrc = '';
			this.showAudioUrl = '';
			this.showVideoUrl = '';
		},
		viewDefault: function () { //防止input框輸入完成後頁面上移
			var This = this;
			if (This.interactionPart.active)
				document.body.scrollTop = 0;
			document.documentElement.scrollTop = 0;
		},
		getNowTime: function () { //擷取最新時間
			var _this = this;
			let yy = new Date().getFullYear();
			let mm = new Date().getMonth() + 1;
			let dd = new Date().getDate();
			let hh = new Date().getHours();
			let mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() : new Date()
				.getMinutes();
			let ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() : new Date()
				.getSeconds();
			_this.nowTime = yy + '-' + mm + '-' + dd + ' ' + hh + ':' + mf + ':' + ss;
		},
	},
	watch: {
		"interactionPart.submitSentence"(newVal, oldVal) { //監聽輸入框是否有内容
			if (newVal) {
				this.interactionPart.active = true
			} else {
				this.interactionPart.active = false
			}
		},
	},
	beforeDestroy() { //銷毀前,移除定時器
		if (this.timer) {
			clearInterval(this.timer); //消除定時器
		}
	},
	destroyed() { //執行個體銷毀的同時,移除resize監聽器
		window.removeEventListener('resize', this.setPlayerSize)
	}
});
           

css樣式:

/* content */
/* 所有的樣式,都要考慮到響應式的問題,盡量适配多種終端。主要是手機 */
/* end content */

/* 公共部分 */

[v-cloak] {
    display: none;
}

* {
    margin: 0;
    padding: 0;
}

a {
    text-decoration: none;
    color: #000000;
}

#app {
    width: 100%;
    height: 100%;
}

.text-center {
    text-align: center;
}

.width-whole {
    width: 100%;
}

.bottom-tip {
    text-align: center;
    font-size: small;
    color: gray;
    display: block;
}

/* pops'style */
.img-box {
    z-index: 999;
    background-color: rgba(16, 16, 16, 0.71);
    cursor: not-allowed;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    overflow: auto;
}

.img-box img {
    width: 100%;
    margin-top: 50%;
}

.img-box video {
    position: absolute;
    top: 50%;
    margin-top: -50%;
    width: 100%;
    height: auto;
}

.img-box audio {
    position: absolute;
    top: 50%;
    margin-top: -50%;
    width: 100%;
}

/* tab切換 */

.active {
    border-bottom: 2px solid rgb(255, 221, 221);
    color: black !important;
}

.tabs {
    width: 100%;
    height: 40px;
    line-height: 40px;
    border-bottom: 2px solid red;
    color: red;
    position: sticky;
    top: 0px;
    background-color: #fff;
    z-index: 2;
}

.li-tab {
    width: 50%;
    height: 100%;
    display: inline-block;
    text-align: center;
}

.divTab {
    width: 100%;
    height: 100%;
}

/* 盤中直播 */
.reflash {
    position: fixed;
    margin-top: 10px;
    right: 40px;
    color: white;
    background-color: rgba(255, 0, 0, 0.68);
    padding: 3px 10px;
    border-radius: 20px;
    transition: unset;
    z-index: 5;
}

#middle {
    width: 100%;
    height: 100%;
    overflow: auto;
    z-index: 1;
    background-color: #fff;
}

/* 上證指數 */
.line-box {
    text-align: center;
    position: sticky;
    top: 42px;
    background-color: #fff;
    border-bottom: 8px solid #f4f4f4;
}

/* 直播時間軸 */

.timeLine {
    margin-bottom: 15px;
    margin-top: 15px;
}

.timeLine li {
    background: url(../images/back1.png) repeat-y 20px 0;
    padding-bottom: 10px;
    width: 100%;
}

.timeLine li:after {
    content: " ";
    display: block;
    height: 0;
    clear: both;
    visibility: hidden;
}

.timeLine li:last-child {
    background: none !important;
}

.timeLine li:nth-last-child(2) {
    background: none !important;
}

.timeLine li p {
    background: url(../images/time.png) no-repeat 10px 0;
    font-size: 14px;
    text-align: left;
    padding-left: 34px;
    padding-right: 10px;
    color: #909090;
    line-height: 20px;
}

.timeLine li .card {
    padding: 10px 10px 10px 34px;
}

.timeLine li .text {
    padding: 10px 15px;
    border: 1px solid #c9c9c9;
    border-radius: 8px;
    line-height: 1.5rem;
    color: #202020;
    margin-bottom: 5px;
}

/* .timeLine li:nth-last-child(2) p {
    background: url(../images/now.png) no-repeat 10px 0 !important;
    font-size: 14px;
    text-align: left;
    padding-left: 34px;
    padding-right: 10px;
    color: #f73e3e;
    line-height: 20px;
} */
.timeLine li:first-child p {
    background: url(../images/now.png) no-repeat 10px 0 !important;
    font-size: 14px;
    text-align: left;
    padding-left: 34px;
    padding-right: 10px;
    color: #f73e3e;
    line-height: 20px;
}

/* 圖檔 */

.timeLine .card .img-btn {
    max-width: 90%;
    max-height: 200px;
    border-radius: 8px;
    border: 1px solid #c9c9c9;
}

.open-img {
    width: 20px;
    border: none !important;
    padding-top: 5px;
}

.link-div {
    background-color: rgb(255, 255, 255);
    padding: 7px 10px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    background-image: url('../images/link2.png');
    background-repeat: no-repeat;
    background-position: right top;
    background-color: #f0f0f0;
}

.text-div {
    width: 100%;
    height: 100%;
    overflow-wrap: break-word;
}

/* 買入指令 */

.timeLine .cardd {
    background-color: #faf8f8;
    border-radius: 10px;
}

.pink {
    background-color: rgb(255, 102, 102) !important;
}

.blue {
    background-color: rgb(70, 151, 255) !important;
}

.timeLine .head1 {
    background-color: rgb(255, 0, 64);
    line-height: 30px;
    color: #fff;
    border-radius: 10px 10px 0 0;
}

.timeLine .buttom {
    border-radius: 0 0 10px 10px;
    display: block;
    border-top: 1px solid #d2d2d2;
    margin: 0 10px;
    padding-bottom: 5px;
}

.timeLine .red {
    color: red;
}

.timeLine .green {
    color: green !important;
}

.timeLine .buttom-item {
    background-color: #fff;
    padding: 4px 18px;
}

.timeLine .body1 {
    height: 70px;
    padding-top: 5px;
}

.timeLine .td1 {
    line-height: 30px;
    width: 40%;
    float: left;
}

.timeLine .td2 {
    line-height: 35px;
    float: left;
    padding-left: 10px;
}

.width-middle {
    width: 50%;
    float: left;
}

.timeLine .head2 {
    background-color: rgb(0, 112, 255);
    ;
    line-height: 30px;
    color: #fff;
    border-radius: 10px 10px 0 0;
}

.timeLine .body2 {
    height: 90px;
    padding-top: 5px;
}

.timeLine .body2 .td2 {
    line-height: 30px;
    float: left;
    padding-left: 10px;
}

.timeLine .body2 .td1 {
    line-height: 40px;
    width: 40%;
    float: left;
    border-right: 1px solid #e4e3e3;
}

.history-btn {
    border: 1px solid #aaa;
    border-radius: 20px;
    padding: 5px 15px;
    line-height: 18px;
}

/* 聊天 */

.chart-player {
    position: sticky;
    top: 42px;
    height: 191.179px;
    z-index: 2;
}

.chart-div {
    margin-bottom: 53px;
}

.chart-div li {
    list-style-type: none;
}

.chat-card {
    padding: 0px 10px 20px 10px;
    position: relative;
    border-bottom: 1px solid #ededed;
}

.user-img {
    float: left;
    border-radius: 50%;
    width: 45px;
    padding: 5px;
}

.chat-content {
    display: inline-block;
    width: 75%;
}

.chart-time {
    color: #aaa;
}

.chart-nickname {
    font-size: 16px;
    font-style: italic;
    color: rgb(135, 135, 135);
    line-height: 44px;
    font-weight: bold;
}

.chart-sentence {
    width: 90%;
    height: 100%;
    overflow-wrap: break-word;
    padding: 5px 10px;
    background-color: #f0f0f0;
    color: #9e9e9e;
    font-size: 18px;
    text-align: justify;
    min-height: 30px;
}

.send-img {
    padding-left: 6px;
    height: 18px;
    padding-top: 8px;
}

.input-buttom {
    position: fixed;
    bottom: 0px;
    height: 40px;
    background-color: #fff;
    width: 95%;
    border: 1px solid #aaa;
    margin: 5px;
    border-radius: 8px;
}

.span-buttom {
    position: fixed;
    bottom: 0px;
    height: 15px;
    background-color: #eee;
    width: 100%;
    line-height: 15px;
    font-size: small;
    text-align: center;
    color: red;
}

.input-buttom input {
    width: 70%;
    border: none;
    height: 35px;
    font-size: 16px;
    background-color: transparent;
}

.input-buttom span {
    color: #747474;
    position: relative;
    float: right;
    padding-right: 12px;
    line-height: 40px;
}

.butt-active {
    color: rgb(255, 0, 0) !important;
    font-weight: 300;
}
           

要注意的一點就是,異步請求是需要時間的,傳回成功後的邏輯務必寫在success的回調函數裡面,否則,會出現還沒有擷取到資料,就開始渲染,導緻渲染出錯。

繼續閱讀