233 lines
5.4 KiB
Vue
233 lines
5.4 KiB
Vue
<template>
|
|
<u-popup v-model="localVisible" mode="bottom" :height="height">
|
|
<view class="nbd-select">
|
|
<!-- 顶部按钮 -->
|
|
<view class="nbd-select-header">
|
|
<u-button size="mini" @click="handleCancel">
|
|
{{ cancelText }}
|
|
</u-button>
|
|
<view v-if="title || tip" class="nbd-select-title-wrap">
|
|
<text class="nbd-select-title">{{ title }}</text>
|
|
<text class="nbd-select-tip" v-if="tip">{{ tip }}</text>
|
|
</view>
|
|
<u-button size="mini" type="primary" @click="handleConfirm">
|
|
{{ confirmText }}
|
|
</u-button>
|
|
</view>
|
|
|
|
<!-- 搜索栏 -->
|
|
<view class="nbd-select-filter" v-if="showSearch">
|
|
<u-search :placeholder="searchPlaceholder" :height="60" v-model="searchKey" :show-action="false"
|
|
@change="handleSearch" @clear="handleSearch"></u-search>
|
|
</view>
|
|
|
|
<!-- 列表内容 -->
|
|
<scroll-view scroll-y="true" class="nbd-select-inner" :scroll-into-view="scrollIntoView">
|
|
<view class="nbd-select-inner-wrap">
|
|
<!-- 空状态 -->
|
|
<view class="nbd-select-empty" v-if="filteredList.length === 0">
|
|
<u-empty mode="list" text="暂无数据"></u-empty>
|
|
</view>
|
|
|
|
<!-- 列表项 -->
|
|
<view v-for="(item, idx) in filteredList" :key="idx" :id="'item-' + idx" class="nbd-select-item"
|
|
:class="{ active: isSelected(item) }" @click="handleItemClick(item)">
|
|
<text class="nbd-select-item-text">{{ item[labelKey] }}</text>
|
|
<view v-if="multiple" class="nbd-select-checkbox">
|
|
<u-icon :name="isSelected(item) ? 'checkbox-mark' : 'square'" :size="36"
|
|
:color="isSelected(item) ? $u.type.primary : '#dcdee0'"></u-icon>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</u-popup>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ref,
|
|
computed,
|
|
watch
|
|
} from 'vue'
|
|
|
|
const props = defineProps({
|
|
// 弹窗显示状态
|
|
modelValue: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 弹窗高度
|
|
height: {
|
|
type: String,
|
|
default: '70%'
|
|
},
|
|
// 标题
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 标题提示
|
|
tip: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 数据列表
|
|
list: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
// 选中项(支持数组,多选时使用)
|
|
model: {
|
|
type: [Object, Array, String, Number],
|
|
default: null
|
|
},
|
|
// 是否多选
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 显示搜索框
|
|
showSearch: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 搜索占位符
|
|
searchPlaceholder: {
|
|
type: String,
|
|
default: '请输入关键词搜索'
|
|
},
|
|
// 字段名配置
|
|
labelKey: {
|
|
type: String,
|
|
default: 'label'
|
|
},
|
|
valueKey: {
|
|
type: String,
|
|
default: 'value'
|
|
},
|
|
// 自定义搜索字段(默认搜索 labelKey)
|
|
searchKeyField: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
// 按钮文字
|
|
cancelText: {
|
|
type: String,
|
|
default: '取消'
|
|
},
|
|
confirmText: {
|
|
type: String,
|
|
default: '确定'
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits([
|
|
'update:modelValue',
|
|
'update:model',
|
|
'cancel',
|
|
'confirm',
|
|
'searchChange',
|
|
'change'
|
|
])
|
|
|
|
const localVisible = ref(false)
|
|
const searchKey = ref('')
|
|
const scrollIntoView = ref('')
|
|
|
|
// 当前选中的项(多选时为数组)
|
|
const selectedItems = ref(props.multiple ? (props.model || []) : (props.model || null))
|
|
|
|
// 监听外部 model 变化
|
|
watch(() => props.model, (newVal) => {
|
|
selectedItems.value = props.multiple ? (newVal || []) : newVal
|
|
}, {
|
|
deep: true
|
|
})
|
|
|
|
// 弹窗状态同步
|
|
watch(() => props.modelValue, (val) => {
|
|
localVisible.value = val
|
|
if (val) {
|
|
// 打开时重置搜索
|
|
searchKey.value = ''
|
|
// 滚动到选中的项
|
|
setTimeout(() => {
|
|
if (selectedItems.value && !props.multiple) {
|
|
const index = props.list?.findIndex(item =>
|
|
item[props.valueKey] === selectedItems.value[props.valueKey]
|
|
)
|
|
if (index !== undefined && index > -1) {
|
|
scrollIntoView.value = 'item-' + index
|
|
}
|
|
}
|
|
}, 300)
|
|
}
|
|
})
|
|
|
|
watch(localVisible, (val) => {
|
|
emit('update:modelValue', val)
|
|
})
|
|
|
|
// 过滤后的列表
|
|
const filteredList = computed(() => {
|
|
if (!searchKey.value) return props.list
|
|
|
|
const keyword = searchKey.value.toLowerCase()
|
|
const searchField = props.searchKeyField || props.labelKey
|
|
|
|
return props.list.filter(item => {
|
|
const value = item[searchField]
|
|
if (value === null || value === undefined) return false
|
|
return String(value).toLowerCase().includes(keyword)
|
|
})
|
|
})
|
|
|
|
// 判断是否选中
|
|
const isSelected = (item) => {
|
|
if (props.multiple) {
|
|
return selectedItems.value.some(
|
|
selected => selected[props.valueKey] === item[props.valueKey]
|
|
)
|
|
}
|
|
if (selectedItems.value && typeof selectedItems.value === 'object') {
|
|
return selectedItems.value[props.valueKey] === item[props.valueKey]
|
|
}
|
|
return selectedItems.value === item[props.valueKey]
|
|
}
|
|
|
|
// 点击列表项
|
|
const handleItemClick = (item) => {
|
|
if (props.multiple) {
|
|
const index = selectedItems.value.findIndex(
|
|
selected => selected[props.valueKey] === item[props.valueKey]
|
|
)
|
|
if (index > -1) {
|
|
selectedItems.value.splice(index, 1)
|
|
} else {
|
|
selectedItems.value.push(item)
|
|
}
|
|
} else {
|
|
selectedItems.value = item
|
|
}
|
|
emit('change', selectedItems.value)
|
|
}
|
|
|
|
// 搜索
|
|
const handleSearch = (val) => {
|
|
emit('searchChange', val)
|
|
}
|
|
|
|
// 取消
|
|
const handleCancel = () => {
|
|
emit('cancel')
|
|
localVisible.value = false
|
|
}
|
|
|
|
// 确认
|
|
const handleConfirm = () => {
|
|
emit('confirm', selectedItems.value)
|
|
emit('update:model', selectedItems.value)
|
|
localVisible.value = false
|
|
}
|
|
</script> |