20240517
This commit is contained in:
224
components/WF-address/WF-address.vue
Normal file
224
components/WF-address/WF-address.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<view class="wrapper">
|
||||
<view class="Sarch">
|
||||
<input class="put" @confirm="confirm" confirm-type="search" v-model="searchValue" bg-color="#F4F4F4" :show-action="false"
|
||||
placeholder="请输入您要搜索的城市">
|
||||
</input>
|
||||
</view>
|
||||
<scroll-view class="calendar-list" scroll-y="true" :scroll-into-view="scrollIntoId">
|
||||
<!-- 国家/城市列表 -->
|
||||
<view class="" v-if="searchValue == ''">
|
||||
<view v-for="(item, index) in Citylist" :id="index" :key="index">
|
||||
<view class="letter-header">{{ index}}</view>
|
||||
<view class="city-div" v-for="(city, i) in item" :key="i" @tap="back_city(city)">
|
||||
<text :class="CtId==city.id ? 'cityClases':'city'">{{ city.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" v-else>
|
||||
<!-- 搜索结果 -->
|
||||
<view class="city-div" v-for="(item, index) in searchList" :key="index" @tap="back_city(item)">
|
||||
<text :class="CtId==item.id ? 'cityClases':'city'">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 右侧字母 -->
|
||||
<view class="letters" v-if="searchValue == ''">
|
||||
<view :class="selectLetter==index ? 'letters-item_back':'letters-item'" v-for="(item,index) in Citylist"
|
||||
:key="index" @tap="scrollTo(index)">
|
||||
{{ index}}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 选中之后字母 -->
|
||||
<view class="mask" v-if="showMask">
|
||||
<view class="mask-r">{{selectLetter}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import address from "./address.js"//测试数据,可根据业务需求替换为接口数据
|
||||
export default {
|
||||
name: "WF-address",
|
||||
data() {
|
||||
return {
|
||||
selectLetter: 'A',//右侧字母默认
|
||||
searchValue: '',//搜索文字
|
||||
scrollIntoId: '',//滚动位置id
|
||||
searchList: [],//搜索数据
|
||||
showMask: false,//mask展示
|
||||
Citylist: [],//展示数据
|
||||
CtId: '',//选中 国家/城市 id
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.Citylist = address
|
||||
},
|
||||
methods: {
|
||||
scrollTo(letter) {
|
||||
this.showMask = true
|
||||
this.selectLetter = letter
|
||||
setTimeout(() => {
|
||||
this.showMask = false
|
||||
}, 300);
|
||||
this.scrollIntoId = letter;
|
||||
},
|
||||
confirm(e) {
|
||||
this.searchList = []
|
||||
var Citylist = Object.entries(this.Citylist)
|
||||
for (var i = 0; i < Citylist.length; i++) {
|
||||
for (var w = 0; w < Citylist[i][1].length; w++) {
|
||||
if (Citylist[i][1][w].name.indexOf(this.searchValue) > -1) {
|
||||
this.searchList.push(Citylist[i][1][w])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
back_city(item) {
|
||||
this.CtId=item.id
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.letters-item_back {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50rpx;
|
||||
background: linear-gradient(-44.2deg, rgba(163, 39, 241, 1) 0%, rgba(239, 108, 38, 1) 100%, rgba(245, 112, 40, 1) 100%);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
line-height: 30rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.Sarch {
|
||||
padding: 10rpx 30rpx;
|
||||
margin: auto;
|
||||
border-bottom: 1rpx solid rgba(249, 250, 252, 1);
|
||||
.put{
|
||||
background: rgba(249, 250, 252, 1);
|
||||
padding: 15rpx 20rpx;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
top: 83rpx;
|
||||
left: 0rpx;
|
||||
right: 0rpx;
|
||||
width: 750rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.mask-r {
|
||||
height: 120rpx;
|
||||
width: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 40rpx;
|
||||
color: #FFFFFF
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 85rpx;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.calendar-list {
|
||||
position: absolute;
|
||||
top: 93rpx;
|
||||
bottom: 0rpx;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.letters {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
bottom: 0px;
|
||||
width: 50rpx;
|
||||
top: 260rpx;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.letters-item {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50rpx;
|
||||
margin-bottom: 10rpx;
|
||||
opacity: 1;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.letter-header {
|
||||
font-size: 22rpx;
|
||||
color: #333333;
|
||||
padding-left: 24rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ebedef;
|
||||
height: 64rpx;
|
||||
background: rgba(249, 250, 252, 1);
|
||||
|
||||
}
|
||||
|
||||
.city-div {
|
||||
width: 660rpx;
|
||||
height: 85rpx;
|
||||
margin-left: 24rpx;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-color: #F9FAFC;
|
||||
border-bottom-style: solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-right: 35rpx;
|
||||
}
|
||||
|
||||
.cityClases {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1rpx;
|
||||
background-image: linear-gradient(to right, #F57028, #A327F1);
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
padding-left: 32rpx;
|
||||
}
|
||||
|
||||
.city {
|
||||
padding-left: 32rpx;
|
||||
color: rgba(42, 23, 49, 1);
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
</style>
|
||||
3837
components/WF-address/address.js
Normal file
3837
components/WF-address/address.js
Normal file
File diff suppressed because it is too large
Load Diff
244
components/barrage/BarrageInput.nvue
Normal file
244
components/barrage/BarrageInput.nvue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="showFixedFooter"
|
||||
class="fixed-footer"
|
||||
:class="[isFullScreen ? 'full-screen' : '', NotchClass]">
|
||||
<view class="place-holder" @click="handleToggleFixedFooter"></view>
|
||||
<view
|
||||
class="barrage-input-bar">
|
||||
<image
|
||||
v-if="pickColorMode"
|
||||
class="color-btn"
|
||||
src="@/static/barrage/color_active@2x.png"
|
||||
@click="togglePickColorMode"></image>
|
||||
<image
|
||||
v-else
|
||||
class="color-btn"
|
||||
:src="isFullScreen ? require('@/static/barrage/color_white@2x.png') : require('@/static/barrage/color_gray@2x.png')"
|
||||
@click="togglePickColorMode"></image>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
ref="barrageFormInput"
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="barrageText"
|
||||
adjust-position="false"
|
||||
placeholder="弹幕走一波"
|
||||
placeholder-style="font-size:13px;color:#999;"
|
||||
cursor-spacing="10px"
|
||||
maxlength="30"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@keyboardheightchange="handleKeyboardHeightChange">
|
||||
<text class="total-label">{{ restLength }}</text>
|
||||
</view>
|
||||
<text
|
||||
class="send-btn"
|
||||
@click="handleBarrageSend"
|
||||
>发送</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="keyboardHeight"
|
||||
class="barrage-color-bar"
|
||||
:style="{height: keyboardHeight + 'px'}">
|
||||
<text class="title">弹幕颜色</text>
|
||||
<view class="lists">
|
||||
<view
|
||||
class="color-item"
|
||||
:class="activeColorIndex === index ? 'active' : ''"
|
||||
v-for="(color, index) in barrageColors">
|
||||
<view
|
||||
class="color-box"
|
||||
:style="{backgroundColor: color}"
|
||||
@click="handleColorPick(index)"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { platform, safeArea, screenHeight, screenWidth } = uni.getSystemInfoSync();
|
||||
//const hasNotchInScreen = plus.navigator.hasNotchInScreen();
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
hasNotchInScreen,
|
||||
barrageText: '' ,// 弹幕输入框文字
|
||||
keyboardHeight: 0 // 软键盘高度
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isFullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showFixedFooter: {
|
||||
type: Boolean
|
||||
},
|
||||
pickColorMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
barrageColors: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
activeColorIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
restLength () {
|
||||
return this.barrageText.length < 30 ? (30 - this.barrageText.length) : 0
|
||||
},
|
||||
NotchClass () {
|
||||
return this.hasNotchInScreen ? 'notch' : '';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showFixedFooter: function(val) {
|
||||
if (!val) {
|
||||
this.keyboardHeight = 0;
|
||||
}
|
||||
},
|
||||
// fix: 安卓横屏不触发keyboardheightchange
|
||||
isFullScreen: function(val) {
|
||||
if (val) {
|
||||
this.handleKeyboardHeightChange();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
focusAction () {
|
||||
console.log('弹幕输入框focus');
|
||||
let tag = false;
|
||||
this.$refs.barrageFormInput.focus();
|
||||
// 重新计算软键盘高度
|
||||
this.handleKeyboardHeightChange();
|
||||
},
|
||||
blurAction () {
|
||||
console.log('弹幕输入框blur');
|
||||
this.$refs.barrageFormInput.blur();
|
||||
},
|
||||
cleanTextAction () {
|
||||
this.barrageText = '';
|
||||
},
|
||||
handleFocus () {
|
||||
this.$emit('handleFocus');
|
||||
},
|
||||
handleBlur () {
|
||||
this.$emit('handleBlur');
|
||||
},
|
||||
handleKeyboardHeightChange (e) {
|
||||
console.log('KeyboardHeightChange', e);
|
||||
if (e && e.detail && !e.detail.height) return;
|
||||
if (platform === 'ios') {
|
||||
this.keyboardHeight = this.isFullScreen ? e.detail.height : (e.detail.height - safeArea.top + 15);
|
||||
} else {
|
||||
this.keyboardHeight = this.isFullScreen ? 207 : e.detail.height;
|
||||
}
|
||||
console.log('keyboradHeihgt:', this.keyboardHeight);
|
||||
},
|
||||
togglePickColorMode () {
|
||||
// 安卓横屏禁用
|
||||
if (platform === 'android' && this.isFullScreen) return;
|
||||
this.$emit('togglePickColorMode');
|
||||
},
|
||||
handleBarrageSend () {
|
||||
this.$emit('handleBarrageSend', this.barrageText);
|
||||
},
|
||||
handleColorPick (index) {
|
||||
this.$emit('handleColorPick', index);
|
||||
},
|
||||
handleToggleFixedFooter () {
|
||||
this.$emit('handleToggleFixedFooter');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.fixed-footer
|
||||
position: fixed
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
&.full-screen
|
||||
&.notch
|
||||
.barrage-input-bar
|
||||
padding: 0 88rpx
|
||||
.barrage-color-bar
|
||||
padding: 40rpx 88rpx
|
||||
.barrage-input-bar
|
||||
background-color: #27282A
|
||||
.input-wrapper
|
||||
background-color: #FFFFFF
|
||||
.barrage-color-bar
|
||||
background-color: #3D3D3F
|
||||
.title
|
||||
color: #FFFFFF
|
||||
.place-holder
|
||||
border-bottom: 1px solid #444;
|
||||
.place-holder
|
||||
flex: 1;
|
||||
border-bottom: 1px solid #DDD;
|
||||
.barrage-input-bar
|
||||
padding: 0 30rpx
|
||||
height: 90rpx
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
background-color: #FFF
|
||||
box-shadow: 0px 1px 0px 0rpx #DDDDDD, 0px -1px 0px 0px #DDDDDD;
|
||||
.color-btn
|
||||
width: 60rpx
|
||||
height: 60rpx
|
||||
.input-wrapper
|
||||
flex 1
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
margin: 0 20rpx
|
||||
padding: 0 30rpx
|
||||
height: 70rpx
|
||||
background-color: #f5f5f5
|
||||
border-radius: 100rpx
|
||||
.input
|
||||
flex 1
|
||||
font-size: 26rpx
|
||||
.total-label
|
||||
font-size: 24rpx
|
||||
color #999
|
||||
.send-btn
|
||||
width: 112rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx
|
||||
background: #FF9520;
|
||||
border-radius: 32rpx;
|
||||
font-size: 30rpx
|
||||
color: #fff
|
||||
text-align: center
|
||||
.barrage-color-bar
|
||||
padding: 40rpx 60rpx
|
||||
background-color: #F5F5F5
|
||||
.title
|
||||
font-size: 24rpx
|
||||
color: #666
|
||||
.lists
|
||||
padding: 25rpx 9rpx
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
.color-item
|
||||
justify-content: center
|
||||
align-items: center
|
||||
margin: 15rpx 21rpx
|
||||
padding: 5rpx
|
||||
border-radius: 100%
|
||||
&.active
|
||||
border: 2rpx solid #FF920A;
|
||||
.color-box
|
||||
width: 44rpx
|
||||
height: 44rpx
|
||||
border-radius: 100%
|
||||
</style>
|
||||
103
components/barrage/ControlBar.nvue
Normal file
103
components/barrage/ControlBar.nvue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view class="barrage-control-bar">
|
||||
<view class="left">
|
||||
<template v-if="barrageOpen">
|
||||
<image
|
||||
class="btn"
|
||||
src="@/static/barrage/open@2x.png"
|
||||
@click="handleToggleBarrageOpen"></image>
|
||||
<image
|
||||
v-if="settingBarrageMode"
|
||||
class="btn space"
|
||||
src="@/static/barrage/setting_active@2x.png"
|
||||
@click="handleToggleBarrageSetting"></image>
|
||||
<image
|
||||
v-else
|
||||
class="btn space"
|
||||
src="@/static/barrage/setting_gray@2x.png"
|
||||
@click="handleToggleBarrageSetting"></image>
|
||||
</template>
|
||||
<image
|
||||
v-else
|
||||
class="btn"
|
||||
src="@/static/barrage/close_gray@2x.png"
|
||||
@click="handleToggleBarrageOpen"></image>
|
||||
</view>
|
||||
<view
|
||||
v-if="barrageOpen"
|
||||
class="right"
|
||||
@click="handleToggleFixedFooter">
|
||||
<text class="text">{{ placeHolderText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BarrageControlBar',
|
||||
props: {
|
||||
barrageOpen: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
settingBarrageMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
barrageSendDisabled: {
|
||||
type: Boolean
|
||||
},
|
||||
barrageSendDisabledTime: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeHolderText () {
|
||||
return this.barrageSendDisabled ? this.barrageSendDisabledTime + 's' : '点我发弹幕';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleToggleBarrageOpen () {
|
||||
this.$emit('handleToggleBarrageOpen');
|
||||
},
|
||||
handleToggleBarrageSetting () {
|
||||
this.$emit('handleToggleBarrageSetting');
|
||||
},
|
||||
handleToggleFixedFooter () {
|
||||
this.$emit('handleToggleFixedFooter');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.barrage-control-bar
|
||||
flex-direction: row
|
||||
padding: 10rpx 30rpx
|
||||
background-color: #FFF
|
||||
.left
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
padding: 0 20rpx
|
||||
background-color: #F5F5F5
|
||||
border-radius: 30rpx
|
||||
border 1rpx solid #DDDDDD
|
||||
.btn
|
||||
width: 60rpx
|
||||
height: 60rpx
|
||||
&.space
|
||||
margin-left: 40rpx
|
||||
.right
|
||||
flex 1
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
margin-left: 20rpx
|
||||
padding: 0 30rpx
|
||||
background-color: #F5F5F5
|
||||
border-radius: 30rpx
|
||||
border 1rpx solid #DDDDDD
|
||||
.text
|
||||
font-size: 26rpx
|
||||
line-height: 26rpx
|
||||
color #999
|
||||
</style>
|
||||
267
components/barrage/SettingLayer.nvue
Normal file
267
components/barrage/SettingLayer.nvue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<!-- 弹幕设置弹层 -->
|
||||
<view
|
||||
v-if="settingBarrageMode"
|
||||
class="barrage-setting-layer"
|
||||
:class="[isFullScreen ? 'full-screen' : '', NotchClass]">
|
||||
<view class="place-holder" @click="handleToggleBarrageSetting"></view>
|
||||
<view class="layer">
|
||||
<view class="header">
|
||||
<image
|
||||
v-if="isFullScreen"
|
||||
class="back-btn"
|
||||
src="@/static/barrage/back@2x.png"
|
||||
@click="handleToggleBarrageSetting"></image>
|
||||
<text class="title">弹幕设置</text>
|
||||
<image
|
||||
v-if="!isFullScreen"
|
||||
class="close-btn"
|
||||
src="@/static/barrage/close_setting@2x.png"
|
||||
@click="handleToggleBarrageSetting"
|
||||
></image>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view class="setting-item">
|
||||
<text class="item-label">不透明度</text>
|
||||
<view class="item-slider">
|
||||
<custom-slider
|
||||
class="slider"
|
||||
:value="barrageOpacity"
|
||||
@change="handleSetBarrageOpacity"></custom-slider>
|
||||
<text class="item-value">{{ barrageOpacity }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-item">
|
||||
<text class="item-label">字号</text>
|
||||
<view class="item-slider">
|
||||
<custom-slider
|
||||
class="slider"
|
||||
:max="maxFontSize"
|
||||
:min="minFontSize"
|
||||
:step="fontSizeStep"
|
||||
:value="barrageFontSize"
|
||||
@change="handleSetBarrageFontSize"
|
||||
></custom-slider>
|
||||
<text class="item-value">{{ barrageFontSizeLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-item">
|
||||
<text class="item-label">速度</text>
|
||||
<view class="item-slider">
|
||||
<custom-slider
|
||||
class="slider"
|
||||
:step="speedStep"
|
||||
:value="barrageSpeedValue"
|
||||
@change="handleSetBarrageSpeed"></custom-slider>
|
||||
<text class="item-value">{{ barrageSpeedLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="setting-item">
|
||||
<text class="item-label">显示区域</text>
|
||||
<view class="item-slider">
|
||||
<custom-slider
|
||||
class="slider"
|
||||
:value="barrageArea"
|
||||
@change="handleSetBarrageArea"
|
||||
></custom-slider>
|
||||
<text class="item-value">{{ barrageArea }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomSlider from '@/components/player/CustomSlider.nvue'
|
||||
|
||||
//const hasNotchInScreen = plus.navigator.hasNotchInScreen();
|
||||
|
||||
const fontSizes = [
|
||||
{
|
||||
label: '超小',
|
||||
value: 16
|
||||
},
|
||||
{
|
||||
label: '小',
|
||||
value: 22
|
||||
},
|
||||
{
|
||||
label: '正常',
|
||||
value: 28
|
||||
},
|
||||
{
|
||||
label: '大',
|
||||
value: 34
|
||||
},
|
||||
{
|
||||
label: '超大',
|
||||
value: 40
|
||||
}
|
||||
]
|
||||
|
||||
const speeds = [
|
||||
{
|
||||
label: '超慢',
|
||||
value: 17000
|
||||
},
|
||||
{
|
||||
label: '慢',
|
||||
value: 14000
|
||||
},
|
||||
{
|
||||
label: '正常',
|
||||
value: 10000
|
||||
},
|
||||
{
|
||||
label: '快',
|
||||
value: 6000
|
||||
},
|
||||
{
|
||||
label: '超快',
|
||||
value: 3000
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomSlider
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hasNotchInScreen,
|
||||
speedStep: 25
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isFullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
settingBarrageMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
barrageOpacity: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
barrageFontSize: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
barrageSpeed: {
|
||||
type: Number,
|
||||
default: 10000
|
||||
},
|
||||
barrageArea: {
|
||||
type: Number,
|
||||
default: 100
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
maxFontSize () {
|
||||
return fontSizes[fontSizes.length - 1].value
|
||||
},
|
||||
minFontSize () {
|
||||
return fontSizes[0].value
|
||||
},
|
||||
fontSizeStep () {
|
||||
return (this.maxFontSize - this.minFontSize) / (fontSizes.length - 1)
|
||||
},
|
||||
barrageFontSizeLabel () {
|
||||
const barrageFontSize = fontSizes.find(item => item.value === this.barrageFontSize);
|
||||
return barrageFontSize.label
|
||||
},
|
||||
barrageSpeedLabel () {
|
||||
const barrageSpeed = speeds.find(item => item.value === this.barrageSpeed);
|
||||
return barrageSpeed.label
|
||||
},
|
||||
barrageSpeedValue () {
|
||||
const barrageSpeedIndex = speeds.findIndex(item => item.value === this.barrageSpeed);
|
||||
return barrageSpeedIndex * this.speedStep
|
||||
},
|
||||
NotchClass () {
|
||||
return this.hasNotchInScreen ? 'notch' : '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleToggleBarrageSetting () {
|
||||
this.$emit('handleToggleBarrageSetting');
|
||||
},
|
||||
handleSetBarrageOpacity (value) {
|
||||
this.$emit('handleSetBarrageOpacity', value);
|
||||
},
|
||||
handleSetBarrageFontSize (value) {
|
||||
this.$emit('handleSetBarrageFontSize', value);
|
||||
},
|
||||
handleSetBarrageSpeed (value) {
|
||||
const activeSpeedIndex = value / this.speedStep;
|
||||
value = speeds[activeSpeedIndex].value
|
||||
console.log(value);
|
||||
this.$emit('handleSetBarrageSpeed', value);
|
||||
},
|
||||
handleSetBarrageArea (value) {
|
||||
this.$emit('handleSetBarrageArea', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.barrage-setting-layer
|
||||
position: fixed
|
||||
top: 0
|
||||
bottom: 0
|
||||
right: 0
|
||||
left: 0
|
||||
&.full-screen
|
||||
flex-direction: row
|
||||
.layer
|
||||
width 500rpx
|
||||
.content
|
||||
flex 1
|
||||
&.notch
|
||||
.layer
|
||||
width: 588rpx;
|
||||
.header
|
||||
padding: 0 108rpx 0 20rpx
|
||||
.content
|
||||
padding-right: 88rpx
|
||||
.place-holder
|
||||
flex: 1
|
||||
.layer
|
||||
align-self: stretch
|
||||
.header
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
height: 80rpx
|
||||
padding: 0 20rpx
|
||||
background-color: #141414
|
||||
.title
|
||||
flex: 1
|
||||
font-size: 28rpx
|
||||
color: #FF920A
|
||||
.back-btn, .close-btn
|
||||
width: 56rpx
|
||||
height: 56rpx
|
||||
.content
|
||||
justify-content: space-around
|
||||
height: 638rpx
|
||||
background-color: rgba(20, 20, 20, 0.8)
|
||||
.setting-item
|
||||
padding: 0 30rpx
|
||||
.item-label
|
||||
font-size: 24rpx
|
||||
color #FFFFFF
|
||||
.item-slider
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
.slider
|
||||
flex 1
|
||||
.item-value
|
||||
min-width: 72rpx
|
||||
font-size: 24rpx
|
||||
color #FFFFFF
|
||||
</style>
|
||||
125
components/barrage/barrage.vue
Normal file
125
components/barrage/barrage.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<view class="box">
|
||||
<template v-for="item in lists">
|
||||
<view
|
||||
:ref="item.doomId"
|
||||
class="box-item"
|
||||
:style="{top:item.top+'px', opacity: barrageOpacity / 100 }">
|
||||
<view
|
||||
:class="[item.self ? 'self': '']">
|
||||
<text :style="{color: `#${item.fc.substr(2)}`, fontSize: barrageFontSize + 'rpx'}">{{item.content}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
|
||||
export default {
|
||||
name: "barrage",
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
idx: 0,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
playerHeight: {
|
||||
type: Number
|
||||
},
|
||||
barrageOpacity: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
barrageFontSize: {
|
||||
type: Number,
|
||||
default: 18
|
||||
},
|
||||
barrageSpeed: {
|
||||
type: Number,
|
||||
default: 10000
|
||||
},
|
||||
barrageArea: {
|
||||
type: Number,
|
||||
default: 100
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//添加弹幕测试doomData = {text: '6666666'}
|
||||
add(doomData) {
|
||||
this.addNvue(doomData)
|
||||
},
|
||||
addNvue(doomData) {
|
||||
let that = this
|
||||
this.idx++
|
||||
let doomId = this.idx
|
||||
// todo 弹幕区域调整
|
||||
// let top = Math.ceil(Math.random() * this.playerHeight)
|
||||
let top = this.dealTop(this.playerHeight, this.barrageArea);
|
||||
doomData.doomId = doomId
|
||||
doomData.top = top
|
||||
// console.log("接收到的弹幕数据", doomData)
|
||||
this.lists.push(doomData)
|
||||
this.$nextTick(function(e) {
|
||||
setTimeout(function(e) {
|
||||
that.move(doomData)
|
||||
}, 200)
|
||||
})
|
||||
},
|
||||
dealTop (height, area) {
|
||||
let column = Math.ceil(area / 25);
|
||||
let randomColumn = Math.floor(Math.random() * column);
|
||||
return Math.ceil(height * randomColumn / 4);
|
||||
},
|
||||
//nvue原生页面弹幕移动
|
||||
move(doomData) {
|
||||
let that = this
|
||||
let doomId = doomData.doomId
|
||||
let el = this.$refs[doomId][0]
|
||||
// console.log("弹幕节点信息", el)
|
||||
animation.transition(el, {
|
||||
styles: {
|
||||
transform: 'translateX(-100%)',
|
||||
},
|
||||
duration: this.barrageSpeed, //ms
|
||||
timingFunction: 'linear', //匀速移动
|
||||
delay: 0 //ms
|
||||
}, () => {
|
||||
//删除对应的弹幕数据,实测会导致渲染闪烁,如有需要自行优化
|
||||
// that.lists.splice(that.lists.indexOf(doomData), 1)
|
||||
// console.log("弹幕移动结束")
|
||||
})
|
||||
},
|
||||
clean() {
|
||||
this.lists = [];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.box
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
white-space: nowap;
|
||||
.box-item
|
||||
position: absolute;
|
||||
right: -88rpx;
|
||||
width: 750px;
|
||||
margin-top: 20rpx;
|
||||
flex-direction: row;
|
||||
transform: 'translateX(100%)';
|
||||
transformOrigin: 'left';
|
||||
timingFunction: 'linear';
|
||||
.self
|
||||
padding: 10rpx;
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #FFFFFF;
|
||||
background-color: rgba(102, 102, 102, 0.3);
|
||||
</style>
|
||||
1063
components/city.js
Normal file
1063
components/city.js
Normal file
File diff suppressed because it is too large
Load Diff
140
components/curry-swiper/curry-slide.vue
Normal file
140
components/curry-swiper/curry-slide.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<view class="carousel-3d-slide" :style="slideStyle" :class="computedClasses" @click="goTo()">
|
||||
<slot :index="index" :isCurrent="isCurrent" :leftIndex="leftIndex" :rightIndex="rightIndex"/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
name: 'curry-slide',
|
||||
props: {
|
||||
index: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
parent: this.$parent.$parent.$parent,
|
||||
styles: {},
|
||||
zIndex: 999
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isCurrent () {
|
||||
return this.index === this.parent.currentIndex
|
||||
},
|
||||
leftIndex () {
|
||||
return this.getSideIndex(this.parent.leftIndices)
|
||||
},
|
||||
rightIndex () {
|
||||
return this.getSideIndex(this.parent.rightIndices)
|
||||
},
|
||||
slideStyle () {
|
||||
let styles = {}
|
||||
if (!this.isCurrent) {
|
||||
const rIndex = this.leftIndex
|
||||
const lIndex = this.rightIndex
|
||||
if (rIndex >= 0 || lIndex >= 0) {
|
||||
styles = rIndex >= 0 ? this.calculatePosition(rIndex, true, this.zIndex) : this.calculatePosition(lIndex, false, this.zIndex)
|
||||
styles.opacity = 1
|
||||
styles.visibility = 'visible'
|
||||
}
|
||||
if (this.parent.hasHiddenSlides) {
|
||||
if (this.matchIndex(this.parent.leftOutIndex)) {
|
||||
styles = this.calculatePosition(this.parent.leftIndices.length - 1, false, this.zIndex)
|
||||
} else if (this.matchIndex(this.parent.rightOutIndex)) {
|
||||
styles = this.calculatePosition(this.parent.rightIndices.length - 1, true, this.zIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.assign(styles, {
|
||||
'border-width': this.parent.border + 'px',
|
||||
'width': this.parent.slideWidth + 'px',
|
||||
'height': this.parent.slideHeight + 'px',
|
||||
'transition': ' transform ' + this.parent.animationSpeed + 'ms, ' +
|
||||
' opacity ' + this.parent.animationSpeed + 'ms, ' +
|
||||
' visibility ' + this.parent.animationSpeed + 'ms'
|
||||
})
|
||||
},
|
||||
computedClasses () {
|
||||
return {
|
||||
[`left-${this.leftIndex + 1}`]: this.leftIndex >= 0,
|
||||
[`right-${this.rightIndex + 1}`]: this.rightIndex >= 0,
|
||||
'current': this.isCurrent
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSideIndex (array) {
|
||||
let index = -1
|
||||
array.forEach((pos, i) => {
|
||||
if (this.matchIndex(pos)) {
|
||||
index = i
|
||||
}
|
||||
})
|
||||
return index
|
||||
},
|
||||
matchIndex (index) {
|
||||
return (index >= 0) ? this.index === index : (this.parent.total + index) === this.index
|
||||
},
|
||||
calculatePosition (i, positive, zIndex) {
|
||||
const z = !this.parent.disable3d ? parseInt(this.parent.inverseScaling) + ((i + 1) * 100) : 0
|
||||
const y = !this.parent.disable3d ? parseInt(this.parent.perspective) : 0
|
||||
const leftRemain = (this.parent.space === 'auto')
|
||||
? parseInt((i + 1) * (this.parent.width / 1.2), 10) // 1.5
|
||||
: parseInt((i + 1) * (this.parent.space), 10)
|
||||
const transform = (positive)
|
||||
? 'translateX(' + (leftRemain) + 'px) translateZ(-' + z + 'px) ' +
|
||||
'rotateY(-' + y + 'deg)'
|
||||
: 'translateX(-' + (leftRemain) + 'px) translateZ(-' + z + 'px) ' +
|
||||
'rotateY(' + y + 'deg)'
|
||||
const top = this.parent.space === 'auto' ? 0 : parseInt((i + 1) * (this.parent.space))
|
||||
return {
|
||||
transform: transform,
|
||||
top: top,
|
||||
zIndex: zIndex - (Math.abs(i) + 1)
|
||||
}
|
||||
},
|
||||
goTo () {
|
||||
if (!this.isCurrent) {
|
||||
if (this.parent.clickable === true) {
|
||||
this.parent.goFar(this.index)
|
||||
}
|
||||
} else {
|
||||
this.parent.onMainSlideClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.carousel-3d-slide {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
border-color: #023c41;
|
||||
border-style: solid;
|
||||
background-size: cover;
|
||||
background-color: #ccc;
|
||||
display: block;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.carousel-3d-slide {
|
||||
text-align: left;
|
||||
}
|
||||
.carousel-3d-slide img {
|
||||
width: 100%;
|
||||
}
|
||||
.carousel-3d-slide.current {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
transform: none !important;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
410
components/curry-swiper/curry-swiper.vue
Normal file
410
components/curry-swiper/curry-swiper.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<view class="carousel-3d-container" :style="{height: this.slideHeight + 'px'}">
|
||||
<view class="carousel-3d-slider" :style="{width: this.slideWidth + 'px', height: this.slideHeight + 'px'}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* eslint-disable */
|
||||
const noop = () => {
|
||||
}
|
||||
export default {
|
||||
name: 'curry-swiper',
|
||||
props: {
|
||||
// Number of slides
|
||||
count: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// Slides perspective position
|
||||
perspective: {
|
||||
type: [Number, String],
|
||||
default: 35
|
||||
},
|
||||
// Number of slides displayed on each page
|
||||
display: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
loop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// Animation between slides in milliseconds
|
||||
animationSpeed: {
|
||||
type: [Number, String],
|
||||
default: 500
|
||||
},
|
||||
// Animation direction
|
||||
dir: {
|
||||
type: String,
|
||||
default: 'ltr'
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 360
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 270
|
||||
},
|
||||
border: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
// Space between slides in pixels
|
||||
space: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// Start slide index. First slide has 0 index
|
||||
startIndex: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// Enable navigation by clicking on slide
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disable3d: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// Minimum distance in pixels to swipe before a slide advance is triggered
|
||||
minSwipeDistance: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
// Slide inverse scaling
|
||||
inverseScaling: {
|
||||
type: [Number, String],
|
||||
default: 300
|
||||
},
|
||||
onLastSlide: {
|
||||
type: Function,
|
||||
default: noop
|
||||
},
|
||||
onSlideChange: {
|
||||
type: Function,
|
||||
default: noop
|
||||
},
|
||||
bias: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
onMainSlideClick: {
|
||||
type: Function,
|
||||
default: noop
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
viewport: 0,
|
||||
currentIndex: 0,
|
||||
total: 0,
|
||||
dragOffset: 0,
|
||||
dragStartX: 0,
|
||||
mousedown: false,
|
||||
zIndex: 998
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
count () {
|
||||
this.computeData()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLastSlide () {
|
||||
return this.currentIndex === this.total - 1
|
||||
},
|
||||
isFirstSlide () {
|
||||
return this.currentIndex === 0
|
||||
},
|
||||
isNextPossible () {
|
||||
return !(!this.loop && this.isLastSlide)
|
||||
},
|
||||
isPrevPossible () {
|
||||
return !(!this.loop && this.isFirstSlide)
|
||||
},
|
||||
slideWidth () {
|
||||
const vw = this.viewport
|
||||
const sw = parseInt(this.width) + (parseInt(this.border, 10) * 2)
|
||||
return vw < sw ? vw : sw
|
||||
},
|
||||
slideHeight () {
|
||||
const sw = parseInt(this.width, 10) + (parseInt(this.border, 10) * 2)
|
||||
const sh = parseInt(parseInt(this.height) + (this.border * 2), 10)
|
||||
const ar = this.calculateAspectRatio(sw, sh)
|
||||
return this.slideWidth / ar
|
||||
},
|
||||
visible () {
|
||||
const v = (this.display > this.total) ? this.total : this.display
|
||||
return v
|
||||
},
|
||||
hasHiddenSlides () {
|
||||
return this.total > this.visible
|
||||
},
|
||||
leftIndices () {
|
||||
let n = (this.visible - 1) / 2
|
||||
n = (this.bias.toLowerCase() === 'left' ? Math.ceil(n) : Math.floor(n))
|
||||
const indices = []
|
||||
for (let m = 1; m <= n; m++) {
|
||||
indices.push((this.dir === 'ltr')
|
||||
? (this.currentIndex + m) % (this.total)
|
||||
: (this.currentIndex - m) % (this.total))
|
||||
}
|
||||
return indices
|
||||
},
|
||||
rightIndices () {
|
||||
let n = (this.visible - 1) / 2
|
||||
n = (this.bias.toLowerCase() === 'right' ? Math.ceil(n) : Math.floor(n))
|
||||
const indices = []
|
||||
for (let m = 1; m <= n; m++) {
|
||||
indices.push((this.dir === 'ltr')
|
||||
? (this.currentIndex - m) % (this.total)
|
||||
: (this.currentIndex + m) % (this.total))
|
||||
}
|
||||
return indices
|
||||
},
|
||||
leftOutIndex () {
|
||||
let n = (this.visible - 1) / 2
|
||||
n = (this.bias.toLowerCase() === 'left' ? Math.ceil(n) : Math.floor(n))
|
||||
n++
|
||||
if (this.dir === 'ltr') {
|
||||
return ((this.total - this.currentIndex - n) <= 0)
|
||||
? (-parseInt(this.total - this.currentIndex - n))
|
||||
: (this.currentIndex + n)
|
||||
} else {
|
||||
return (this.currentIndex - n)
|
||||
}
|
||||
},
|
||||
rightOutIndex () {
|
||||
let n = (this.visible - 1) / 2
|
||||
n = (this.bias.toLowerCase() === 'right' ? Math.ceil(n) : Math.floor(n))
|
||||
n++
|
||||
if (this.dir === 'ltr') {
|
||||
return (this.currentIndex - n)
|
||||
} else {
|
||||
return ((this.total - this.currentIndex - n) <= 0)
|
||||
? (-parseInt(this.total - this.currentIndex - n, 10))
|
||||
: (this.currentIndex + n)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Go to next slide
|
||||
*/
|
||||
goNext () {
|
||||
if (this.isNextPossible) {
|
||||
this.isLastSlide ? this.goSlide(0) : this.goSlide(this.currentIndex + 1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Go to previous slide
|
||||
*/
|
||||
goPrev () {
|
||||
if (this.isPrevPossible) {
|
||||
this.isFirstSlide ? this.goSlide(this.total - 1) : this.goSlide(this.currentIndex - 1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Go to slide
|
||||
* @param {String} index of slide where to go
|
||||
*/
|
||||
goSlide (index) {
|
||||
this.currentIndex = (index < 0 || index > this.total - 1) ? 0 : index
|
||||
if (this.isLastSlide) {
|
||||
if (this.onLastSlide !== noop) {
|
||||
console.warn('onLastSlide deprecated, please use @last-slide')
|
||||
}
|
||||
this.onLastSlide(this.currentIndex)
|
||||
this.$emit('last-slide', this.currentIndex)
|
||||
}
|
||||
this.$emit('before-slide-change', this.currentIndex)
|
||||
setTimeout(() => this.animationEnd(), this.animationSpeed)
|
||||
},
|
||||
/**
|
||||
* Go to slide far slide
|
||||
*/
|
||||
goFar (index) {
|
||||
let diff = (index === this.total - 1 && this.isFirstSlide) ? -1 : (index - this.currentIndex)
|
||||
if (this.isLastSlide && index === 0) {
|
||||
diff = 1
|
||||
}
|
||||
const diff2 = (diff < 0) ? -diff : diff
|
||||
let timeBuff = 0
|
||||
let i = 0
|
||||
while (i < diff2) {
|
||||
i += 1
|
||||
const timeout = (diff2 === 1) ? 0 : (timeBuff)
|
||||
setTimeout(() => (diff < 0) ? this.goPrev(diff2) : this.goNext(diff2), timeout)
|
||||
timeBuff += (this.animationSpeed / (diff2))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Trigger actions when animation ends
|
||||
*/
|
||||
animationEnd () {
|
||||
if (this.onSlideChange !== noop) {
|
||||
console.warn('onSlideChange deprecated, please use @after-slide-change')
|
||||
}
|
||||
this.onSlideChange(this.currentIndex)
|
||||
this.$emit('after-slide-change', this.currentIndex)
|
||||
},
|
||||
/**
|
||||
* Trigger actions when mouse is released
|
||||
* @param {Object} e The event object
|
||||
*/
|
||||
handleMouseup () {
|
||||
this.mousedown = false
|
||||
this.dragOffset = 0
|
||||
},
|
||||
/**
|
||||
* Trigger actions when mouse is pressed
|
||||
* @param {Object} e The event object
|
||||
*/
|
||||
handleMousedown (e) {
|
||||
if (!e.touches) {
|
||||
e.preventDefault()
|
||||
}
|
||||
this.mousedown = true
|
||||
this.dragStartX = ('ontouchstart' in window) ? e.touches[0].clientX : e.clientX
|
||||
},
|
||||
/**
|
||||
* Trigger actions when mouse is pressed and then moved (mouse drag)
|
||||
* @param {Object} e The event object
|
||||
*/
|
||||
handleMousemove (e) {
|
||||
if (!this.mousedown) {
|
||||
return
|
||||
}
|
||||
const eventPosX = ('ontouchstart' in window) ? e.touches[0].clientX : e.clientX
|
||||
const deltaX = (this.dragStartX - eventPosX)
|
||||
this.dragOffset = deltaX
|
||||
if (this.dragOffset > this.minSwipeDistance) {
|
||||
this.handleMouseup()
|
||||
this.goNext()
|
||||
} else if (this.dragOffset < -this.minSwipeDistance) {
|
||||
this.handleMouseup()
|
||||
this.goPrev()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A mutation observer is used to detect changes to the containing node
|
||||
* in order to keep the magnet container in sync with the height its reference node.
|
||||
*/
|
||||
attachMutationObserver () {
|
||||
const MutationObserver = window.MutationObserver ||
|
||||
window.WebKitMutationObserver ||
|
||||
window.MozMutationObserver
|
||||
if (MutationObserver) {
|
||||
const config = {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
characterData: true
|
||||
}
|
||||
this.mutationObserver = new MutationObserver(() => {
|
||||
this.$nextTick(() => {
|
||||
this.computeData()
|
||||
})
|
||||
})
|
||||
if (this.$el) {
|
||||
this.mutationObserver.observe(this.$el, config)
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Stop listening to mutation changes
|
||||
*/
|
||||
detachMutationObserver () {
|
||||
if (this.mutationObserver) {
|
||||
this.mutationObserver.disconnect()
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Get the number of slides
|
||||
* @return {Number} Number of slides
|
||||
*/
|
||||
getSlideCount () {
|
||||
if (this.$slots.default !== undefined) {
|
||||
return this.$slots.default.filter((value) => {
|
||||
return value.tag !== void 0
|
||||
}).length
|
||||
}
|
||||
return 0
|
||||
},
|
||||
/**
|
||||
* Calculate slide with and keep defined aspect ratio
|
||||
* @return {Number} Aspect ratio number
|
||||
*/
|
||||
calculateAspectRatio (width, height) {
|
||||
return Math.min(width / height)
|
||||
},
|
||||
/**
|
||||
* Re-compute the number of slides and current slide
|
||||
*/
|
||||
computeData (firstRun) {
|
||||
this.total = this.getSlideCount()
|
||||
if (firstRun || this.currentIndex >= this.total) {
|
||||
this.currentIndex = parseInt(this.startIndex) > this.total - 1 ? this.total - 1 : parseInt(this.startIndex)
|
||||
}
|
||||
this.viewport = this.$el.clientWidth
|
||||
},
|
||||
setSize () {
|
||||
this.$el.style.cssText += 'height:' + this.slideHeight + 'px;'
|
||||
this.$el.childNodes[0].style.cssText += 'width:' + this.slideWidth + 'px;' + ' height:' + this.slideHeight + 'px;'
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.computeData(true)
|
||||
this.attachMutationObserver()
|
||||
if (!this.$isServer) {
|
||||
window.addEventListener('resize', this.setSize)
|
||||
if ('ontouchstart' in window) {
|
||||
this.$el.addEventListener('touchstart', this.handleMousedown)
|
||||
this.$el.addEventListener('touchend', this.handleMouseup)
|
||||
this.$el.addEventListener('touchmove', this.handleMousemove)
|
||||
} else {
|
||||
this.$el.addEventListener('mousedown', this.handleMousedown)
|
||||
this.$el.addEventListener('mouseup', this.handleMouseup)
|
||||
this.$el.addEventListener('mousemove', this.handleMousemove)
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (!this.$isServer) {
|
||||
this.detachMutationObserver()
|
||||
if ('ontouchstart' in window) {
|
||||
this.$el.removeEventListener('touchmove', this.handleMousemove)
|
||||
} else {
|
||||
this.$el.removeEventListener('mousemove', this.handleMousemove)
|
||||
}
|
||||
window.removeEventListener('resize', this.setSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.carousel-3d-container {
|
||||
min-height: 1px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
margin: 0px auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.carousel-3d-slider {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
transform-style: preserve-3d;
|
||||
-webkit-perspective: 1000px;
|
||||
-moz-perspective: 1000px;
|
||||
perspective: 1000px;
|
||||
}
|
||||
</style>
|
||||
78
components/download/downloadFooter.vue
Normal file
78
components/download/downloadFooter.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<view
|
||||
class="button-group"
|
||||
>
|
||||
<button
|
||||
v-if="!pickMode"
|
||||
class="download-btn btn"
|
||||
type="default"
|
||||
@click="openPickMode"
|
||||
>
|
||||
<text class="text">下载</text>
|
||||
</button>
|
||||
<template
|
||||
v-else>
|
||||
<button
|
||||
class="confirm-btn btn"
|
||||
type="default"
|
||||
@click="confirm"
|
||||
>
|
||||
<text class="text">确认下载</text>
|
||||
</button>
|
||||
<button
|
||||
class="cancel-btn btn"
|
||||
type="default"
|
||||
@click="cancel"
|
||||
>
|
||||
<text class="text">取消</text>
|
||||
</button>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
pickMode: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPickMode () {
|
||||
this.$emit('changePickMode', true);
|
||||
},
|
||||
confirm () {
|
||||
this.$emit('downloadConfirm');
|
||||
},
|
||||
cancel () {
|
||||
this.$emit('changePickMode', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.button-group
|
||||
padding: 30rpx 0
|
||||
flex-direction: row
|
||||
justify-content: center
|
||||
.btn
|
||||
width: 610rpx
|
||||
height: 80rpx
|
||||
border-radius: 49rpx
|
||||
border: 2rpx solid #FF920A;
|
||||
.text
|
||||
font-size: 30rpx;
|
||||
color: #FF920A;
|
||||
&.download-btn
|
||||
width: 610rpx
|
||||
&.confirm-btn
|
||||
width: 290rpx
|
||||
&.cancel-btn
|
||||
margin-left: 30rpx
|
||||
width: 290rpx
|
||||
background: #F3F4F5
|
||||
border: 2rpx solid transparent;
|
||||
.text
|
||||
color: #333
|
||||
</style>
|
||||
454
components/linzq-citySelect/linzq-citySelect.vue
Normal file
454
components/linzq-citySelect/linzq-citySelect.vue
Normal file
@@ -0,0 +1,454 @@
|
||||
<template>
|
||||
<div class="wrapper" :style="'top:'+statusBarHeight+'px'">
|
||||
<div class="header">
|
||||
<view class="back_div">
|
||||
<image class="back_img" @click="back_city()" src="../../static/back_img.png" mode=""></image>
|
||||
</view>
|
||||
<input class="input" @input="onInput" placeholder="中文/拼音/首字母" v-model="searchValue" />
|
||||
</div>
|
||||
<scroll-view class="calendar-list" scroll-y="true" :scroll-into-view="scrollIntoId">
|
||||
<view v-if="disdingwei" id="hot">
|
||||
<!-- 定位模块 -->
|
||||
<view class="dingwei">
|
||||
<view class="dingwei_Tips">
|
||||
当前定位
|
||||
</view>
|
||||
<view class="dingwei_city">
|
||||
<view class="dingwei_city_one">
|
||||
{{position}}
|
||||
</view>
|
||||
<view class="dingweis_div" @click="getWarpweft">
|
||||
<image class="dingweis" src="../../static/dingweis.png" mode=""></image>
|
||||
<text>{{po_tips}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近模块 -->
|
||||
<view class="dingwei" v-if="Visit.length>=0">
|
||||
<view class="dingwei_Tips">
|
||||
最近访问
|
||||
</view>
|
||||
<view class="dingwei_city dingwei_city_zuijin">
|
||||
<view class="dingwei_city_one toright" v-for="(item,index) in Visit" v-if="index<2" @click="back_city(item)">
|
||||
{{item.cityName}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 城市列表 -->
|
||||
<view v-if="searchValue == ''" v-for="(item, index) in list" :id="getId(index)" :key="index">
|
||||
<view class="letter-header">{{ getId(index) }}</view>
|
||||
<view class="city-div" v-for="(city, i) in item" :key="i" @click="back_city(city)">
|
||||
<text class="city">{{ city.cityName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 搜索结果 -->
|
||||
<view class="city-div" v-for="(item, index) in searchList" @click="back_city(item)">
|
||||
<text class="city">{{ item.cityName }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧字母 -->
|
||||
<view class="letters" v-if="searchValue == ''">
|
||||
<view class="letters-item" @click="scrollTo('hot')">最近</view>
|
||||
<view class="letters-item" v-for="item in letter" :key="item" @click="scrollTo(item)">{{ item }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 选中之后字母 -->
|
||||
<view class="mask" v-if="showMask">
|
||||
<view class="mask-r">{{selectLetter}}</view>
|
||||
</view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Citys from '../city.js';
|
||||
export default {
|
||||
components: {},
|
||||
props: {},
|
||||
|
||||
computed: {
|
||||
hotCity() {
|
||||
return Citys.hotCity;
|
||||
},
|
||||
|
||||
citys() {
|
||||
return Citys.cities;
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: this.statusBarHeight,
|
||||
ImgUrl: this.ImgUrl,
|
||||
letter: [],
|
||||
selectLetter: '',
|
||||
searchValue: '',
|
||||
scrollIntoId: '',
|
||||
list: [],
|
||||
tId: null,
|
||||
searchList: [],
|
||||
showMask: false,
|
||||
disdingwei: true,
|
||||
Visit: [], //最近访问
|
||||
position: '青岛',
|
||||
longitude: '', //经度
|
||||
latitude: '', //纬度
|
||||
seconds: 3,
|
||||
po_tips: '重新定位',
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
//获取存储的最近访问
|
||||
var that = this
|
||||
uni.getStorage({
|
||||
key: 'Visit_key',
|
||||
success: function(res) {
|
||||
that.Visit = res.data
|
||||
}
|
||||
});
|
||||
//获取定位 经度纬度
|
||||
that.getWarpweft()
|
||||
//获取city.js 的程序字母
|
||||
var mu = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'w', 'x', 'y',
|
||||
'z'
|
||||
];
|
||||
var tmp = [];
|
||||
for (var i = 0; i < mu.length; i++) {
|
||||
var item = mu[i];
|
||||
for (var j = 0; j < this.citys.length; j++) {
|
||||
var py = this.citys[j].py;
|
||||
if (py.substring(0, 1) == item) {
|
||||
if (tmp.indexOf(item) == -1) {
|
||||
this.list[i] = [this.citys[j]];
|
||||
tmp.push(item);
|
||||
this.letter.push(item.toUpperCase());
|
||||
} else {
|
||||
this.list[i].push(this.citys[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getId(index) {
|
||||
return this.letter[index];
|
||||
},
|
||||
|
||||
scrollTo(letter) {
|
||||
this.showMask = true
|
||||
this.selectLetter = letter == 'hot' ? '最' : letter
|
||||
setTimeout(() => {
|
||||
this.showMask = false
|
||||
}, 300);
|
||||
this.scrollIntoId = letter;
|
||||
},
|
||||
|
||||
|
||||
query(source, text) {
|
||||
let res = [];
|
||||
var self = this;
|
||||
res = source.filter(item => {
|
||||
const arr = [];
|
||||
let isHave = false;
|
||||
Object.keys(item).forEach(prop => {
|
||||
const itemStr = item[prop];
|
||||
self.isString(itemStr) &&
|
||||
itemStr.split(',').forEach(val => {
|
||||
arr.push(val);
|
||||
});
|
||||
});
|
||||
arr.some(val => {
|
||||
isHave = new RegExp('^' + text).test(val);
|
||||
return isHave;
|
||||
});
|
||||
return isHave;
|
||||
});
|
||||
console.log(JSON.stringify(res));
|
||||
return res;
|
||||
},
|
||||
|
||||
isString(obj) {
|
||||
return typeof obj === 'string';
|
||||
},
|
||||
|
||||
onInput(e) {
|
||||
const value = e.target.value;
|
||||
console.log(value);
|
||||
if (value !== '' && this.citys && this.citys.length > 0) {
|
||||
const queryData = this.query(this.citys, String(value).trim());
|
||||
this.searchList = queryData;
|
||||
this.disdingwei = false
|
||||
} else {
|
||||
this.searchList = [];
|
||||
this.disdingwei = true
|
||||
}
|
||||
},
|
||||
|
||||
back_city(item) {
|
||||
if (item) {
|
||||
this.$emit('back_city', item);
|
||||
//unshift 把数据插入到首位,与push相反
|
||||
this.Visit.unshift(item)
|
||||
this.searchValue = "";
|
||||
this.disdingwei = true
|
||||
var arr = this.Visit
|
||||
//数组去重
|
||||
function distinct(arr) {
|
||||
let newArr = []
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (newArr.indexOf(arr[i]) < 0) {
|
||||
newArr.push(arr[i])
|
||||
}
|
||||
}
|
||||
return newArr
|
||||
}
|
||||
this.Visit = distinct(arr)
|
||||
console.log(this.Visit, "---最近访问")
|
||||
uni.setStorage({
|
||||
key: 'Visit_key',
|
||||
data: this.Visit
|
||||
});
|
||||
} else {
|
||||
this.$emit('back_city', 'no');
|
||||
}
|
||||
|
||||
},
|
||||
getWarpweft() {
|
||||
var that = this
|
||||
that.po_tips = '定位中...'
|
||||
let countdown = setInterval(() => {
|
||||
that.seconds--;
|
||||
uni.getLocation({
|
||||
type: 'wgs84',
|
||||
success: function(res) {
|
||||
console.log('当前位置的经度:' + res.longitude);
|
||||
console.log('当前位置的纬度:' + res.latitude);
|
||||
that.longitude = res.longitude
|
||||
that.latitude = res.latitude
|
||||
}
|
||||
});
|
||||
if (that.seconds <= 0) {
|
||||
that.seconds = 3
|
||||
that.po_tips = '重新定位'
|
||||
clearInterval(countdown);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrapper {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
background: #ffffff;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
bottom: 0upx;
|
||||
top: 83upx;
|
||||
left: 0upx;
|
||||
right: 0upx;
|
||||
width: 750upx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.mask-r {
|
||||
height: 120upx;
|
||||
width: 120upx;
|
||||
border-radius: 60upx;
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 40upx;
|
||||
color: #FFFFFF
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 85upx;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.back_div {
|
||||
width: 65upx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back_img {
|
||||
width: 35upx;
|
||||
height: 35upx;
|
||||
}
|
||||
|
||||
.input {
|
||||
font-size: 28upx;
|
||||
width: 620upx;
|
||||
height: 55upx;
|
||||
border-radius: 40upx;
|
||||
background-color: #F5F5F5;
|
||||
padding-left: 20upx;
|
||||
padding-right: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 30upx;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.show {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.hide {
|
||||
left: 100%;
|
||||
width: 100%;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
font-size: 30upx;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.calendar-list {
|
||||
position: absolute;
|
||||
top: 83upx;
|
||||
bottom: 0upx;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.letters {
|
||||
position: absolute;
|
||||
right: 30upx;
|
||||
bottom: 0px;
|
||||
width: 50upx;
|
||||
top: 260upx;
|
||||
color: #2f9bfe;
|
||||
text-align: center;
|
||||
font-size: 24upx;
|
||||
}
|
||||
|
||||
.letters-item {
|
||||
margin-bottom: 5upx;
|
||||
}
|
||||
|
||||
.letter-header {
|
||||
height: 45upx;
|
||||
font-size: 22upx;
|
||||
color: #333333;
|
||||
padding-left: 24upx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ebedef;
|
||||
|
||||
}
|
||||
|
||||
.city-div {
|
||||
width: 660upx;
|
||||
height: 85upx;
|
||||
margin-left: 24upx;
|
||||
border-bottom-width: 0.5upx;
|
||||
border-bottom-color: #ebedef;
|
||||
border-bottom-style: solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 35upx;
|
||||
}
|
||||
|
||||
.city {
|
||||
font-size: 28upx;
|
||||
color: #000000;
|
||||
padding-left: 30upx;
|
||||
}
|
||||
|
||||
.dingwei {
|
||||
width: 100%;
|
||||
padding-top: 25upx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 26upx;
|
||||
}
|
||||
|
||||
.dingwei_Tips {
|
||||
margin-left: 24upx;
|
||||
margin-bottom: 24upx;
|
||||
font-size: 24upx;
|
||||
color: #A5A5A5;
|
||||
}
|
||||
|
||||
.dingwei_city {
|
||||
width: 100%;
|
||||
height: 60upx;
|
||||
padding-left: 55upx;
|
||||
padding-right: 70upx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dingwei_city_one {
|
||||
width: 185upx;
|
||||
height: 60upx;
|
||||
background-color: #F5F5F5;
|
||||
border-radius: 10upx;
|
||||
font-size: 32upx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dingweis_div {
|
||||
display: flex;
|
||||
align-content: flex-end;
|
||||
align-items: center;
|
||||
font-size: 24upx;
|
||||
color: #FD5745;
|
||||
}
|
||||
|
||||
.dingweis {
|
||||
width: 32upx;
|
||||
height: 32upx;
|
||||
}
|
||||
|
||||
.dingwei_city_zuijin {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.toright {
|
||||
margin-right: 25upx;
|
||||
}
|
||||
</style>
|
||||
77
components/list/SelectionsList.nvue
Normal file
77
components/list/SelectionsList.nvue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<view class="video-list">
|
||||
<list
|
||||
class="list"
|
||||
show-scrollbar="false">
|
||||
<cell
|
||||
class="cell"
|
||||
:key="item.videoId"
|
||||
v-for="item in lists">
|
||||
<view
|
||||
class="video-item"
|
||||
@click="handleClick(item)">
|
||||
<image class="video-cover" :src="item.videoCover" mode="aspectFill"></image>
|
||||
<view class="right-view">
|
||||
<text
|
||||
class="video-title"
|
||||
:class="{active : item.definition ? item.videoId === currentVideo && item.quality == currentDefinition : item.videoId === currentVideo}"
|
||||
>{{ item.videoTitle }}</text>
|
||||
<!-- <text class="video-time">{{ item.videoTime }}</text> -->
|
||||
</view>
|
||||
</view>
|
||||
</cell>
|
||||
</list>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"VideoList",
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
lists: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
currentVideo: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
currentDefinition: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(item) {
|
||||
this.$emit('listClick', item);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.video-list
|
||||
flex 1
|
||||
.list
|
||||
flex 1
|
||||
padding 0 20rpx
|
||||
.video-item
|
||||
flex-direction row
|
||||
margin 15rpx 0
|
||||
.right-view
|
||||
margin-left 20rpx
|
||||
flex 1
|
||||
.video-cover
|
||||
width 160rpx
|
||||
height 90rpx
|
||||
border-radius 2rpx
|
||||
.video-title
|
||||
font-size 26rpx
|
||||
line-height 39rpx
|
||||
color #FFF
|
||||
&.active
|
||||
color #FF920A
|
||||
</style>
|
||||
118
components/list/VideoList.nvue
Normal file
118
components/list/VideoList.nvue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<view class="video-list">
|
||||
<slot name="title"></slot>
|
||||
<list
|
||||
class="list"
|
||||
show-scrollbar="false">
|
||||
<cell
|
||||
class="cell"
|
||||
:key="item.videoId"
|
||||
v-for="item in lists">
|
||||
<view
|
||||
class="video-item"
|
||||
@click="handleClick(item)">
|
||||
<template
|
||||
v-if="pickMode"
|
||||
>
|
||||
<image
|
||||
v-if="!item.selected"
|
||||
class="radio-icon"
|
||||
src="@/static/download/unselected@2x.png"></image>
|
||||
<image
|
||||
v-else
|
||||
class="radio-icon"
|
||||
src="@/static/download/selected@2x.png"></image>
|
||||
</template>
|
||||
<image class="video-cover" :src="item.videoCover" mode="aspectFill"></image>
|
||||
<view class="right-view">
|
||||
<text
|
||||
class="video-title"
|
||||
:class="{active : item.videoId === currentVideo}"
|
||||
>{{ item.videoTitle }}</text>
|
||||
<text class="video-time">{{ item.videoTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</cell>
|
||||
</list>
|
||||
<slot name="footer"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapMutations } from 'vuex'
|
||||
export default {
|
||||
name:"VideoList",
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
lists: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
currentVideo: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isPushing: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
pickMode: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['makeDownloadList']),
|
||||
handleClick(item) {
|
||||
if (this.pickMode) {
|
||||
this.$set(item, 'selected', !item.selected);
|
||||
return;
|
||||
}
|
||||
if (this.isPushing) {
|
||||
uni.showToast({
|
||||
title: '投屏中,暂不支持切换',
|
||||
icon: 'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
this.$emit('listClick', item);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.video-list
|
||||
flex 1
|
||||
padding 0 30rpx
|
||||
background-color: #FFF;
|
||||
.list
|
||||
flex 1
|
||||
.video-item
|
||||
flex-direction row
|
||||
align-items: center
|
||||
margin 15rpx 0
|
||||
.right-view
|
||||
margin-left 20rpx
|
||||
flex 1
|
||||
.radio-icon
|
||||
margin-right: 20rpx
|
||||
width: 30rpx
|
||||
height: 30rpx
|
||||
.video-cover
|
||||
width 320rpx
|
||||
height 180rpx
|
||||
border-radius 10rpx
|
||||
.video-title
|
||||
font-size 28rpx
|
||||
line-height 42rpx
|
||||
color #333
|
||||
&.active
|
||||
color #FF920A
|
||||
.video-time
|
||||
margin-top 30rpx
|
||||
font-size 26rpx
|
||||
color #666
|
||||
</style>
|
||||
63
components/list/WaterFall.nvue
Normal file
63
components/list/WaterFall.nvue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="video-list">
|
||||
<waterfall
|
||||
class="waterfall"
|
||||
column-count="2"
|
||||
column-gap="10"
|
||||
left-gap="10"
|
||||
right-gap="10"
|
||||
>
|
||||
<cell
|
||||
class="cell"
|
||||
:key="item.videoId"
|
||||
v-for="item in lists">
|
||||
<navigator
|
||||
class="video-item"
|
||||
hover-class="navigator-hover"
|
||||
:url="`/pages/detail/detail?videoId=${item.videoId}&userId=${item.userId}`">
|
||||
<image class="video-cover" :src="item.videoCover"></image>
|
||||
<text class="video-title">{{ item.videoTitle }}</text>
|
||||
</navigator>
|
||||
</cell>
|
||||
</waterfall>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"VideoList",
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
props: {
|
||||
lists: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
column: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.video-list
|
||||
flex 1
|
||||
.waterfall
|
||||
flex 1
|
||||
.video-cover
|
||||
width 350rpx
|
||||
height 196rpx
|
||||
border-radius 10rpx
|
||||
.video-title
|
||||
margin 20rpx 0
|
||||
font-size 28rpx
|
||||
color #333333
|
||||
text-overflow ellipsis
|
||||
lines 1
|
||||
|
||||
</style>
|
||||
@@ -271,7 +271,7 @@
|
||||
.fengmianBox .defaultBg{ width: 100%; margin: 0 auto;border-radius: 200rpx; margin: 0 auto;
|
||||
margin-bottom: 20rpx; background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('@/static/icon/home_icon_0.png');
|
||||
background-image: url('@/static/icon/home_icon_logo.png');
|
||||
}
|
||||
|
||||
page {
|
||||
|
||||
50
components/player/Brightness.nvue
Normal file
50
components/player/Brightness.nvue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<view class="volume">
|
||||
<text class="text">亮度</text>
|
||||
<view class="process-bar">
|
||||
<view
|
||||
class="active-process"
|
||||
:style="{flex: precent}"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Brightness',
|
||||
props: {
|
||||
currentValue: {
|
||||
type: Number | String
|
||||
},
|
||||
totalValue: {
|
||||
type: Number | String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
precent () {
|
||||
return this.currentValue / this.totalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.volume
|
||||
flex-direction row
|
||||
align-items center
|
||||
justify-content center
|
||||
padding 10rpx
|
||||
background rgba(20, 20, 20, 0.8)
|
||||
border-radius 2rpx
|
||||
.text
|
||||
color #FFFFFF
|
||||
.process-bar
|
||||
margin-left 20rpx
|
||||
width 260rpx
|
||||
height: 8rpx
|
||||
flex-direction row
|
||||
background-color rgba(153, 153, 153, 0.4)
|
||||
.active-process
|
||||
background-color #FF920A
|
||||
</style>
|
||||
307
components/player/CustomSlider.nvue
Normal file
307
components/player/CustomSlider.nvue
Normal file
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<view class="custom-slider">
|
||||
<image
|
||||
v-if="minIcon"
|
||||
class="icon"
|
||||
:src="minIcon"></image>
|
||||
<view
|
||||
ref="slider"
|
||||
class="slider"
|
||||
@touchstart.stop="onTouchStart"
|
||||
@touchmove.stop="onTouchMove"
|
||||
@touchend.stop="onTouchEnd">
|
||||
<view class="bar">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view
|
||||
class="active-bar-wrapper">
|
||||
<view
|
||||
class="active-bar"
|
||||
:style="{flex: presentShow}"></view>
|
||||
</view>
|
||||
<view v-if="step !== 1" class="steps-wrapper">
|
||||
<template v-for="(step, index) in steps">
|
||||
<view v-if="index" class="step"></view>
|
||||
<view class="holder-bar"></view>
|
||||
</template>
|
||||
</view>
|
||||
<view
|
||||
class="dot-wrapper">
|
||||
<view
|
||||
class="holder-bar"
|
||||
:style="{flex: presentShow}"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
<view class="know-area">
|
||||
<slot name="know"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<image
|
||||
v-if="maxIcon"
|
||||
class="icon"
|
||||
:src="maxIcon"></image>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
// 注意平台差异
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
import { range, addNumber } from '@/utils/formate.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dragStatus: '',
|
||||
size: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
currentValue: 0
|
||||
}
|
||||
},
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
vertical: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
barHeight: [Number, String],
|
||||
buttonSize: [Number, String],
|
||||
activeColor: String,
|
||||
inactiveColor: String,
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
minIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
maxIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
isDOMShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scope() {
|
||||
return this.max - this.min;
|
||||
},
|
||||
presentShow () {
|
||||
return (this.currentValue - this.min) / this.scope;
|
||||
},
|
||||
steps () {
|
||||
return this.scope / this.step
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isDOMShow (value) {
|
||||
this.getSliderSize();
|
||||
},
|
||||
value (val) {
|
||||
this.currentValue = val;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 同步初始值
|
||||
this.currentValue = this.value;
|
||||
},
|
||||
mounted () {
|
||||
this.getSliderSize();
|
||||
},
|
||||
methods: {
|
||||
touchStart(event) {
|
||||
this.resetTouchStatus();
|
||||
this.startX = event.touches[0].screenX;
|
||||
this.startY = event.touches[0].screenY;
|
||||
},
|
||||
touchMove(event) {
|
||||
const touch = event.touches[0];
|
||||
// safari back will set clientX to negative number
|
||||
this.deltaX = touch.screenX < 0 ? 0 : touch.screenX - this.startX;
|
||||
this.deltaY = touch.screenY - this.startY;
|
||||
this.offsetX = Math.abs(this.deltaX);
|
||||
this.offsetY = Math.abs(this.deltaY);
|
||||
// lock direction when distance is greater than a certain value
|
||||
const LOCK_DIRECTION_DISTANCE = 10;
|
||||
if (
|
||||
!this.direction ||
|
||||
(this.offsetX < LOCK_DIRECTION_DISTANCE &&
|
||||
this.offsetY < LOCK_DIRECTION_DISTANCE)
|
||||
) {
|
||||
this.direction = this.getDirection(this.offsetX, this.offsetY);
|
||||
}
|
||||
},
|
||||
resetTouchStatus() {
|
||||
this.direction = '';
|
||||
this.deltaX = 0;
|
||||
this.deltaY = 0;
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
},
|
||||
getDirection (x, y) {
|
||||
if (x > y) {
|
||||
return 'horizontal';
|
||||
}
|
||||
if (y > x) {
|
||||
return 'vertical';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onTouchStart(event) {
|
||||
console.log('start')
|
||||
this.touchStart(event);
|
||||
this.startValue = this.format(this.currentValue);
|
||||
this.dragStatus = 'start';
|
||||
this.$emit('dragStart');
|
||||
},
|
||||
onTouchMove(event) {
|
||||
this.touchMove(event);
|
||||
const rect = this.size;
|
||||
const delta = this.vertical ? this.deltaY : this.deltaX;
|
||||
const total = this.vertical ? rect.height : rect.width;
|
||||
const diff = (delta / total) * this.scope;
|
||||
// console.log('move', delta);
|
||||
if (delta === 0) return;
|
||||
this.dragStatus = 'draging';
|
||||
this.currentValue = this.startValue + diff;
|
||||
this.updateValue(this.currentValue, true);
|
||||
},
|
||||
onTouchEnd(event) {
|
||||
console.log('end')
|
||||
if(this.dragStatus === 'draging') {
|
||||
this.updateValue(this.currentValue, true);
|
||||
return;
|
||||
}
|
||||
if (this.dragStatus === 'start') {
|
||||
this.onClick(event);
|
||||
};
|
||||
},
|
||||
onClick(event) {
|
||||
if (this.disabled) return;
|
||||
const rect = this.size;
|
||||
const delta = this.vertical
|
||||
? event.changedTouches[0].screenY - rect.top
|
||||
: event.changedTouches[0].screenX - rect.left;
|
||||
const total = this.vertical ? rect.height : rect.width;
|
||||
let value = +this.min + (delta / total) * this.scope;
|
||||
// this.startValue = this.value;
|
||||
this.updateValue(value, true);
|
||||
},
|
||||
updateValue(value, end) {
|
||||
value = this.format(value);
|
||||
this.currentValue = value;
|
||||
this.$emit('changing', value);
|
||||
if (end) {
|
||||
this.$emit('change', value);
|
||||
}
|
||||
},
|
||||
format(value) {
|
||||
const min = +this.min;
|
||||
const max = +this.max;
|
||||
const step = +this.step;
|
||||
value = range(value, min, max);
|
||||
const diff = parseInt((value - min) / step) * step;
|
||||
return addNumber(min, diff);
|
||||
},
|
||||
isSameValue(newValue, oldValue) {
|
||||
return JSON.stringify(newValue) === JSON.stringify(oldValue);
|
||||
},
|
||||
getSliderSize () {
|
||||
setTimeout(()=> {
|
||||
const result = dom.getComponentRect(this.$refs.slider, option => {
|
||||
this.size = option.size;
|
||||
})
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.custom-slider
|
||||
flex-direction row
|
||||
align-items center
|
||||
justify-content center
|
||||
.icon
|
||||
width 40rpx
|
||||
height 40rpx
|
||||
.slider
|
||||
position relative
|
||||
margin 0 15rpx
|
||||
height 78rpx
|
||||
flex-direction row
|
||||
align-items center
|
||||
flex 1
|
||||
.bar
|
||||
flex 1
|
||||
flex-direction row
|
||||
height 6rpx
|
||||
background-color rgba(153, 153, 153, 0.4)
|
||||
.active-bar-wrapper
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
flex-direction row
|
||||
align-items center
|
||||
.active-bar
|
||||
height 6rpx
|
||||
background-color #FF920A
|
||||
.steps-wrapper
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
flex-direction row
|
||||
align-items center
|
||||
.step
|
||||
width 6rpx
|
||||
height 6rpx
|
||||
background-color #FFFFFF
|
||||
.holder-bar
|
||||
flex 1
|
||||
height: 6rpx
|
||||
.dot-wrapper
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
flex-direction row
|
||||
align-items center
|
||||
.holder-bar
|
||||
height 6rpx
|
||||
.dot
|
||||
width 28rpx
|
||||
height 28rpx
|
||||
background-color #FFFFFF
|
||||
border-radius 100%
|
||||
.know-area
|
||||
position absolute
|
||||
top 0
|
||||
right 0
|
||||
bottom 0
|
||||
left 0
|
||||
</style>
|
||||
215
components/player/Gesture.nvue
Normal file
215
components/player/Gesture.nvue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<view
|
||||
@touchstart.stop="touchstart"
|
||||
@touchmove.stop="touchmove"
|
||||
@touchend.stop="touchend">
|
||||
<slot></slot>
|
||||
</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;
|
||||
let plusTime = parseInt(Math.abs(precent * duration));
|
||||
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:
|
||||
this.$emit('onGestureEvent', {
|
||||
type: this.downLeft ? 'LEFT_DOWN' : 'RIGHT_DOWN',
|
||||
position
|
||||
});
|
||||
break;
|
||||
// 上滑
|
||||
case -2:
|
||||
this.$emit('onGestureEvent', {
|
||||
type: this.downLeft ? 'LEFT_UP' : 'RIGHT_UP',
|
||||
position
|
||||
});
|
||||
break;
|
||||
// 左滑
|
||||
case 0:
|
||||
this.swipeDir = type;
|
||||
countTime(position);
|
||||
this.$emit('onGestureEvent', {
|
||||
type: 'SEEK_TIME_UPDATE',
|
||||
finalTime
|
||||
});
|
||||
break;
|
||||
// 右滑
|
||||
case 1:
|
||||
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;
|
||||
}
|
||||
this.$emit('onGestureEvent', {
|
||||
type: 'TOUCH_END'
|
||||
});
|
||||
break;
|
||||
// doubleTap
|
||||
case 4:
|
||||
break;
|
||||
// tap
|
||||
case 5:
|
||||
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 isXchange = () => {
|
||||
if (Math.abs(X) > Math.abs(Y)) {
|
||||
position = X;
|
||||
hasType = 'X';
|
||||
isJustTouch = false;
|
||||
}
|
||||
if (X >= 0) excuteCb(1);
|
||||
// 左滑
|
||||
else excuteCb(0);
|
||||
};
|
||||
|
||||
const isYchange = () => {
|
||||
if (Math.abs(Y) > Math.abs(X)) {
|
||||
position = Y;
|
||||
hasType = 'Y';
|
||||
isJustTouch = false;
|
||||
}
|
||||
if (Y >= 0) excuteCb(-1);
|
||||
// 上滑
|
||||
else excuteCb(-2);
|
||||
};
|
||||
|
||||
const countDirect = () => {
|
||||
if (hasType) {
|
||||
hasType === 'X' ? isXchange() : isYchange();
|
||||
return;
|
||||
}
|
||||
if (Math.abs(X) > 5) {
|
||||
isXchange();
|
||||
return;
|
||||
}
|
||||
if (Math.abs(Y) > 5) {
|
||||
isYchange();
|
||||
}
|
||||
};
|
||||
|
||||
this.handleTouch = (type, e) => {
|
||||
switch(type) {
|
||||
case 'start':
|
||||
startX = e.touches[0].screenX;
|
||||
startY = e.touches[0].screenY;
|
||||
T = Date.now();
|
||||
hasType = '';
|
||||
isJustTouch = true;
|
||||
this.downLeft = startX < this.halfWidth ? true : false;
|
||||
break;
|
||||
case 'move':
|
||||
moveEndX = e.touches[0].screenX;
|
||||
moveEndY = e.touches[0].screenY;
|
||||
X = moveEndX - startX;
|
||||
Y = moveEndY - startY;
|
||||
// console.log('Y', Y);
|
||||
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);
|
||||
},
|
||||
limit(num, min, max) {
|
||||
if (num < min) return min;
|
||||
if (num > max) return max;
|
||||
return num;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
44
components/player/Process.nvue
Normal file
44
components/player/Process.nvue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<view class="volume">
|
||||
<text class="text">{{ currentValue }}/{{ totalValue }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Process',
|
||||
props: {
|
||||
currentValue: {
|
||||
type: Number | String
|
||||
},
|
||||
totalValue: {
|
||||
type: Number | String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
precent () {
|
||||
return this.currentValue / this.totalValue;
|
||||
},
|
||||
restPrecent () {
|
||||
return 1 - this.precent;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.volume
|
||||
flex-direction row
|
||||
align-items center
|
||||
justify-content center
|
||||
padding 10rpx
|
||||
background rgba(20, 20, 20, 0.8)
|
||||
border-radius 2rpx
|
||||
.text
|
||||
color #FFFFFF
|
||||
</style>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
41
components/player/StatusBar.nvue
Normal file
41
components/player/StatusBar.nvue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view
|
||||
class="status-bar">
|
||||
<component
|
||||
:is="name"
|
||||
:current-value="currentValue"
|
||||
:total-value="totalValue"></component>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Volume from './Volume.nvue'
|
||||
import Brightness from'./Brightness.nvue'
|
||||
import Process from './Process.nvue'
|
||||
export default {
|
||||
components: {
|
||||
Volume,
|
||||
Brightness,
|
||||
Process
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
currentValue: {
|
||||
type: Number | String
|
||||
},
|
||||
totalValue: {
|
||||
type: Number | String
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.status-bar
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
</style>
|
||||
50
components/player/Volume.nvue
Normal file
50
components/player/Volume.nvue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<view class="volume">
|
||||
<text class="text">音量</text>
|
||||
<view class="process-bar">
|
||||
<view
|
||||
class="active-process"
|
||||
:style="{flex: precent}"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Volume',
|
||||
props: {
|
||||
currentValue: {
|
||||
type: Number | String
|
||||
},
|
||||
totalValue: {
|
||||
type: Number | String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
precent () {
|
||||
return this.currentValue / this.totalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.volume
|
||||
flex-direction row
|
||||
align-items center
|
||||
justify-content center
|
||||
padding 10rpx
|
||||
background rgba(20, 20, 20, 0.8)
|
||||
border-radius 2rpx
|
||||
.text
|
||||
color #FFFFFF
|
||||
.process-bar
|
||||
margin-left 20rpx
|
||||
width 260rpx
|
||||
height: 8rpx
|
||||
flex-direction row
|
||||
background-color rgba(153, 153, 153, 0.4)
|
||||
.active-process
|
||||
background-color #FF920A
|
||||
</style>
|
||||
73
components/player/player.vue
Normal file
73
components/player/player.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
|
||||
<div id="url-player-test"></div>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
// 在适合的生命周期,通过script和link标签引入播放器sdk、css
|
||||
this.loadWebPlayerSDK().then(() => {
|
||||
// 如果需要使用自定义组件,打开以下注释
|
||||
// this.loadComponent().then(() => {
|
||||
let player = new Aliplayer({
|
||||
id: "url-player-test",
|
||||
source: "//player.alicdn.com/video/aliyunmedia.mp4",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}, function (player) {
|
||||
});
|
||||
player.one('canplay', function () {
|
||||
console.log('canplay', player.tag);
|
||||
player.tag.play();
|
||||
});
|
||||
// }).catch((e) => { console.log("加载组件失败", e) })
|
||||
}).catch((e) => {
|
||||
console.log("加载播放器SDK失败", e);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
loadWebPlayerSDK() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const s_tag = document.createElement('script'); // 引入播放器js
|
||||
s_tag.type = 'text/javascript';
|
||||
s_tag.src = 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.20.3/aliplayer-min.js';
|
||||
s_tag.charset = 'utf-8';
|
||||
s_tag.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
document.body.appendChild(s_tag);
|
||||
const l_tag = document.createElement('link'); // 引入播放器css
|
||||
l_tag.rel = 'stylesheet';
|
||||
l_tag.href = 'https://g.alicdn.com/apsara-media-box/imp-web-player/2.20.3/skins/default/aliplayer-min.css';
|
||||
document.body.appendChild(l_tag);
|
||||
});
|
||||
},
|
||||
loadComponent() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const s_tag = document.createElement('script');
|
||||
s_tag.type = 'text/javascript';
|
||||
// 需要先下载组件 js 文件,放到项目 /static/ 目录下
|
||||
// 下载地址:https://github.com/aliyunvideo/AliyunPlayer_Web/blob/master/customComponents/dist/aliplayer-components/aliplayercomponents-1.0.9.min.js
|
||||
s_tag.src = './static/aliplayercomponents-1.0.9.min.js';
|
||||
s_tag.charset = 'utf-8';
|
||||
s_tag.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
document.body.appendChild(s_tag);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.container {
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
height: 800px;
|
||||
}
|
||||
</style>
|
||||
140
components/popup-layer/popup-layer.vue
Normal file
140
components/popup-layer/popup-layer.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-show="ifshow" @tap="ableClose" @touchmove.stop.prevent class="popup-layer" >
|
||||
|
||||
</view>
|
||||
<view ref="popRef" class="popup-content" @tap.stop="stopEvent" :style="_location">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'popup-layer',
|
||||
model: {
|
||||
prop: "showPop",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
showPop:{
|
||||
type:Boolean,
|
||||
default:false,
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'top', // 方向 top,bottom,left,right
|
||||
},
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ifshow: false, // 是否展示,
|
||||
translateValue: -100, // 位移距离
|
||||
site:-100,
|
||||
timer: null,
|
||||
iftoggle: false,
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
_translate() {
|
||||
const transformObj = {
|
||||
'top': `transform:translateY(${-this.translateValue}%)`,
|
||||
'bottom': `transform:translateY(${this.translateValue}%)`,
|
||||
'left': `transform:translateX(${-this.translateValue}%)`,
|
||||
'right': `transform:translateX(${this.translateValue}%)`
|
||||
};
|
||||
return transformObj[this.direction]
|
||||
},
|
||||
_location() {
|
||||
const positionValue = {
|
||||
'top': `bottom:${this.site}%;width:100%;`,
|
||||
'bottom': `top:${this.site}%;width:100%;`,
|
||||
'left': `right:0px;top:0;height:100%;`,
|
||||
'right': `left:0px;top:0;height:100%;`,
|
||||
};
|
||||
return positionValue[this.direction]+ this._translate;
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
if(this.showPop){
|
||||
// console.log(222);
|
||||
this.show();
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
showPop(value){
|
||||
console.log(value)
|
||||
if(value){
|
||||
this.show();
|
||||
}else{
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
stopMove(event){
|
||||
return;
|
||||
},
|
||||
show(events) {
|
||||
this.ifshow = true;
|
||||
this.site=0;
|
||||
let _open = setTimeout(() => {
|
||||
this.translateValue = 0;
|
||||
_open = null;
|
||||
}, 100)
|
||||
let _toggle = setTimeout(() => {
|
||||
this.iftoggle = true;
|
||||
_toggle = null;
|
||||
}, 300);
|
||||
},
|
||||
close() {
|
||||
if (this.timer !== null || !this.iftoggle) {
|
||||
return;
|
||||
}
|
||||
this.translateValue = -100;
|
||||
this.timer = setTimeout(() => {
|
||||
this.ifshow = false;
|
||||
this.timer = null;
|
||||
this.iftoggle = false;
|
||||
this.$emit('closeCallBack', null);
|
||||
this.$emit('change',false)
|
||||
}, 300);
|
||||
},
|
||||
ableClose() {
|
||||
if (this.autoClose) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
stopEvent(event) {},
|
||||
doSome(){
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.popup-layer {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
// background: rgba(0, 0, 0, .3);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
position: fixed;
|
||||
z-index: 1000000;
|
||||
background: #FFFFFF;
|
||||
transition: transform .2s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user