This commit is contained in:
2023-08-18 01:41:17 +08:00
31 changed files with 2540 additions and 118 deletions

View File

@@ -0,0 +1,358 @@
<template>
<view class="liu-list">
<scroll-view class="liu-scroll-left" scroll-y="true" :scroll-with-animation="true"
:scroll-into-view="scrollIntoView">
<view class="liu-search" id="TOP">
<image class="liu-search-img" src="../../static/image/mine/search.png"></image>
<input class="liu-input" @input="search" v-model="searchStr" placeholder="请输入搜索信息" maxlength="50"
placeholder-class="liu-placeholder" />
</view>
<view class="left-list" v-for="(item,index) of scrollLeftObj" :key="index" :id="index!='#'?index:'BOTTOM'">
<view class="left-item-title" v-if="item && item.length">{{index}}</view>
<view class="left-item-card" v-for="(mess,inx) in item" @click.stop="chooseItem(mess)">
<view class="left-item-card-info"
:style="inx<item.length-1?'border-bottom: solid #F4F4F4 1rpx;':''">
<view class="left-item-card-name" :class="{ actived: mess[idKey] == current }">
{{mess[nameKey] || ''}}
</view>
<u-icon class="mr30" v-if="mess[idKey] == current" color="#05C341" size="16px" name="checkmark"></u-icon>
</view>
</view>
</view>
<view class="no-data" v-if="!hasData">
<image class="no-data-img" src="../../static/image/mine/noData.png"></image>
<view class="no-data-name">暂无数据</view>
</view>
</scroll-view>
<view class="liu-scroll-right" v-if="hasData">
<image class="liu-scroll-right-top" src="../../static/image/mine/top.png" @click.stop="scrollIntoView = 'TOP'"></image>
<view :class="{'liu-scroll-right-name':true,'liu-scroll-right-select':item==scrollIntoView}"
v-for="(item,index) in scrollRightList" :key="index" @click.stop="chooseType(item)">{{item}}
</view>
</view>
</view>
</template>
<script>
import {
pinyinUtil
} from './pinyinUtil.js';
export default {
props: {
//数据源
dataList: {
type: Array,
required: true,
default: () => {
return []
}
},
//显示的主键key值
idKey: {
type: String,
default: 'id'
},
//显示的名字key值
nameKey: {
type: String,
default: 'name'
},
//显示的电话key值
phoneKey: {
type: String,
default: 'phone'
},
//显示的头像key值
imgKey: {
type: String,
default: 'img'
},
//头像圆角(rpx、px、%)
radius: {
type: String,
default: '6rpx'
},
current: {
type: [String,Number],
default: ''
}
},
data() {
return {
searchStr: '',
scrollIntoView: '',
scrollLeftObj: {},
oldObj: {},
scrollRightList: [],
hasData: true
};
},
watch: {
dataList: {
immediate: true,
deep: true,
handler(newList) {
if (newList && newList.length) this.cleanData(newList)
},
},
},
methods: {
search() {
if (this.searchStr) {
let has = false
this.scrollLeftObj = JSON.parse(JSON.stringify(this.oldObj))
for (let i in this.scrollLeftObj) {
this.scrollLeftObj[i] = this.scrollLeftObj[i].filter(item => {
return (item[this.nameKey].indexOf(this.searchStr) != -1) || item[this.phoneKey]
.indexOf(this.searchStr) != -1
})
if (this.scrollLeftObj[i].length) has = true
}
if (has) this.hasData = true
else this.hasData = false
} else {
this.hasData = true
this.scrollLeftObj = JSON.parse(JSON.stringify(this.oldObj))
}
},
cleanData(list) {
this.scrollRightList = this.getLetter()
let newList = []
list.forEach(res => {
let initial = pinyinUtil.getFirstLetter(res[this.nameKey].trim())
let firsfirs = initial ? initial.substring(0, 1) : ''
if (!newList[firsfirs]) newList[firsfirs] = []
newList[firsfirs].push({
[this.idKey]: res[this.idKey] || '',
[this.nameKey]: res[this.nameKey].trim() || '',
[this.phoneKey]: res[this.phoneKey] || '',
[this.imgKey]: res[this.imgKey] || ''
})
})
this.scrollRightList.forEach(t => {
if (newList[t]) {
this.$set(this.scrollLeftObj, t, newList[t])
} else {
this.$set(this.scrollLeftObj, t, [])
}
})
let surplusList = []
for (var i in newList) {
let han = this.scrollRightList.find(v => {
return v == i
})
if (!han) surplusList.push(newList[i])
}
surplusList.forEach(item => {
this.scrollLeftObj['#'] = this.scrollLeftObj['#'].concat(item)
})
this.oldObj = JSON.parse(JSON.stringify(this.scrollLeftObj))
},
getLetter() {
let list = []
for (var i = 0; i < 26; i++) {
list.push(String.fromCharCode(65 + i))
}
list.push('#')
return list
},
chooseType(item) {
if (item == '#') item = 'BOTTOM'
this.scrollIntoView = item
},
preview(img) {
uni.previewImage({
current: 0,
urls: [img]
})
},
chooseItem(item) {
this.$emit('update:current', item[this.idKey])
this.$emit('click', item)
}
},
emits: ['update:current']
};
</script>
<style>
/deep/ ::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
display: none;
}
</style>
<style lang="scss" scoped>
.liu-list {
width: 100%;
height: 100vh;
background-color: #F4F4F4;
box-sizing: border-box;
padding-top: 1px;
.liu-scroll-left {
height: 100%;
.liu-search {
width: 100%;
height: 106rpx;
background-color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.liu-search-img {
width: 32rpx;
height: 32rpx;
position: absolute;
left: 64rpx;
}
.liu-input {
width: calc(100% - 64rpx);
height: 72rpx;
background: #EEEEEE;
border-radius: 36rpx;
padding: 0 32rpx 0 80rpx;
box-sizing: border-box;
color: #333333;
}
.liu-placeholder {
color: #777777;
}
}
.left-list {
height: auto;
.left-item-title {
width: calc(100% - 24rpx);
height: 60rpx;
padding-left: 24rpx;
text-align: left;
line-height: 60rpx;
font-size: 30rpx;
color: #666666;
}
.left-item-card {
width: 100%;
height: 112rpx;
background-color: #FFFFFF;
box-sizing: border-box;
padding-left: 24rpx;
display: flex;
align-items: center;
justify-content: flex-start;
.left-item-card-img {
width: 80rpx;
min-width: 80rpx;
height: 80rpx;
background-color: #CFCFCF;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
font-weight: bold;
color: #FFFFFF;
}
.img-info {
background: none;
border: solid #f0f0f0 1rpx;
}
.left-item-card-info {
width: 100%;
margin-left: 32rpx;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.left-item-card-name {
font-size: 30rpx;
line-height: 30rpx;
color: #333333;
}
.left-item-card-phone {
margin-top: 14rpx;
font-size: 28rpx;
line-height: 28rpx;
color: #999999;
}
}
}
}
.no-data {
width: 100%;
display: flex;
align-items: center;
justify-items: center;
flex-direction: column;
margin-top: 25%;
.no-data-img {
width: 200rpx;
height: 200rpx;
}
.no-data-name {
margin-top: 20rpx;
font-size: 28rpx;
color: #666666;
}
}
}
.liu-scroll-right {
position: fixed;
right: 0rpx;
top: 50%;
transform: translateY(-47%);
z-index: 999 !important;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.liu-scroll-right-top {
width: 32rpx;
height: 32rpx;
margin-right: 14rpx;
z-index: 999 !important;
}
.liu-scroll-right-name {
width: 32rpx;
padding-right: 14rpx;
height: 28rpx;
font-size: 22rpx;
color: #333333;
line-height: 22rpx;
margin-top: 8rpx;
display: flex;
align-items: center;
justify-content: center;
}
.liu-scroll-right-select {
padding: 0;
margin-right: 14rpx;
width: 28rpx;
height: 28rpx;
border-radius: 50%;
background: #2991FF;
color: #FFFFFF;
}
}
}
.actived {
color: #05C341;
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
/**
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
* @param {number|string} value 用户传递值的px值
* @param {boolean} unit
* @returns {number|string}
*/
export const getPx = (value, unit = false)=> {
if (testNumber(value)) {
return unit ? `${value}px` : Number(value)
}
// 如果带有rpx先取出其数值部分再转为px值
if (/(rpx|upx)$/.test(value)) {
return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
}
return unit ? `${parseInt(value)}px` : parseInt(value)
}
/**
* @description 添加单位如果有rpxupx%px等单位结尾或者值为auto直接返回否则加上px单位结尾
* @param {string|number} value 需要添加单位的值
* @param {string} unit 添加的单位名 比如px
*/
export const addUnit =(value = 'auto', unit = 'px')=> {
value = String(value)
// 用uView内置验证规则中的number判断是否为数值
return testNumber(value) ? `${value}${unit}` : value
}
/**
* 验证十进制数字
*/
function testNumber(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* @description 获取系统信息同步接口
* @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
*/
export const sys=()=> {
return uni.getSystemInfoSync()
}

View File

@@ -0,0 +1,345 @@
<template>
<view class="sunny-video">
<video class="video" :style="{height:elVideoHeight.strNum}" id="sunnyVideo" ref="sunnyVideo" :title="title" :src="src" :show-center-play-btn="false" :controls="isPlay" @timeupdate="timeupdate" enable-play-gesture :show-mute-btn="showMuteBtn" @play="play" @pause="pause" @ended="ended" @error="playError" @fullscreenchange="fullscreenchange">
<!-- <cover-view class="video-view">
<cover-image class="img" src="static/left.png" @click="onBack"></cover-image>
</cover-view> -->
<cover-view v-if="!isPlay" class="banner-view" :style="{height:elVideoHeight.strNum}">
<cover-image v-if="poster" class="banner" :style="{height:elVideoHeight.strNum}" :src="poster" mode="" @click="onPlayChange"></cover-image>
<cover-image class="imgPal" :style="{width:playBtnHeight.strNum,height:playBtnHeight.strNum,top:`${elVideoHeight.intNum/2}px`,transform:`translate(-${playBtnHeight.intNum/2}px,-${playBtnHeight.intNum/2}px)`}" @click="onPlayChange" :src="playImg"></cover-image>
</cover-view>
<cover-view v-if="isPlay&&!isShowRateBox" class="speedText" :style="{top:`${isFullScreen?(screenInfo.width/2) - 14.5 +'px':(elVideoHeight.intNum/2)- 14.5+'px'}`}" @click="isShowRateBox = true">
<!-- #ifndef MP-WEIXIN || APP-NVUE -->
<cover-view class="text" @click="isShowRateBox = true">{{rateText}}X</cover-view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<text class="text">{{rateText}}X</text>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
{{rateText}}X
<!-- #endif -->
</cover-view>
<cover-view v-if="isFullScreen&&isShowRateBox" class="vertical vertical-full" :style="{width:`${screenInfo.height}px`,height:`${isShowRateBox? screenInfo.width : screenInfo.width-40}px`}">
<!-- #ifdef APP-NVUE -->
<view class="options" :style="{width:`${screenInfo.height - screenInfo.options}px`,height:`${screenInfo.width}px`}" @click="isShowRateBox=false"></view>
<view class="speed-box" :style="{height:`${screenInfo.width}px`}">
<text class="speeds" :class="{act:item.isTo}" v-for="(item,index) in rateList" :key="item.name" @click="changeRate(item,index)">{{item.name}}X</text>
</view>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<cover-view class="options" :style="{width:`${screenInfo.height - screenInfo.options}px`,height:`${screenInfo.width}px`}" @click="isShowRateBox=false"></cover-view>
<cover-view class="speed-box" :style="{height:`${screenInfo.width}px`}">
<cover-view class="speeds" :class="{act:item.isTo}" v-for="(item,index) in rateList" :key="item.name" @click="changeRate(item,index)">{{item.name}}X</cover-view>
</cover-view>
<!-- #endif -->
</cover-view>
<cover-view v-if="!isFullScreen&&isShowRateBox" class="vertical" :style="{height:`${isShowRateBox ? elVideoHeight.intNum:elVideoHeight.intNum - 44}px`}">
<!-- #ifdef APP-NVUE -->
<view class="options" :style="{height:elVideoHeight.strNum}" @click="isShowRateBox=false"></view>
<view class="speed-box" :style="{height:elVideoHeight.strNum}">
<text class="speeds" :class="{act:item.isTo}" v-for="(item,index) in rateList" :key="item.name" @click="changeRate(item,index)">{{item.name}}X</text>
</view>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<cover-view class="options" :style="{height:elVideoHeight.strNum}" @click="isShowRateBox=false"></cover-view>
<cover-view class="speed-box" :style="{height:elVideoHeight.strNum}">
<cover-view class="speeds" :class="{act:item.isTo}" v-for="(item,index) in rateList" :key="item.name" @click="changeRate(item,index)">{{item.name}}X</cover-view>
</cover-view>
<!-- #endif -->
</cover-view>
</video>
</view>
</template>
<script>
import {getPx,addUnit,sys} from './function.js'
import playImgs from '../../static/image/index/play.png'
export default {
props:{
// 视频地址
src:{
type:String,
default:''
},
// 视频标题
title:{
type:String,
default:''
},
// 视频封面
poster:{
type:String,
default:''
},
// 视频高度
videoHeight:{
type: [String, Number],
default: '230px'
},
// 播放图片按钮宽高
playImgHeight:{
type: [String, Number],
default: '70rpx'
},
// 暂停按钮
playImg:{
type:String,
default:playImgs
},
// 是否显示静音按钮
showMuteBtn:{
type:Boolean,
default:false
},
// 播放完毕是否退出全屏
isExitFullScreen:{
type:Boolean,
default:true
}
},
data() {
return {
videoCtx:'', //视频上下文
isPlay:false,// 视频是否播放过
isShowRateBox:false,// 是否显示倍速盒子
rateText:'1.0',// 当前倍速
rateList: [{
name:'0.5',
isTo:false
},{
name:'0.8',
isTo:false
},{
name:'1.0',
isTo:true
},{
name:'1.25',
isTo:false
},{
name:'1.5',
isTo:false
}
// #ifdef MP-WEIXIN
,{
name:'2.0',
isTo:false
}
// #endif
], //倍数
isFullScreen: false, //全屏状态
// 屏幕信息
screenInfo:{
width:0,
height:0,
options:getPx('160rpx'),// 遮罩要减去的高度
}
}
},
computed:{
// 视频单位高度信息
elVideoHeight() {
return {
intNum:getPx(this.videoHeight ? this.videoHeight : 230),
strNum:addUnit(this.videoHeight ? this.videoHeight : 230)
}
},
// 播放图片按钮宽高信息
playBtnHeight(){
return {
intNum:getPx(this.playImgHeight ? this.playImgHeight : '70rpx'),
strNum:addUnit(this.playImgHeight ? this.playImgHeight : '70rpx')
}
}
},
mounted() {
this.videoCtx = uni.createVideoContext('sunnyVideo', this)
this.getScreenInfo()
},
methods: {
// 获取元素信息
getDomInfo(elId){
return new Promise(resolve => {
const view = uni.createSelectorQuery().select(`#${elId}`)
view.boundingClientRect(data=>{
resolve(data.height)
}).exec()
})
},
//监听开始播放
play(e){
this.isPlay=true
this.$emit('play')
},
// 监听视频暂停
pause(){
this.$emit('pause')
},
// 视屏播放出错
playError(e){
this.$emit('playError',e)
},
// 监听视频结束
ended(){
this.$emit('videoEnded')
if(!this.isExitFullScreen)return
this.videoCtx.exitFullScreen(); //退出全屏
},
onBack(){
uni.navigateBack()
},
// 播放进度
timeupdate(e) {
this.$emit('timeupdate',e)
},
// 切换倍速
changeRate(item,index){
if(item.isTo)return this.isShowRateBox = false
this.rateList.forEach((v,i)=>{
i==index?v.isTo=true:v.isTo=false
})
this.videoCtx.playbackRate(item.name*1)
this.rateText = item.name
this.isShowRateBox = false
},
// 播放视频
onPlayChange(){
this.$nextTick(()=>{
this.videoCtx.play()
/* this.rateText ='1.0'
this.videoCtx.playbackRate(1) */
})
},
// 全屏操作
fullscreenchange(){
this.isFullScreen=!this.isFullScreen
this.$emit('fullscreenchange',this.isFullScreen)
},
// 获取屏幕信息
getScreenInfo(){
const res = sys()
this.screenInfo.width = res.screenWidth
this.screenInfo.height = res.screenHeight
},
}
}
</script>
<style lang="scss">
$sunny-primary: #00B74F;
.sunny-video{
position: relative;
z-index: 0;
.speedText{
position: absolute;
top: 240rpx;
right: 30rpx;
z-index: 5;
padding: 10rpx;
padding-right: 0;
font-size: 30rpx;
color: $sunny-primary;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
.text{
font-size: 30rpx;
color: $sunny-primary;
}
}
}
.video {
width: 750rpx;
height: 230px;
}
.video-view{
position: absolute;
z-index:999;
display: flex;
align-items: center;
justify-content: space-between;
width: 750rpx;
/* #ifndef APP-NVUE */
padding: var(--status-bar-height) 32rpx 0;
/* #endif */
.img{
width: 56rpx;
height: 56rpx;
}
}
.banner-view{
position: relative;
width: 750rpx;
height: 230px;
.banner{
width: 750rpx;
height: 230px;
}
.imgPal{
position: absolute;
z-index: 2;
top: 240rpx;
left: 375rpx;
transform: translate(-35rpx,-35rpx);
width: 70rpx;
height: 70rpx;
}
}
.vertical{
position: relative;
width: 750rpx;
height: 410rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
&.vertical-full{
position: relative;
.speed-box{
padding: 50rpx 0;
}
}
.options{
width: 590rpx;
}
.speed-text{
position: absolute;
top: 240rpx;
right: 30rpx;
font-size: 30rpx;
color: #fff;
}
.speed-box{
position: absolute;
right: 0;
top: 0;
width: 160rpx;
height: 230px;
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 10px 0;
background-color: rgba(0, 0, 0, 0.7);
.speeds{
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
width: 160rpx;
text-align: center;
font-size: 28rpx;
color: #fff;
padding: 10px 0px;
&.act{
color: $sunny-primary;
}
}
}
}
</style>