YNUtdPlatform/components/missthee-indexlist/missthee-indexlist.vue

605 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class='index-list'>
<view class='index-list__head'>
<input v-model="citySearch" :placeholder='placeholder' class="index-list__input"
placeholder-class="index-list__input__placeholder" />
</view>
<div>
<div class='index-list__scroller'>
<div ref="bigDataTablePanel" class="index-list__big-data__table-panel">
<div ref="bigDataTableBody" class="index-list__big-data__table-body">
<div ref="bigDataTableDataPanel" id="bigDataTableDataPanel"
class="index-list__big-data__table-data-panel" @touchstart="bigDataTouchStartEvent"
@touchmove.stop.prevent="bigDataScrollEvent" @touchend="bigDataTouchEndEvent"
@mousewheel="bigDataScrollEvent">
<div ref="bigDataTableDataMarginSpace" :style="{'margin-top':newMarginTop+'px'}"></div>
<div v-for='item in bigDataFilteredFlatWithLineInfoCurrent' :key='item.id'>
<div v-if='item.__line_info__.isEmptySpace'
class="index-list__group-cell index-list__group-cell--empty-space"></div>
<div v-else-if='!item.__line_info__.isEmptySpace&&item.__line_info__.isHead'
class="index-list__group-head">{{item.username}}</div>
<div v-else-if='!item.__line_info__.isEmptySpace&&!item.__line_info__.isHead'
class="index-list__group-cell" @click="()=>{ itemClickHandler(item)}" :key="item.id"
style="display: flex;" @longpress="()=>{itemLongPress(item)}">
<view>
<image src="../../static/images/tx.png"
style="width:50rpx;height: 50rpx;margin-top: 10rpx;"></image>
</view>
<span
style="padding-left: 20rpx;font-size:34rpx ;letter-spacing: 1rpx;">{{item.username}}</span>
<span
style="padding-left: 20rpx;font-size:34rpx ;letter-spacing: 1rpx;">({{item.phone}})</span>
</div>
</div>
<div v-for='groupKey in Object.keys(bigDataFiltered)' style='' :key='groupKey'>
<div class="index-list__group-head"
:id='"list-archor-"+ encodeURIComponent(groupKey).replace("%","_")'>
{{groupKey}}
</div>
<div class="index-list__group-cell" v-for='city in dataFiltered[groupKey]'
@click="()=>{itemClickHandler(city)}" :clickable='true' :key="city.id">
{{city.username}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<view v-if='useIndex&&dataFiltered&&Object.keys(dataFiltered).length>0' class='index-list__index'
@touchmove="indexTouchMoveHandler" @touchstart="indexTouchMoveHandler" @touchend="isZoomActiveIndex=false">
<div class='index-list__index__wrapper'>
<div v-for='groupKey in Object.keys(dataFiltered)' :key='groupKey' class='index-list__index__letter'
:class='{"index-list__index__letter--active":currentActiveIndex===groupKey&&isZoomActiveIndex,
"index-list__index__letter--active2":currentActiveIndex===groupKey}'>
{{groupKey}}
</div>
</div>
</view>
</view>
</template>
<script>
import {
pinyin
} from "@/api/convertPinyin.js";
export default {
name: "missthee-indexlist",
props: {
data: {
type: Object,
default: function() {
return {}
}
},
placeholder: {
type: String,
default: function() {
return '输入关键字查询'
}
},
useIndex: {
type: Boolean,
default: function() {
return true
}
},
},
data() {
return {
dataFiltered: {},
currentActiveIndex: '',
isZoomActiveIndex: false,
citySearch: '',
citySeatchDebounce: null,
bigDataFilteredLineHeightTotal: 0,
bigDataFiltered: {},
bigDataFilteredFlatWithLineInfo: [],
bigDataFilteredFlatWithLineInfoCurrent: [],
bigDataTableDataPanelEl: null,
bigDataParam: {
scrollHeight: 0,
dataHeadHeight: 35, // 索引行高
dataRowHeight: 45, // 数据行高
},
bigDataComputedParam: {
dataRowTopMax: 0, //数据行滑到底,移动的最大距离
dataRowTop: 0, //数据行当前移动的距离
},
bigDataScrollActionParam: {
speed: 0,
speedMax: 50,
resistance: 0.5,
prePositionY: null,
preTimestamp: null,
isMouseDown: false,
timeoutObj: null
},
newMarginTop: 0,
islongPress: false, //长按记录变量
}
},
watch: {
data: {
immediate: true,
handler() {
this.buildDataFiltered()
}
},
citySearch() {
if (this.citySeatchDebounce) {
clearTimeout(this.citySeatchDebounce)
this.citySeatchDebounce = null
}
this.citySeatchDebounce = setTimeout(() => {
this.buildDataFiltered()
}, 200)
},
dataFiltered: {
deep: true,
handler() {
this.bigDataComputedParam.dataRowTop = 0
this.bigDataBuildData()
this.bigDataUpdateRows()
}
}
},
mounted() {
this.bigDataTableDataPanelEl = this.$refs.bigDataTableDataPanel
// #ifdef H5
this.bigDataParam.scrollHeight = this.bigDataTableDataPanelEl.offsetHeight
// #endif
// #ifdef APP-VUE || APP
let view = uni.createSelectorQuery().in(this).select("#bigDataTableDataPanel")
view.fields({
size: true
}, res => {
this.bigDataParam.scrollHeight = res.height - 100
this.afterHeight = true;
}).exec()
// #endif
this.bigDataAddEvent()
this.bigDataBuildData()
this.bigDataUpdateRows()
this.bigDataScrollAminate()
},
beforeDestroy() {
this.bigDataClearScrollAnimate()
this.bigDataRemoveEvent()
},
methods: {
indexTouchMoveHandler(e) {
this.isZoomActiveIndex = true
const touch = e.touches[0];
const target = e.currentTarget || e.target
let currentIndex = Math.floor((touch.clientY + Object.keys(this.dataFiltered).length * 15 / 2 -
target.offsetTop) / 15)
currentIndex = Math.min(Object.keys(this.dataFiltered).length - 1, currentIndex)
currentIndex = Math.max(0, currentIndex)
if (Object.keys(this.dataFiltered)[currentIndex] !== this.currentActiveIndex) {
this.currentActiveIndex = Object.keys(this.dataFiltered)[currentIndex]
let currentStartLine = this.bigDataFilteredFlatWithLineInfo.find(item => item.id === 'Head:' + this
.currentActiveIndex)
this.bigDataComputedParam.dataRowTop = currentStartLine.__line_info__.totalHeight -
currentStartLine
.__line_info__.currentHeight
this.bigDataUpdateRows()
}
if ("undefined" !== typeof event) {
event.preventDefault()
event.stopPropagation()
}
},
itemClickHandler(item) {
if (this.islongPress) {
return
}
let result = {
...item
}
for (let key of Object.keys(result)) {
if (key === '__line_info__') {
delete result[key]
}
}
this.$emit('select-item', result)
},
itemLongPress(item) {
this.islongPress = true
let result = {
...item
}
for (let key of Object.keys(result)) {
if (key === '__line_info__') {
delete result[key]
}
}
let _this = this
uni.setClipboardData({
data: result.phone,
success: function() {
uni.showToast({
title: '复制成功',
icon: 'success'
});
setTimeout(() => {
_this.islongPress = false
}, 2000)
}
});
},
buildDataFiltered() {
if (!this.data) {
return {}
}
const resultObj = {}
for (const groupkey of Object.keys(this.data)) {
const cityListResult = this.data[groupkey].filter(item =>
item.username.toLowerCase().indexOf(this
.citySearch.toLowerCase()) >= 0
)
if (cityListResult && cityListResult.length > 0) {
resultObj[groupkey] = cityListResult
}
}
this.dataFiltered = resultObj
},
bigDataAddEvent() {
// #ifdef APP-VUE || APP
// plus.globalEvent.addEventListener('mousewheel', this.bigDataScrollEvent);
// plus.globalEvent.addEventListener('touchmove', this.bigDataScrollEvent);
// plus.globalEvent.addEventListener('touchstart', this.bigDataTouchStartEvent);
// plus.globalEvent.addEventListener('touchend', this.bigDataTouchEndEvent);
// #endif
// #ifdef H5
this.bigDataTableDataPanelEl.addEventListener('mousewheel', this.bigDataScrollEvent);
this.bigDataTableDataPanelEl.addEventListener('touchmove', this.bigDataScrollEvent);
this.bigDataTableDataPanelEl.addEventListener('touchstart', this.bigDataTouchStartEvent);
this.bigDataTableDataPanelEl.addEventListener('touchend', this.bigDataTouchEndEvent);
// #endif
},
bigDataRemoveEvent() {
this.bigDataTableDataPanelEl.removeEventListener('mousewheel', this.bigDataScrollEvent);
this.bigDataTableDataPanelEl.removeEventListener('touchmove', this.bigDataScrollEvent);
this.bigDataTableDataPanelEl.removeEventListener('touchstart', this.bigDataTouchStartEvent);
this.bigDataTableDataPanelEl.removeEventListener('touchend', this.bigDataTouchEndEvent);
},
bigDataTouchStartEvent(e) {
this.bigDataScrollActionParam.isMouseDown = true
this.bigDataScrollActionParam.speed = 0
},
bigDataTouchEndEvent(e) {
this.bigDataScrollActionParam.isMouseDown = false
this.bigDataScrollActionParam.prePositionY = null
},
bigDataScrollEvent(e) {
let scrollEvent = e;
// if (scrollEvent instanceof WheelEvent) {
// #ifdef H5
this.bigDataComputedParam.dataRowTop += (scrollEvent.wheelDelta < 0 ? 1 : -1) * 30;
if (this.bigDataComputedParam.dataRowTop <= 0) {
this.bigDataComputedParam.dataRowTop = 0; //滑块最多滑到顶部
} else if (this.bigDataComputedParam.dataRowTop >= this.bigDataComputedParam.dataRowTopMax) {
this.bigDataComputedParam.dataRowTop = this.bigDataComputedParam.dataRowTopMax; //滑块最多滑到最底部
} else {
e.preventDefault(); //禁用滑轮滚动默认事件。当表格滑动到边缘时再启用默认滚动事件
}
// #endif
// } else
// if (scrollEvent instanceof TouchEvent) {
// #ifdef APP||APP-VUE
if (this.bigDataScrollActionParam.isMouseDown) {
let positionY = scrollEvent.changedTouches[0].clientY
let timeStamp = scrollEvent.timeStamp
let step = 0
this.bigDataScrollActionParam.prePositionY = this.bigDataScrollActionParam.prePositionY ||
positionY
this.bigDataScrollActionParam.preTimeStamp = this.bigDataScrollActionParam.preTimeStamp ||
timeStamp
let timeDuration = timeStamp - this.bigDataScrollActionParam.preTimeStamp;
let velocity;
if (timeDuration === 0) {
velocity = 0
} else {
velocity = (positionY - this.bigDataScrollActionParam.prePositionY) / timeDuration
}
if (this.bigDataScrollActionParam.prePositionY !== null) {
step = positionY - this.bigDataScrollActionParam.prePositionY
this.bigDataComputedParam.dataRowTop -= step
}
this.bigDataScrollActionParam.prePositionY = positionY
this.bigDataScrollActionParam.preTimeStamp = timeStamp
this.bigDataScrollActionParam.speed = velocity * 16
e.preventDefault()
e.stopPropagation()
}
// #endif
// }
this.bigDataUpdateRows();
},
bigDataBuildData() {
this.bigDataFilteredFlatWithLineInfo = []
this.bigDataFilteredLineHeightTotal = 0
for (let key of Object.keys(this.dataFiltered)) {
if (this.dataFiltered[key] && this.dataFiltered[key].length > 0) {
this.bigDataFilteredLineHeightTotal += this.bigDataParam.dataHeadHeight
this.bigDataFilteredFlatWithLineInfo.push({
id: "Head:" + key,
username: key,
__line_info__: {
isEmptySpace: false,
isHead: true,
currentHeight: this.bigDataParam.dataHeadHeight,
totalHeight: this.bigDataFilteredLineHeightTotal
}
})
this.dataFiltered[key].forEach((item) => {
this.bigDataFilteredLineHeightTotal += this.bigDataParam.dataRowHeight
this.bigDataFilteredFlatWithLineInfo.push({
id: "Row:" + item.id,
username: item.username,
phone: item.phone,
__line_info__: {
isEmptySpace: false,
isHead: false,
currentHeight: this.bigDataParam.dataRowHeight,
totalHeight: this.bigDataFilteredLineHeightTotal
}
})
})
}
}
this.bigDataFilteredLineHeightTotal += this.bigDataParam.dataRowHeight
this.bigDataFilteredFlatWithLineInfo.push({
id: "EmptyFooter",
username: '',
__line_info__: {
isEmptySpace: true,
isHead: false,
currentHeight: this.bigDataParam.dataRowHeight,
totalHeight: this.bigDataFilteredLineHeightTotal
}
})
//计算出数据区域滑动到最后一条数据,所需要的位移距离
this.bigDataComputedParam.dataRowTopMax = Math.max(this.bigDataFilteredLineHeightTotal - this.bigDataParam
.scrollHeight, 0)
},
bigDataUpdateRows() {
this.bigDataComputedParam.dataRowTop = Math.max(0, this.bigDataComputedParam.dataRowTop)
this.bigDataComputedParam.dataRowTop = Math.min(this.bigDataComputedParam.dataRowTop, this
.bigDataComputedParam.dataRowTopMax)
if (this.bigDataFilteredFlatWithLineInfo.length > 0) {
//更新数据区的数据,根据滚动的距离,确定可见的第一条数据下标(此方法保证可见数据行内容的更新。注释掉此方法,拖动滚动条,数据会在开始部分一直循环)
//遍历数据,插入窗口中可见的数据行
let startIndex = null
let endIndex = null
for (let i = parseInt(this.bigDataComputedParam.dataRowTop / Math.max(this.bigDataParam.dataRowHeight,
this.bigDataParam.dataHeadHeight), 10) - 1; i < this.bigDataFilteredFlatWithLineInfo
.length; i++) {
if (i < 0) {
continue
}
const totalHeight = this.bigDataFilteredFlatWithLineInfo[i].__line_info__.totalHeight
if (totalHeight >= this.bigDataComputedParam.dataRowTop && startIndex === null) {
startIndex = i
}
if (totalHeight >= (this.bigDataComputedParam.dataRowTop + this.bigDataParam.scrollHeight) &&
endIndex === null) {
endIndex = Math.min(i + 3, this.bigDataFilteredFlatWithLineInfo.length)
} else if (endIndex === null && this.bigDataFilteredFlatWithLineInfo.length < 10) {
endIndex = this.bigDataFilteredFlatWithLineInfo.length
}
if (startIndex !== null && endIndex !== null) {
break
}
}
startIndex = startIndex || 0
endIndex = endIndex || 0
this.bigDataFilteredFlatWithLineInfoCurrent = this.bigDataFilteredFlatWithLineInfo.slice(startIndex,
endIndex)
if (this.bigDataFilteredFlatWithLineInfoCurrent && this.bigDataFilteredFlatWithLineInfoCurrent[0]) {
const tmp = this.bigDataFilteredFlatWithLineInfoCurrent[0]
let firstRowHeight = tmp.__line_info__.currentHeight || 0
let firstRowTotalHeight = tmp.__line_info__.totalHeight || 0
//数据区实现拖动滑条的位移效果(保证数据行的滚动效果,注释此方法,拖动滚动条,数据无滚动效果,但内容会更新)
this.newMarginTop = firstRowTotalHeight - this.bigDataComputedParam.dataRowTop - firstRowHeight
if (!this.isZoomActiveIndex) {
this.currentActiveIndex = pinyin.getFullChars(tmp.username).charAt(0).toUpperCase();
}
}
}
},
bigDataScrollAminate() {
this.bigDataScrollActionParam.timeoutObj = setInterval(() => {
if (Math.abs(this.bigDataScrollActionParam.speed) > 0 && !this.bigDataScrollActionParam
.isMouseDown) {
this.bigDataScrollActionParam.speed = (this.bigDataScrollActionParam.speed >= 0 ? 1 : -1) *
Math.min(this.bigDataScrollActionParam.speedMax, Math.max(0, Math.abs(this
.bigDataScrollActionParam.speed) - this.bigDataScrollActionParam
.resistance))
this.bigDataComputedParam.dataRowTop -= this.bigDataScrollActionParam.speed
this.bigDataUpdateRows()
}
}, 16)
},
bigDataClearScrollAnimate() {
if (this.bigDataScrollActionParam.timeoutObj) {
clearInterval(this.bigDataScrollActionParam.timeoutObj)
this.bigDataScrollActionParam.timeoutObj = null
}
}
}
}
</script>
<style lang='scss' scoped>
* {
font-family: Consolas, sans-serif;
box-sizing: border-box !important;
margin: 0;
padding: 0;
}
.index-list__index__letter--active2 {
color: red;
}
.index-list__index__letter {
margin: 5px 0;
font-size: 20px;
}
.index-list {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
&__head {
position: absolute;
top: 0;
width: 100%;
height: 50px;
overflow: hidden;
background-color: $uni-bg-color;
border-bottom: 1px solid $uni-bg-color-grey;
}
&__input {
box-sizing: border-box;
width: 90%;
height: 30px;
padding: 0 20px;
margin: auto;
height: 30px;
margin: 10px auto;
line-height: 30px;
background-color: $uni-bg-color-grey;
border-radius: 30px;
font-size: 12px;
color: $uni-text-color;
&__placeholder {
text-align: center;
color: $uni-text-color-placeholder;
}
}
&__scroller {
position: absolute;
top: 50px;
bottom: 0;
width: 100%;
background-color: white;
overflow-x: hidden;
overflow-y: auto;
}
&__big-data {
&__table-panel {
height: 100%;
width: 100%;
}
&__table-body {
/*因为数据区与滚动条要使用position:absolute做成左右固定布局table-body作为其两节点的父节点使用position: relative*/
position: relative;
height: 100%;
overflow: hidden;
background-color: white;
}
&__table-data-panel {
overflow: hidden;
position: absolute;
left: 0;
/*right值宽度等于滚动条的宽度。也可不设置但滚动条会覆盖表格的最右侧*/
/*right: 15px;*/
width: 100%;
height: 100%;
}
}
&__group-head {
box-sizing: border-box;
background-color: $uni-bg-color-grey;
color: $uni-text-color-grey;
height: 35px;
line-height: 45px;
padding: 0 0 0 15px;
}
&__group-cell {
box-sizing: border-box;
background-color: $uni-bg-color;
color: $uni-text-color;
font-size: 14px;
font-family: SourceHanSansSC-Regular, SourceHanSansSC;
height: 45px;
line-height: 45px;
padding: 0 0 0 15px;
&:after {
display: block;
position: absolute;
content: '';
width: 100%;
height: 1px;
background-color: $uni-bg-color-grey;
}
&:active {
background-color: $uni-bg-color-hover;
}
&--empty-space {
&:active {
background-color: white;
}
}
}
&__index {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
&__wrapper {
width: 20px;
margin: 0 7px 0 0;
text-align: center;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 10px;
color: $uni-text-color-grey;
padding: 10px 0;
font-family: Consolas, sans-serif;
}
&__letter {
display: block;
height: 15px !important;
line-height: 15px !important;
transition: transform 0.1s;
&--active {
transition: none;
font-weight: bold;
font-size: 50px;
transform: translateX(-70px);
}
}
}
}
</style>