组件
vue
<template>
<view class="popup" v-show="show">
<view class="bg" @tap="cancelMultiple"></view>
<view class="selectMultiple">
<view class="multipleBody">
<view class="title">
<view class="close" @tap="cancelMultiple">
取消
</view>
<view class="name">
<!-- cancelButton="none" 不展示取消按钮-->
<uni-search-bar
@input="updateList"
@confirm="onSearchConfirm"
cancelButton="none">
</uni-search-bar>
</view>
<view class="confirm" @tap="confirmMultiple">
确认
</view>
</view>
<view class="list">
<view class="mask mask-top"></view>
<view class="mask mask-bottom"></view>
<scroll-view class="diet-list" scroll-y="true">
<view v-for="(item, index) in list" :class="['item', item.selected ? 'checked' : '']" @tap="onChange(index, item)">
<span style="font-size: 16px;">{{item.label}}</span>
<view class="icon" v-show="item.selected">
<icon type="success_no_circle" size="16" color="#2D8DFF"/>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name:"my-curry-multi-select",
data() {
return {
// 选中值
value: [],
// 选中列表
selected: [],
// 列表数据
list: [],
originList: [],
};
},
props: {
// 是否显示
show: {
type: Boolean,
default: false
},
// 标题
title: {
type: String,
default: ''
},
//数据列表
columns: {
type: Array,
default: []
},
// 默认选中
defaultIndex: {
type: Array,
default: [],
},
isMultiSelect: {
type: Boolean,
default: true
},
},
watch: {
// 监听是否显示
show(val) {
if(val) {
this.openMultiple();
}
}
},
methods: {
sortListWithSelectedFirst(list) {
return list.slice().sort((a, b) => {
const aSelected = a.selected ? 1 : 0;
const bSelected = b.selected ? 1 : 0;
return bSelected - aSelected;
});
},
updateList(str) {
this.list.map(e => {
this.originList.map(item => {
if (e.selected && item.value === e.value) {
item.selected = true
}
})
})
if (str === null || str === undefined || str === '') {
this.list = JSON.parse(JSON.stringify(this.originList))
} else {
const filtered = this.originList.filter(e => e.label.indexOf(str) > -1 || e.selected);
this.list = this.sortListWithSelectedFirst(filtered);
}
},
// 新增:处理搜索确认事件
onSearchConfirm(e) {
const searchValue = e.value || e;
// 先更新列表
this.updateList(searchValue);
// 如果有搜索内容且搜索结果不为空,自动选择第一个未选中的项目
if (searchValue && this.list.length > 0) {
// 找到第一个未选中的项目
const firstUnselectedIndex = this.list.findIndex(item => !item.selected);
if (firstUnselectedIndex !== -1) {
// 自动选择第一个未选中的项目
this.onChange(firstUnselectedIndex, this.list[firstUnselectedIndex]);
}
}
},
// 列点击事件
onChange(index, item) {
// 单选
if (!this.isMultiSelect) {
this.value = [];
this.selected = [];
this.value.push(item.value.toString());
this.selected.push({
label: item.label,
value: item.value,
});
return this.$emit("confirm", {selected: this.selected, value: this.value});
}
// 是否已选中
if(this.value.indexOf(item.value.toString()) >= 0) {
this.list[index].selected = false;
} else {
this.list[index].selected = true;
}
// 筛选已勾选数据
this.value = [];
this.selected = [];
this.list.forEach((col_item, col_index) => {
if(col_item.selected) {
this.value.push(col_item.value.toString());
this.selected.push({
label: col_item.label,
value: col_item.value,
});
}
});
this.list = this.sortListWithSelectedFirst(this.list);
this.$emit("change", {selected: this.selected, value: this.value});
},
// 弹出框开启触发事件
openMultiple() {
// 初始化列表数据,默认勾选数据
this.value = this.defaultIndex;
this.columns.forEach((item, index) => {
this.$set(item, "selected", false);
if(this.value.indexOf(item.value.toString()) >= 0) {
item.selected = true;
}
});
this.originList = Object.assign([], this.columns);
this.list = this.sortListWithSelectedFirst(JSON.parse(JSON.stringify(this.originList)));
},
// 确认
confirmMultiple() {
this.$emit("confirm", {selected: this.selected, value: this.value});
},
// 关闭/取消
cancelMultiple() {
this.$emit("cancel");
},
}
}
</script>
<style scoped lang="scss">
.popup {
width: 100%;
height: 100vh;
position: fixed;
z-index: 99999;
left: 0;
top: 0;
.bg {
width: 100%;
height: 100%;
background-color: rgba(black, .5);
}
}
.selectMultiple {
transition: none !important;
will-change: transform;
width: 100%;
position: absolute;
left: 0;
top: 0;
height: 50vh;
background-color: white;
border-radius: 0 0 20rpx 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
.multipleBody {
width: 100%;
height: 100%;
padding: 30rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
.title {
flex-shrink: 0;
font-size: 28rpx;
display: flex;
flex-direction: row;
align-items: center; /* 添加这一行 */
.close {
width: 80rpx;
text-align: left;
opacity: .5;
}
.name {
width: 530rpx;
text-align: center;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient:vertical;
-webkit-line-clamp:1;
}
.confirm {
width: 80rpx;
text-align: right;
color: #2D8DFF;
}
}
.list {
flex: 1;
overflow: hidden;
width: 100%;
padding-top: 30rpx;
position: relative;
.mask {
width: 100%;
height: 60rpx; // 减小渐变区域
position: absolute;
left: 0;
z-index: 2;
pointer-events: none;
&.mask-top {
top: 30rpx;
background-image: linear-gradient(to bottom, #fff, rgba(#fff, 0));
}
&.mask-bottom {
bottom: 0;
background-image: linear-gradient(to bottom, rgba(#fff, 0), #fff);
}
}
.diet-list {
height: 100%;
max-height: none;
}
.item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1px solid rgba(#000, 0.05);
span {
flex: 1;
font-size: 30rpx;
text-align: center;
}
.icon {
width: 32rpx;
height: 32rpx;
}
&.checked {
color: #2D8DFF;
}
&:last-child {
border-bottom: none;
margin-bottom: 60rpx;
}
&:first-child {
margin-top: 60rpx;
}
}
}
}
}
</style>
使用
vue
<template>
<view class="container">
<view>
<uni-forms ref="taskForm" :model="taskForm" labelWidth="80px">
<!-- 任务类型单选 -->
<uni-forms-item label="任务类型" name="type">
<view class="item">
<view :class="['select', taskForm.type ? 'selected' : '']"
@tap="openTypeSelectionBox(taskForm.type)">
{{ taskForm.type ? getTaskTypeName(taskForm.type) : '请选择任务类型' }}
</view>
<!-- 如果有内容显示关闭图标 -->
<uni-icons v-if="taskForm.type !== ''" type="clear" size="24" color="#c0c4cc" class="close-btn"
@tap="clearType"></uni-icons>
<!-- 如果没有内容显示下拉图标 -->
<uni-icons v-else type="pulldown" size="24" color="#c0c4cc" class="close-btn"
@tap="openTypeSelectionBox(taskForm.type)"></uni-icons>
<my-curry-multi-select title="请选择" :show="taskTypeShow" :columns="taskTypeList"
:defaultIndex="defaultTaskTypeIndex"
:isMultiSelect="false"
@confirm="confirmType($event)"
@cancel="taskTypeShow = false"></my-curry-multi-select>
</view>
</uni-forms-item>
<!-- 负责人多选 -->
<uni-forms-item label="负责人" name="personInChargeIds">
<view class="item">
<view :class="['select', (Array.isArray(taskForm.personInChargeIds) && taskForm.personInChargeIds.length > 0) ? 'selected' : '']"
@tap="openPersonInChargeIdsMultiSelectionBox(taskForm.personInChargeIds)">
{{ (Array.isArray(taskForm.personInChargeIds) && taskForm.personInChargeIds.length > 0)
? getUserNamesByIds(taskForm.personInChargeIds)
: '请选择负责人' }}
</view>
<uni-icons v-if="Array.isArray(taskForm.personInChargeIds) && taskForm.personInChargeIds.length > 0"
type="clear" size="24" color="#c0c4cc" class="close-btn"
@tap="clearPersonInChargeIds"></uni-icons>
<uni-icons v-else type="pulldown" size="24" color="#c0c4cc" class="close-btn"
@tap="openPersonInChargeIdsMultiSelectionBox(taskForm.personInChargeIds)"></uni-icons>
<my-curry-multi-select title="请选择" :show="taskTaskPersonInChargesShow"
:columns="userList"
:defaultIndex="defaultTaskPersonInChargesIndex"
:isMultiSelect="true"
@confirm="confirmPersonInChargeIds($event)"
@cancel="taskTaskPersonInChargesShow = false"></my-curry-multi-select>
</view>
</uni-forms-item>
</uni-forms>
<button type="primary" @click="submit">提交</button>
</view>
</view>
</template>
<script>
import myCurryMultiSelect from "@/components/curry-multi-select/my-curry-multi-select.vue";
export default {
components: {myCurryMultiSelect},
data() {
return {
// 当前正选择哪个任务类型元素
defaultTaskTypeIndex: [],
// 任务类型列表,单选
taskTypeList: [
{"label": "指派任务", "value": 1},
{"label": "设计任务", "value": 2},
{"label": "代办", "value": 2}
],
// 是否展示任务类型下拉选项
taskTypeShow: false,
// 当前正选择哪些任务负责人元素
defaultTaskPersonInChargesIndex: [],
// 是否展示任务负责人下拉选项
taskTaskPersonInChargesShow: false,
// 用户列表,多选
userList: [
{ value: 1, label: '张三' },
{ value: 2, label: '李四' },
{ value: 3, label: '王五' },
],
// 表单数据
taskForm: {
taskTitle: '',
insertUser: '',
type: 2,
personInChargeIds: [1,2],
personInChargeId: null,
taskContent: '',
requiredCompletionTime: '',
actualCompletionTime: '',
weight: '',
weightScore: '',
timeoutStatus: '0',
participants: [],
taskPictureUrl: [],
taskFileUrl: [],
useSchedule: '1',
standardWorkingHours: '',
},
// 表单验证规则
rules: {
type: {
rules: [{required: true, errorMessage: '任务类型不能为空'}]
},
personInChargeIds: {
rules: [
{
required: true,
errorMessage: '负责人不能为空',
validateFunction: (rule, value) => {
return Array.isArray(value) && value.length > 0;
}
}
]
},
},
}
},
onLoad() {
},
created() {
},
onReady() {
this.$refs.taskForm.setRules(this.rules)
},
methods: {
// =====单选====
// 打开任务类型选择框
openTypeSelectionBox(val) {
console.log('执行了openTypeSelectionBox,展开选择框时val值是:', val)
this.defaultTaskTypeIndex = val !== '' ? [String(val)] : [];
console.log('this.defaultTaskTypeIndex',this.defaultTaskTypeIndex)
this.taskTypeShow = true;
},
// 清空类型选择框
clearType() {
this.defaultTaskTypeIndex = [];
this.taskForm.type = '';
},
// 获取任务类型名称,把值转换为名称显示出来
getTaskTypeName(value) {
const option = this.taskTypeList.find(item => String(item.value) === String(value));
return option ? option.label : '请选择任务类型';
},
// 确认选择任务类型
confirmType(e) {
// e是一个数组
this.taskForm.type = e.value[0];
this.taskTypeShow = false;
},
// =====多选====
// 打开负责人多选框
openPersonInChargeIdsMultiSelectionBox(val) {
console.log('执行了openPersonInChargeIdsMultiSelectionBox,展开选择框时val值是:', val)
this.defaultTaskPersonInChargesIndex = Array.isArray(val) ? val.map(item => String(item)) : [];
console.log('this.defaultTaskPersonInChargesIndex', this.defaultTaskPersonInChargesIndex)
this.taskTaskPersonInChargesShow = true;
},
// 清空负责人选择框
clearPersonInChargeIds() {
this.defaultTaskPersonInChargesIndex = [];
this.taskForm.personInChargeIds = null; // 继续保持你的设定
},
// 获取任务负责人名称,把值转换为名称显示出来
getUserNamesByIds(values) {
if (!Array.isArray(values) || values.length === 0) return '请选择负责人';
const labels = values.map(value => {
const option = this.userList.find(item => String(item.value) === String(value));
return option ? option.label : value;
});
return labels.join(',');
},
// 确认选择任务负责人
confirmPersonInChargeIds(e) {
// e是一个数组
this.taskForm.personInChargeIds = e.value;
this.taskTaskPersonInChargesShow = false;
},
// 提交表单
submit() {
console.log('提交时表单数据是:', this.taskForm)
// 就是上面这个写法有一个问题,就是提交的时候,选择框的绑定的都是字符串。就是是数值,也是转为字符串的。但是前段字符串,后端用Long也能接收。所以问题不大。
this.$refs.taskForm.validate().then(res => {
this.$modal.msgSuccess("修改成功")
})
},
}
}
</script>
<style lang="scss">
.item {
width: 100%;
padding: 0;
position: relative;
display: flex;
align-items: center;
height: 35px;
.select {
flex-grow: 1;
border: 1px solid #dadbde;
padding: 4px 9px;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
color: #6a6a6a;
line-height: 25px;
height: 100%;
overflow: hidden;
&.selected {
color: black;
font-size: 15px;
}
}
.close-btn {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
color: red;
cursor: pointer;
}
}
</style>
效果
效果:

