8.21提交

This commit is contained in:
zoujiandong
2025-08-21 17:45:03 +08:00
parent bbd3169335
commit f8003ca7a6
40 changed files with 5558 additions and 30 deletions
+95
View File
@@ -0,0 +1,95 @@
<template>
<view class="plv-player-drag-tips">
<view class="plv-player-drag-tips__box">
<image class="plv-player-drag-tips__direction-img" :src="imgSrc"
@error="imageError">
</image>
<text class="plv-player-drag-tips__time">
{{changeTimeFormat}} / {{durationFormat}}
</text>
</view>
</view>
</template>
<script>
import utils from '@/common/util.js';
import backImg from '@/static/skin/back.png';
import forwardImg from '@/static/skin/forward.png'
export default {
props: {
currentTime: {
type: Number
},
duration: {
type: Number
},
changeTime: {
type: Number
}
},
data() {
return {
isForward: false
}
},
watch: {
changeTime(newValue, oldValue){
this.isForward = newValue > oldValue;
}
},
computed: {
timeFormatType() {
this.duration >= 3600 ? 2 : 1;
},
changeTimeFormat() {
return utils.formatTimeByDuration(this.changeTime, this.timeFormatType);
},
durationFormat() {
return utils.formatTimeByDuration(this.duration, this.timeFormatType)
},
imgSrc() {
return this.isForward ? forwardImg : backImg;
}
}
}
</script>
<style>
.plv-player-drag-tips {
width: 250rpx;
height: 160rpx;
background: rgba(0,0,0,.7);
border-radius: 10rpx;
}
.plv-player-drag-tips__box {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
justify-content: center;
align-items: center;
}
.plv-player-drag-tips__direction-img {
width: 100rpx;
height: 100rpx;
}
.plv-player-drag-tips__time {
font-size: 24rpx;
color: #FFFFFF;
}
</style>
+271
View File
@@ -0,0 +1,271 @@
<template>
<view class="plv-player-gesture"
@touchstart="touchstart"
@touchmove="touchmove"
@longpress="handleLongPress"
@touchcancel="handleTouchCancel"
@touchend="touchend">
</view>
</template>
<script>
export default {
props: {
// 屏幕宽度(物理像素)
holeWidth: Number,
// 视频总时长
duration: Number,
// 视频当前播放时间
currentTime: Number
},
computed: {
halfWidth() {
return parseInt(this.holeWidth / 2);
}
},
data() {
return {
// 是否在左区域按下手势
downLeft: false,
// 滑动方向
swipeDir: null
}
},
mounted() {
this.addHandler();
},
methods:{
addHandler() {
// 增加父容器左右滑动监听
let finalTime = -1;
// 计算左右滑动seek进度
const countTime = (position) => {
const { holeWidth, duration, currentTime } = this;
const precent = Math.abs(position) / holeWidth;
const nowTime = currentTime || 0;
const constSecond = duration < 60 ? duration : 60;
let plusTime = parseInt(Math.abs(precent * constSecond));
if (position < 0) plusTime = plusTime * -1;
finalTime = nowTime + plusTime;
finalTime = this.limit(finalTime, 0, duration);
return finalTime;
};
this.addGesture((type, position) => {
switch (type) {
// 下滑
case -1:
// console.log('//下滑');
this.$emit('onGestureEvent', {
type: this.downLeft ? 'LEFT_DOWN' : 'RIGHT_DOWN',
position
});
break;
// 上滑
case -2:
// console.log('//上滑');
this.$emit('onGestureEvent', {
type: this.downLeft ? 'LEFT_UP' : 'RIGHT_UP',
position
});
break;
// 左滑
case 0:
// console.log('//左滑');
this.swipeDir = type;
countTime(position);
this.$emit('onGestureEvent', {
type: 'SEEK_TIME_UPDATE',
finalTime
});
break;
// 右滑
case 1:
// console.log('//右滑');
this.swipeDir = type;
countTime(position);
this.$emit('onGestureEvent', {
type: 'SEEK_TIME_UPDATE',
finalTime
});
break;
// touchend for swipe
case 3:
if (this.swipeDir > -1) {
this.$emit('onGestureEvent', {
type: this.swipeDir ? 'SWIPE_RIGHT' : 'SWIPE_LEFT'
});
this.swipeDir = -1;
}
if (finalTime > -1) {
this.$emit('onGestureSeekTo', finalTime);
finalTime = -1;
}
break;
// doubleTap
case 4:
// console.log('>>>> 双击');
this.$emit('onGestureEvent', {
type: 'DOUBLE_CLICK'
});
break;
// tap
case 5:
// console.log('>>>> 单击');
this.handleClick();
break;
// longTap
case 6:
break;
}
});
},
addGesture(callback) {
const { options } = this;
let startX, startY, moveEndX, moveEndY, X, Y, position, hasType;
let T, lastTap, isJustTouch = true;
const excuteCb = (type) => {
// type 手势类型 -2 上滑 -1 下滑 0 左滑 1 右滑 2 just touch 3 touchend 4 doubleTap 5 tap 6 longTap
if (callback && typeof callback === 'function')
callback(type, position);
};
const YPlus = 20;
const YReduce = YPlus * -1;
const XPlus = 50;
const XReduce = XPlus * -1;
const isXchange = () => {
if (X > XPlus || X < XReduce) {
position = X;
hasType = 'X';
isJustTouch = false;
}
if (X > XPlus) excuteCb(1);
// 左滑
else if (X < XReduce) excuteCb(0);
};
const isYchange = () => {
if (Y > YPlus || Y < YReduce) {
position = Y;
hasType = 'Y';
isJustTouch = false;
}
if (Y > YPlus) excuteCb(-1);
// 上滑
else if (Y < YReduce) excuteCb(-2);
};
const countDirect = () => {
if (hasType) {
hasType === 'X' ? isXchange() : isYchange();
return;
}
isXchange();
isYchange();
};
this.handleTouch = (type, e) => {
switch(type) {
case 'start':
startX = e.changedTouches[0].pageX;
startY = e.changedTouches[0].pageY;
T = Date.now();
hasType = '';
isJustTouch = true;
this.downLeft = startX < this.halfWidth ? true : false;
break;
case 'move':
moveEndX = e.changedTouches[0].pageX;
moveEndY = e.changedTouches[0].pageY;
X = moveEndX - startX;
Y = moveEndY - startY;
countDirect();
break;
case 'end':
if (isJustTouch)
if (lastTap && Date.now() - lastTap <= 300)
excuteCb(4);
else if (Date.now() - T < 1000)
excuteCb(5);
else
excuteCb(6);
else
excuteCb(3);
lastTap = Date.now();
isJustTouch = true;
break;
}
};
},
handleClick() {
this.$emit('onGestureClick');
},
touchstart(e) {
this.handleTouch('start', e);
},
touchmove(e) {
this.handleTouch('move', e);
},
touchend(e) {
this.handleTouch('end', e);
this.$emit('onTouchEnd');
},
handleTouchCancel() {
this.$emit('onTouchCancel');
},
handleLongPress() {
this.$emit('onLongPress');
},
/**
* 控制最小值,最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
limit(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
}
}
</script>
<style>
.plv-player-gesture {
flex: 1;
flex-direction: row;
}
.plv-player-gesture__left {
flex: 5;
}
.plv-player-gesture__right {
flex: 5;
}
</style>
+68
View File
@@ -0,0 +1,68 @@
<template>
<view v-if="isShow" class="plv-player-icon">
<image class="plv-player-icon__img" :src="imgUrl"></image>
<text class="plv-player-icon__text">{{ precent + '%' }}</text>
</view>
</template>
<script>
export default {
props: {
imgUrl: {
type: 'string',
required: true
},
precent: {
type: 'Number',
default: 100,
required: true
}
},
watch: {
precent(num) {
if (!this.isShow) this.isShow = true;
this.startClock();
}
},
data() {
return {
isShow: false
}
},
methods: {
startClock() {
this.clearClock();
this.clock = setTimeout(() => {
this.isShow = false;
}, 1500);
},
clearClock() {
if (this.clock) clearTimeout(this.clock);
this.clock = null;
}
}
}
</script>
<style>
.plv-player-icon {
align-items: center;
justify-content: center;
}
.plv-player-icon__img {
width: 80rpx;
height: 80rpx;
}
.plv-player-icon__text {
color: #FFFFFF;
text-align: center;
}
</style>
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
<template>
<view v-if="isShow" class="plv-player-volume-wrap">
<image class="plv-player-volume__img" :src="value === 0 ? muteImgUrl : imgUrl "></image>
<view class="plv-player-volume__progress">
<view class="plv-player-volume__progress__played" :style="'flex:' + value"></view>
<view class="plv-player-volume__progress_surplus" :style="'flex:' + valueLeft"></view>
</view>
</view>
</template>
<script>
export default {
props: {
imgUrl: {
type: 'string',
required: true
},
muteImgUrl: {
type: 'string',
required: true
},
value: {
type: 'Number',
default: 0.5,
required: true
}
},
watch: {
value(num) {
if (!this.isShow) this.isShow = true;
this.startClock();
}
},
computed: {
valueLeft() {
return 1 - this.value;
}
},
data() {
return {
isShow: false
}
},
methods: {
startClock() {
this.clearClock();
this.clock = setTimeout(() => {
this.isShow = false;
}, 1500);
},
clearClock() {
if (this.clock) clearTimeout(this.clock);
this.clock = null;
}
}
}
</script>
<style>
.plv-player-volume-wrap {
flex-direction: row;
align-items: center;
justify-content: center;
}
.plv-player-volume__img {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.plv-player-volume__progress {
width: 260rpx;
height: 8rpx;
flex-direction: row;
background-color: #09BB07;
}
.plv-player-volume__progress__played {
background-color: #007AFF;
}
.plv-player-volume__progress_surplus {
background-color: #FFFFFF;
}
</style>
+367
View File
@@ -0,0 +1,367 @@
<template>
<view class="custom-tabbar">
<view
class="tabbar-item"
v-for="(item, index) in tabList"
:key="index"
@click="switchTab(index, item)"
:class="{ active: currentTab === index }"
>
<!-- 图标容器 -->
<view class="icon-container">
<!-- <uni-icons
:type="currentTab === index ? item.activeIcon : item.icon"
:size="currentTab === index ? 28 : 24"
:color="currentTab === index ? item.activeColor : item.color"
></uni-icons> -->
<image class="img" :src="currentTab === index ? item.activeIcon : item.icon" alt="" />
<!-- 徽章 -->
<view class="badge" v-if="item.badge && item.badge > 0">
<uni-badge
:text="item.badge > 99 ? '99+' : item.badge.toString()"
type="error"
size="small"
></uni-badge>
</view>
<!-- 红点 -->
<view class="red-dot" v-if="item.showRedDot"></view>
</view>
<!-- 文字 -->
<text class="tab-text" :class="{ active: currentTab === index }">
{{ item.text }}
</text>
<!-- 动画效果 -->
<!-- <view class="active-indicator" v-if="currentTab === index"></view> -->
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue';
import { onShow, onLoad } from "@dcloudio/uni-app";
import home from '@/static/home_nor.png'
import homeOn from '@/static/home_sel.png'
import classroom from "@/static/classroom.png"
import classroomOn from "@/static/classroomOn.png"
import live from "@/static/live.png"
import liveOn from "@/static/liveOn.png"
import education from '@/static/education.png'
import educationOn from '@/static/educationOn.png'
import my from '@/static/my.png'
import myOn from '@/static/myOn.png'
// 定义props
const props = defineProps({
// 可以添加自定义props
});
// 定义emits
const emit = defineEmits(['tabChange']);
// 当前选中的tab
const currentTab = ref(0);
// tabbar数据
const tabList = reactive([
{
icon: home,
activeIcon: homeOn,
text: '首页',
color: '#999999',
activeColor: '#007aff',
badge: 0,
showRedDot: false,
pagePath: '/pages/index/index'
},
{
icon: classroom,
activeIcon: classroomOn,
text: '患教学堂',
color: '#999999',
activeColor: '#007aff',
badge: 5,
showRedDot: false,
pagePath: '/pages/education/education'
},
{
icon: live,
activeIcon: liveOn,
text: '会议·直播',
color: '#999999',
activeColor: '#007aff',
badge: 0,
showRedDot: true,
pagePath: '/pages/meeting/meeting'
},
{
icon: education,
activeIcon:educationOn,
text: '继续教育',
color: '#999999',
activeColor: '#007aff',
badge: 12,
showRedDot: false,
pagePath: '/pages/education/continuing'
},
{
icon: my,
activeIcon: myOn,
text: '我的',
color: '#999999',
activeColor: '#007aff',
badge: 0,
showRedDot: false,
pagePath: '/pages/profile/profile'
}
]);
// 切换tab
const switchTab = (index, item) => {
if (currentTab.value === index) return;
// 更新当前tab
currentTab.value = index;
// 清除红点
if (item.showRedDot) {
item.showRedDot = false;
}
// 页面跳转
uni.switchTab({
url: item.pagePath,
fail: () => {
// 如果页面不存在,使用navigateTo
uni.navigateTo({
url: item.pagePath,
fail: () => {
uni.showToast({
title: '页面开发中',
icon: 'none'
});
}
});
}
});
// 触发父组件事件
emit('tabChange', {
index,
item
});
};
// 设置徽章数量
const setBadge = (index, count) => {
if (index >= 0 && index < tabList.length) {
tabList[index].badge = count;
}
};
// 显示红点
const showRedDot = (index) => {
if (index >= 0 && index < tabList.length) {
tabList[index].showRedDot = true;
}
};
// 隐藏红点
const hideRedDot = (index) => {
if (index >= 0 && index < tabList.length) {
tabList[index].showRedDot = false;
}
};
// 设置当前tab
const setCurrentTab = (index) => {
if (index >= 0 && index < tabList.length) {
currentTab.value = index;
}
};
// 获取当前tab信息
const getCurrentTab = () => {
return {
index: currentTab.value,
item: tabList[currentTab.value]
};
};
// 更新tabbar数据
const updateTabList = (newTabList) => {
if (Array.isArray(newTabList)) {
tabList.splice(0, tabList.length, ...newTabList);
}
};
// 暴露方法给父组件
defineExpose({
setBadge,
showRedDot,
hideRedDot,
setCurrentTab,
getCurrentTab,
updateTabList,
currentTab,
tabList
});
// 页面加载时获取当前页面路径
onLoad(() => {
nextTick(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPath = '/' + currentPage.route;
// 根据当前页面路径设置选中的tab
const tabIndex = tabList.findIndex(item => item.pagePath === currentPath);
if (tabIndex !== -1) {
currentTab.value = tabIndex;
}
});
});
// 页面显示时更新状态
onShow(() => {
// 可以在这里更新徽章数量等状态
console.log('Tabbar onShow - 当前tab:', currentTab.value);
});
// 组件挂载后
onMounted(() => {
console.log('Tabbar组件已挂载');
});
</script>
<style scoped>
.custom-tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
overflow-x: hidden;
height: 120rpx;
background-color: #ffffff;
display: flex;
justify-content: space-around;
align-items: center;
border-top: 2rpx solid #f0f0f0;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.1);
z-index: 999;
padding-bottom: env(safe-area-inset-bottom);
}
.tabbar-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
transition: all 0.3s ease;
}
.img{
width: 46rpx;
height: 46rpx;
}
.icon-container {
width:100%;
overflow-x: hidden;
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8rpx;
}
.badge {
position: absolute;
top: -8rpx;
right: -8rpx;
z-index: 10;
}
.red-dot {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 16rpx;
height: 16rpx;
background-color: #ff0000;
border-radius: 50%;
border: 2rpx solid #ffffff;
z-index: 10;
}
.tab-text {
font-size: 20rpx;
color: #999999;
transition: all 0.3s ease;
font-weight: 400;
}
.tab-text.active {
color: #8B2316;
font-weight: 600;
transform: scale(1.05);
}
.active-indicator {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: linear-gradient(90deg, #007aff, #0056b3);
border-radius: 2rpx;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
width: 0;
opacity: 0;
}
to {
width: 40rpx;
opacity: 1;
}
}
/* 点击效果 */
.tabbar-item:active {
transform: scale(0.95);
}
/* 响应式设计 */
@media (max-width: 750rpx) {
.custom-tabbar {
height: 100rpx;
}
.tab-text {
font-size: 18rpx;
}
}
/* 深色模式支持 */
@media (prefers-color-scheme: dark) {
.custom-tabbar {
background-color: #1c1c1e;
border-top-color: #2c2c2e;
}
.tab-text {
color: #8e8e93;
}
.tab-text.active {
color: #0a84ff;
}
}
</style>
+315
View File
@@ -0,0 +1,315 @@
<template>
<view class="wrap">
<view :class="isFull ? 'player-full' : 'player'">
<plv-player
ref="vod"
class="vod-player"
seekType=1
autoPlay=true
disableScreenCAP=false
rememberLastPosition=false
@onPlayStatus="onPlayStatus"
@onPlayError="onPlayError"
@positionChange="positionChange"
@onEnableSubtitle="onEnableSubtitle"
@onSRTTextConfig="onSRTTextConfig"
@onSRTTitle="onSRTTitle">
</plv-player>
<skin ref="skin"
class="skin-control"
:defaultVolume="defaultVolume"
@onPlayBtnClick="onPlayBtnClick"
@onToSeek="onToSeek"
@onFullBtnClick="onFullBtnClick"
@onHdBtnClick="onHdBtnClick"
@onRateBtnClick="onRateBtnClick"
@onVolumeChanged="onVolumeChanged"
@onScalingBtnClick="onScalingBtnClick"
@onScreenShot="onScreenShot"
@onChangeSRTBtnClick="onChangeSRTBtnClick"
@onChangeSRTSingleMode="onChangeSRTSingleMode"
@onOpenSRT="onOpenSRT">
</skin>
</view>
<view v-if="!isFull" style="padding: 20rpx;">
<input class="uni-input" style="min-height: 35px; height: 35px; border: 1px solid #cccccc;font-size: 14px;" :value="videoVid"
@input="onVidInput" placeholder="请输入视频vid" />
<button type="primary" @click="setVid()">播放</button>
</view>
</view>
</template>
<script>
import Skin from '@/components/plv-player-skin/skin.nvue';
import DragTips from '@/components/plv-player-skin/drag-tips.nvue';
const dom = weex.requireModule('dom');
export default {
components: {
Skin
},
onReady() {
this.vodPlayer = this.$refs.vod;
this.skin = this.$refs.skin;
// plus.screen.lockOrientation('portrait-primary');
},
data() {
return {
title: '播放器Demo',
// 播放器组件
vodPlayer: null,
// 控制栏组件
skin: null,
//是否全屏
isFull: false,
// 默认音量
defaultVolume: 0.5,
videoVid: 'cfb7a69a75b5004ddb137d7bd96aa7d7_c'
};
},
methods: {
//输入vid
onVidInput: function(event) {
this.videoVid = event.detail.value;
},
setVid() {
if(!this.videoVid) {
uni.showToast({
title: "请输入视频Vid",
icon: "none"
})
return;
}
const { vodPlayer } = this;
vodPlayer.setVid({
vid:this.videoVid,
level:0
}, (ret) => {
this.text = JSON.stringify(ret);
if (ret.errMsg != null) {
uni.showToast({
title: ret.errMsg,
icon: "none"
})
}
});
},
onPlayError(e){
if (e.detail.errEvent != null) {
uni.showToast({
title:'playErrorEvent - '+e.detail.errEvent,
icon: "none"
})
}
},
positionChange(e){
this.skin.timeUpdate(e.detail.currentPosition);
},
onEnableSubtitle(e){
console.log("===1" + e.detail);
this.skin.setEnableSubtitle(e.detail.isEnableSubtitle, e.detail.isEnableDoubleSubtitle);
},
onSRTTextConfig(e) {
console.log("===1 fontColor: " + e.detail.fontColor);
this.skit.setSRTTextConfig(e.detail);
},
onSRTTitle(e) {
console.log("===1 onSRTTitle" + e.detail);
},
onPlayStatus(e){
const { skin, vodPlayer } = this;
const state = e.detail.playbackState;
const preparedToPlay = e.detail.preparedToPlay;
if (state != null) {
this.skin.changePlayStatus(state === 'start');
} else if (preparedToPlay != null) {
this.updateDuration();
this.updateLevels();
this.updateScaling();
}
},
updateDuration() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getDuration(null, ret => {
this.skin.updateDuration(ret.duration);
});
},
updateLevels() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getLevelNum(null, ret => {
this.skin.updateLevels(ret.levelNum);
});
vodPlayer.getCurrentLevel(null, ret => {
this.skin.updateCurrentLevel(ret.currentLevel);
});
},
updateScaling() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getScalingMode({}, ret => {
this.skin.updateScaling(ret.scalingMode);
});
},
onPlayBtnClick(isPlaying) {
const { vodPlayer } = this;
if (!vodPlayer) return;
isPlaying ? vodPlayer.start() : vodPlayer.pause();
},
onHdBtnClick(level) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeLevel({level});
},
onRateBtnClick(speed) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setSpeed({speed});
},
onScalingBtnClick(scalingMode) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setScalingMode({scalingMode});
},
onChangeSRTBtnClick(srt) {
console.log("===1 " + srt);
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeSRT(srt);
},
onChangeSRTSingleMode(isSingle) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeSRTSingleMode(isSingle);
},
onOpenSRT(isOpen) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setOpenSRT({
openSrt: isOpen
});
},
onFullBtnClick(isFull) {
// plus.screen.unlockOrientation();
plus.navigator.setFullscreen(isFull);
//通过h5+实现横竖屏
// isFull ? plus.screen.lockOrientation('landscape-primary') : plus.screen.lockOrientation('portrait-primary');
//通过原生实现横竖屏切换
const { vodPlayer } = this;
if (!vodPlayer) return;
isFull ? vodPlayer.changeToLandscape() : vodPlayer.changeToPortrait();
this.isFull = isFull;
},
onToSeek(time) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.seekTo({seconds: time});
},
onVolumeChanged(value) {
const { vodPlayer, skin } = this;
if (!vodPlayer) return;
vodPlayer.getVolume(null, ret => {
const changedValue = ret.volume + value;
const realValue = this.limit(changedValue, 0, 1);
vodPlayer.setVolume({volume: realValue});
skin.updateVolumeValue(realValue);
});
},
onScreenShot() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.snapshot(null, result =>{
if(result.errMsg != null){
console.log(result.errMsg)
}
});
},
/**
* 控制最小值,最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
limit(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
}
}
</script>
<style>
.wrap {
flex: 1;
}
.title {
height: 140rpx;
}
.player {
height: 400rpx;
}
.player-full {
flex: 1;
}
.vod-player {
flex: 1;
}
.skin-control {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.hide {
height: 0;
}
</style>