封装一个小程序选择器(可多选、单选、搜索)
组件
<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>
使用
<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>
效果
效果:
个人站点链接
我的博客链接:https://blog.yimengtut.online/
相关文章:

封装一个小程序选择器(可多选、单选、搜索)
组件 <template><view class"popup" v-show"show"><view class"bg" tap"cancelMultiple"></view><view class"selectMultiple"><view class"multipleBody"><view class&…...

Dest建筑能耗模拟仿真功能简介
Dest建筑能耗模拟仿真功能简介 全球建筑能耗占终端能源消费的30%以上,掌握建筑能耗模拟是参与绿色建筑认证(如LEED、WELL)、超低能耗设计、既有建筑节能改造的必备能力。DEST作为国内主流建筑能耗模拟工具,广泛应用于设计院、咨询…...

【Hot 100】121. 买卖股票的最佳时机
目录 引言买卖股票的最佳时机我的解题 🙋♂️ 作者:海码007📜 专栏:算法专栏💥 标题:【Hot 100】121. 买卖股票的最佳时机❣️ 寄语:书到用时方恨少,事非经过不知难! 引…...

【机器学习基础】机器学习入门核心算法:XGBoost 和 LightGBM
机器学习入门核心算法:XGBoost 和 LightGBM 一、算法逻辑XGBoost (eXtreme Gradient Boosting)LightGBM (Light Gradient Boosting Machine) 二、算法原理与数学推导目标函数(二者通用)二阶泰勒展开:XGBoost 分裂点增益计算&#…...

Linux | Shell脚本的常用命令
一. 常用字符处理命令 1.1 连续打印字符seq seq打印数字;且只能正向打印,不可反向连续打印 设置打印步长 指定打印格式 1.2 反向打印字符tac cat 正向,tac 反向 1.3 打印字符printf printf "打印的内容"指定格式打印内容 换行…...
跑步的强度等级分类
概述 最大心率简化计算公式是【220-年龄】,具体值建议通过实际测试校准。在跑步训练中,以最大心率(Heart Rate Maximum)为指标对强度分类,常见分类对应的心率区间如下: 强度等级心率区间(% HR…...

【JUC】深入解析 JUC 并发编程:单例模式、懒汉模式、饿汉模式、及懒汉模式线程安全问题解析和使用 volatile 解决内存可见性问题与指令重排序问题
单例模式 单例模式确保某个类在程序中只有一个实例,避免多次创建实例(禁止多次使用new)。 要实现这一点,关键在于将类的所有构造方法声明为private。 这样,在类外部无法直接访问构造方法,new操作会在编译…...

2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷3:共计6题带解析)
2025年全国青少年信息素养大赛复赛C++算法创意实践挑战赛真题模拟强化训练(试卷3:共计6题带解析) 第1题:四位数密码 【题目描述】 情报员使用4位数字来传递信息,同时为了防止信息泄露,需要将数字进行加密。数据加密的规则是: 每个数字都进行如下处理:该数字加上5之后除…...

Mongodb | 基于Springboot开发综合社交网络应用的项目案例(中英)
目录 Project background Development time Project questions Create Project create springboot project project framework create folder Create Models user post Comment Like Message Serive tier user login and register Dynamic Publishing and Bro…...

飞腾D2000与FPGA结合的主板
UD VPX-404是基于高速模拟/数字采集回放、FPGA信号实时处理、CPU主控、高速SSD实时存储架构开发的一款高度集成的信号处理组合模块,采用6U VPX架构,模块装上外壳即为独立整机,方便用户二次开发。 UD VPX-404模块的国产率可达到100%࿰…...

百度量子蜘蛛3.0横空出世,搜索引擎迎来“量子跃迁“级革命
一、量子蜘蛛3.0的三大颠覆性升级 1. 动态抓取:让内容实时"量子纠缠" - 智能频率调节:根据网站更新频率自动调整抓取节奏,新闻类站点日抓取量达3-5次,静态页面抓取间隔延长至72小时。某财经媒体通过"热点事件15分钟…...

GitHub开源|AI顶会论文中文翻译PDF合集(gpt-translated-pdf-zh)
项目核心特点 该项目专注于提供计算机科学与人工智能领域的高质量中文翻译资源,以下为关键特性: 主题覆盖广泛:包含算法、数据结构、概率统计等基础内容,以及深度学习、强化学习等前沿研究方向。格式统一便捷:所有文…...
JSR 303(即 Bean Validation)是一个通过注解在 Java Bean 上定义和执行验证规则的规范
🛠️ 一、JSR 303是什么? JSR 303(Java Specification Requests 303)是Java EE 6的子规范,全称Bean Validation。它通过注解方式对JavaBean的属性值进行标准化校验,例如检查非空、长度、格式等规则…...
5G 网络中的双向认证机制解析
一、5G 网络中的双向认证机制解析 在 5G 核心网中,双向认证是指UE(用户设备)与网络互相验证对方身份的过程。这一机制通过多层次的安全协议和密钥交换,确保通信双方的合法性,防止中间人攻击和身份伪造。 1. UE 存储的关键信息 UE 作为用户终端,存储以下核心安全信息:…...
DAY07:Vue Router深度解析与多页面博客系统实战
第一部分:Vue Router核心概念深度剖析 1.1 现代前端路由的本质 在单页应用(SPA)时代,前端路由扮演着至关重要的角色。它突破了传统多页面应用的跳转方式,通过以下机制实现无刷新页面切换: Hash模式&#…...

Drawio编辑器二次开发
Drawio (现更名为 Diagrams.net )是一款完全免费的在线图表绘制工具,由 JGraph公司 开发。它支持创建多种类型的图表,包括流程图、组织结构图、UML图、网络拓扑图、思维导图等,适用于商务演示、软件设计等多种场景…...

1.测试过程之需求分析和测试计划
测试基础 流程 1.分析测试需求 2.编写测试计划 3.设计与编写测试用例 4.执行测试 5.评估与总结 测试目标 根据测试阶段不同可分为四个主要目标:预防错误(早期)、发现错误(开发阶段)、建立信心(验收阶段&a…...
第三十七天打卡
过拟合的判断:测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint,还包含训练状态 早停策略 过拟合判断 import torch import torch.nn as nn import torch.optim as optim from sklearn.datasets import load…...
Qt 窗口标志(Window Flags)详解:控制窗口样式与行为
在 Qt 中,windowFlags 用于控制窗口的样式和行为,包括标题栏、边框、最大化/最小化按钮等。合理设置 windowFlags 可以自定义窗口的外观和交互方式。本文将详细介绍常用的窗口标志及其组合效果。 1. 基本概念 windowFlags 是一个 Qt::WindowFlags 类型的…...
ABP VNext + CRDT 打造实时协同编辑
🛠️ ABP VNext CRDT 打造实时协同编辑器 🎉 📚 目录 🛠️ ABP VNext CRDT 打造实时协同编辑器 🎉🧠 背景与挑战🔹 系统架构🛣️ 端到端流程 🚦🔒 安全与鉴…...
微信小程序真机调试时如何实现与本地开发环境服务器交互
最近在开发微信小程序项目,真机调试时需要在手机上运行小程序,为了实现本地开发服务器与手机小程序的交互,需要以下步骤 1.将手机连到和本地一样的局域网 2.Visual Studio中将IIS Express服务器的localhost端口地址修改为本机的IP自定义的端口: 1)找到web api项目…...
Linux: network: dpdk, VF, ip link set down 对VF不生效
文章目录 问题另一个测试的结果是从dpdk的文档看怎么设置VF给VM内核的调用需要使用的命令问题 最近遇到一个问题,也可以说是一种常识,至少是之前不知道的常识:如果一个VF分配给了VM用作dpdk的输入。在host做ip link set down 这个PF的接口,对这个VM里的VF的功能没有任何影…...

[春秋云镜] CVE-2023-23752 writeup
首先奉上大佬的wp表示尊敬:(很详细)[ 漏洞复现篇 ] Joomla未授权访问Rest API漏洞(CVE-2023-23752)_joomla未授权访问漏洞(cve-2023-23752)-CSDN博客 知识点 Joomla版本为4.0.0 到 4.2.7 存在未授权访问漏洞 Joomla是一套全球知名的内容管理…...
Java集合操作常见错误与最佳实践
错误69:搜索无关类型的对象 泛型方法的类型安全漏洞 在Java引入参数化类型前,集合元素只能声明为Object类型,导致可以随意将字符串添加到数值列表中。虽然泛型机制对添加元素的方法进行了类型约束,但搜索和删除相关方法仍保留了Object类型的参数设计。这包括以下关键方法…...

CSS专题之水平垂直居中
前言 石匠敲击石头的第 16 次 在日常开发中,经常会遇到水平垂直居中的布局,虽然现在基本上都用 Flex 可以轻松实现,但是在某些无法使用 Flex 的情况下,又应该如何让元素水平垂直居中呢?这也是一道面试的必考题…...
python打卡day41@浙大疏锦行
知识回顾 1. 数据增强 2. 卷积神经网络定义的写法 3. batch归一化:调整一个批次的分布,常用与图像数据 4. 特征图:只有卷积操作输出的才叫特征图 5. 调度器:直接修改基础学习率 卷积操作常见流程如下: 1. …...
vue3 基本语法 父子关系
在Vue 3中,父子组件的关系是通过组件的嵌套实现的。父组件可以传递数据(props)给子组件,同时子组件可以通过事件(emits)与父组件通信。下面是如何在Vue 3中建立和使用父子组件的基本语法: 1. 创…...
算法-js-子集
题:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 方法一:迭代法 核心逻辑:动态扩展子集, 小规…...

(新)MQ高级-MQ的可靠性
消息到达MQ以后,如果MQ不能及时保存,也会导致消息丢失,所以MQ的可靠性也非常重要。 一、数据持久化 为了提升性能,默认情况下MQ的数据都是在内存存储的临时数据,重启后就会消失。为了保证数据的可靠性,必须…...
Android设置界面层级为最上层实现
Android设置界面层级为最上层实现 文章目录 Android设置界面层级为最上层实现一、前言二、Android设置界面层级为最上层实现1、主要代码2、后遗症 三、其他1、Android设置界面层级为最上层小结2、悬浮框的主要代码悬浮框 注意事项(1)权限限制(…...