2025-11-27 17:15:44 +08:00

339 lines
7.3 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="g-picker">
<view class="g-picker-value" @click="showPicker">
<!-- <u-input v-model="value" disabled disabledColor="#fff" :inputAlign="inputAlign" :placeholder="placeholder" style="pointer-events: none">
</u-input> -->
<text class="_value" v-if="value">{{value}}</text>
<text class="_placeholder" v-else>{{placeholder}}</text>
<!-- <u-icon v-if="!disabled" name="arrow-right" color="#c0c4cc"></u-icon> -->
</view>
<van-popup v-model:show="show" round position="bottom" :style="{ height: '40%' }">
<view class="g-picker-con">
<view class="g-picker-operate">
<text @click="show = false">取消</text>
<van-field v-if="searchFlag" class="g-input" v-model="searchName" placeholder="请输入">
</van-field>
<text @click="confirm">确认</text>
</view>
<!-- 使用计算属性实现滚动加载 -->
<scroll-view
class="g-picker-list"
scroll-y="true"
@scrolltolower="loadMore"
ref="scrollView"
>
<view
class="g-picker-item"
v-for="(col, inx) in displayData"
:key="col[this.filter.value] || inx"
@click="checkItem(col, inx)"
>
<view
:class="['g-picker-item_label', col._check ? 'g-picker-item--actived' : '']"
:style="{color: col._check ? activedColor : '#333'}"
>
{{ col[filter.label] }}
</view>
<u-icon
class="g-picker-item_icon"
v-show="col._check"
name="checkbox-mark"
:color="activedColor"
></u-icon>
</view>
<!-- 加载状态提示 -->
<view class="load-more" v-if="hasMoreData || isLoading">
<text v-if="isLoading">加载中...</text>
<text v-else-if="!hasMoreData">没有更多了</text>
</view>
</scroll-view>
</view>
</van-popup>
</view>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
// picker组件绑定值默认 name 用,拼接
value: {
type: String,
default: ""
},
// 数据源
columns: {
type: Array,
default: () => {
return []
}
},
// 数据显示格式化
filter: {
type: Object,
default: () => {
return {
label: 'label',
value: 'value'
}
}
},
defaultList: {
type: Array,
default: () => {
return []
}
},
// 是否开启搜索
searchFlag: {
type: Boolean,
default: false
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 选中后颜色
activedColor: {
type: String,
default: "#000"
},
// 文本对齐方式
inputAlign: {
type: String,
default: "right"
},
// 默认提示
placeholder: {
type: String,
default: "请选择"
},
// 每页加载数量
pageSize: {
type: Number,
default: 50
}
},
data() {
return {
show: false,
columnsList: [],
value_chx: [],
searchName: "",
// 滚动加载相关状态
currentPage: 1,
isLoading: false
}
},
watch: {
value: {
handler(n) {
if (n) this.reShow();
},
immediate: true
},
columns: {
handler(n) {
this.columnsList = n
if (n.length) {
for (let val of this.columnsList) {
this.$set(val, '_check', false)
}
}
// 重置页码
this.currentPage = 1
},
immediate: true
},
// 监听搜索关键词变化,重置页码
searchName: {
handler() {
this.currentPage = 1
}
},
// 监听弹窗显示状态
show: {
handler(newVal) {
if (newVal) {
this.$nextTick(() => {
// 弹窗打开时确保滚动到顶部
if (this.$refs.scrollView) {
this.$refs.scrollView.scrollTop = 0
}
})
}
}
}
},
computed: {
// 计算过滤后的数据
filteredData() {
const resultList = this.columnsList.filter(item =>
this.searchName ? item[this.filter.label].includes(this.searchName) : true
)
// 处理默认选中项
this.defaultList.forEach(item => {
const find = resultList.find(ele => ele[this.filter.value] == item);
if(find) {
find._check = true;
}
})
return resultList;
},
// 计算当前应该显示的数据(使用计算属性实现滚动加载)
displayData() {
// 根据当前页码和每页数量计算显示的数据范围
const endIndex = this.currentPage * this.pageSize;
return this.filteredData.slice(0, endIndex);
},
// 判断是否还有更多数据
hasMoreData() {
return this.displayData.length < this.filteredData.length;
}
},
methods: {
showPicker() {
if (this.disabled) return
this.show = true
},
// 滚动到底部加载更多
loadMore() {
// 如果正在加载或没有更多数据,则不执行操作
if (this.isLoading || !this.hasMoreData) return;
this.isLoading = true;
// 模拟异步加载延迟
setTimeout(() => {
// 增加页码,触发计算属性重新计算显示数据
this.currentPage++;
this.isLoading = false;
}, 100);
},
reShow() {
let data = this.value.split(",")
setTimeout(() => {
for (let val of this.columnsList) {
if (data.includes(val[this.filter.label])) val._check = true
}
}, 100)
},
checkItem(col, inx) {
col._check = !col._check
},
confirm() {
let value = [],
label = []
// 从完整数据源中获取所有选中项
this.value_chx = this.columnsList.filter(v => v._check)
for (let val of this.value_chx) {
value.push(val[this.filter.value])
label.push(val[this.filter.label])
}
this.show = false
this.$emit('confirm', this.value_chx, value, label)
this.$emit('change', label.join(","))
}
}
}
</script>
<style lang="scss" scoped>
.g-picker {
.g-picker-value {
display: flex;
}
.g-picker-con {
color: #333;
font-size: 28rpx;
.g-picker-operate {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 32rpx;
position: relative;
.g-input {
width: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 11;
border: 1rpx solid;
padding: 0 32rpx !important;
border-color: #c1c1c1;
}
:deep(.van-field__control) {
font-size: 28rpx;
}
:deep(.van-field__control::placeholder) {
font-size: 28rpx;
}
text {
color: #999;
&:last-child {
color: #3c9cff;
}
}
}
.g-picker-list {
min-height: 30vh;
max-height: 60vh;
.g-picker-item {
position: relative;
width: 100%;
height: 80rpx;
.g-picker-item_label {
width: 66%;
margin: 0 auto;
text-align: center;
line-height: 80rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.g-picker-item--actived {
font-weight: 600;
}
.g-picker-item_icon {
position: absolute;
top: 50%;
right: 40rpx;
transform: translateY(-50%);
}
}
// 加载更多提示样式
.load-more {
text-align: center;
padding: 20rpx 0;
font-size: 24rpx;
color: #999;
}
}
}
}
._value {
word-break: break-all;
line-height: 36rpx;
}
._placeholder {
color: rgb(192, 196, 204);
font-size: 28rpx;
}
</style>