This commit is contained in:
2024-05-17 18:02:49 +08:00
parent 8407d51fb6
commit b5264dc222
4056 changed files with 308094 additions and 41932 deletions

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

File diff suppressed because it is too large Load Diff

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

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

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

View 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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

View File

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

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

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

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

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

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

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

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

View 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', // 方向 topbottomleftright
},
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>