vue3.2 + element-plus 实现跟随input输入框的弹框,弹框里可以分组或tab形式显示选项

效果

基础用法(分组选项)
在这里插入图片描述

高级用法(带Tab栏)
在这里插入图片描述

<!-- 弹窗跟随通用组件  SmartSelector.vue -->
<!-- 弹窗跟随通用组件 -->
<template><div class="smart-selector-container"><el-popover :visible="visible" :width="width" :placement="placement" trigger="manual" :popper-class="popperClass"@show="$emit('open')" @hide="$emit('close')"><template #reference><el-input ref="inputRef" v-model="selectedText" :placeholder="placeholder" :style="{ width: inputWidth }":type="multiline ? 'textarea' : 'text'" :autosize="autosize" :size="size" :readonly="readonly"@click="togglePopup"><template #suffix><el-icon><arrow-down /></el-icon></template></el-input></template><div class="smart-selector-content"><!-- Tab栏 --><el-tabs v-if="hasTabs" v-model="activeTab" @tab-click="handleTabChange"><el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.label" :name="tab.name" /></el-tabs><el-scrollbar :max-height="maxHeight"><!-- 分组选项 --><template v-for="(group, index) in currentGroups" :key="index"><div v-if="group.title" class="group-title">{{ group.title }}</div><div class="options-grid"><div v-for="(item, itemIndex) in group.options" :key="itemIndex" class="option-item":class="{ 'is-selected': isSelected(item) }" @click="handleSelect(item)">{{ getOptionLabel(item) }}</div></div><el-divider v-if="index < currentGroups.length - 1" /></template></el-scrollbar></div></el-popover></div>
</template><script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'const props = defineProps({modelValue: { type: [String, Array], default: '' },options: { type: Array, default: () => [] },groups: { type: Array, default: () => [] }, // 分组格式: [{title: '分组1', options: [...]}]tabs: { type: Array, default: () => [] }, // Tab格式: [{name: 'tab1', label: 'Tab1', options: [...]}]placeholder: { type: String, default: '请选择' },width: { type: String, default: '500px' },inputWidth: { type: String, default: '200px' },maxHeight: { type: String, default: '300px' },separator: { type: String, default: ',' },multiline: Boolean,autosize: { type: [Object, Boolean], default: () => ({ minRows: 2, maxRows: 4 }) },placement: { type: String, default: 'bottom-start' },readonly: { type: Boolean, default: false },popperClass: String,size: {type: String, default: 'default', validator: (value: string) => ['large', 'default', 'small'].includes(value)},singleSelect: Boolean, // 是否单选模式
})const emit = defineEmits(['update:modelValue', 'select', 'open', 'close', 'tab-change'])const inputRef = ref<HTMLElement | null>(null)
const popoverRef = ref<HTMLElement | null>(null)
const visible = ref(false)
const activeTab: any = ref('')const tabs: any = props.tabs || []const hasTabs = computed(() => tabs.length > 0)
const currentGroups = computed(() => {if (hasTabs.value) {const tab = tabs.value.find((t: any) => t.name === activeTab.value)if (tab?.groups) return tab.groupsif (tab?.options) return [{ options: tab.options }]return []}return props.groups.length > 0 ? props.groups : [{ options: props.options }]
})const selectedText = computed({get: () => {if (Array.isArray(props.modelValue)) {return props.modelValue.join(props.separator)}return props.modelValue || ''},set: (val) => emit('update:modelValue', val)
})// 初始化第一个Tab
if (hasTabs.value) {activeTab.value = tabs.value[0].name
}const togglePopup = () => {visible.value = !visible.value
}// 获取选项显示文本
const getOptionLabel = (item: any) => {return item?.label || item?.value || item
}// 检查是否已选中
const isSelected = (item: any) => {const value = item.value || item.label || itemif (Array.isArray(props.modelValue)) {return props.modelValue.includes(value)}return props.modelValue === value
}const handleSelect = (item: any) => {const value = item?.value || item?.label || itemif (props.singleSelect) {// 单选模式emit('update:modelValue', value)} else {// 多选模式if (Array.isArray(props.modelValue)) {const newValue = props.modelValue.includes(value)? props.modelValue.filter(v => v !== value): [...props.modelValue, value]emit('update:modelValue', newValue)} else {const currentValue = props.modelValue || ''if (currentValue.includes(value)) returnconst newValue = currentValue? `${currentValue}${props.separator}${value}`: valueemit('update:modelValue', newValue)}}emit('select', item)if (props.singleSelect) {visible.value = false}
}const handleTabChange = (tab: any) => {activeTab.value = tab.props.nameemit('tab-change', tab.props.name)
}// 处理键盘事件
const handleKeydown = (e: any) => {if (e.key === 'Escape') {visible.value = false}
}onMounted(() => {document.addEventListener('keydown', handleKeydown)
})onUnmounted(() => {document.removeEventListener('keydown', handleKeydown)
})
</script><style scoped lang="scss">
.smart-selector-container {position: relative;display: inline-block;
}.smart-selector-content {padding: 8px;:deep(.el-tabs__header) {margin: 0 0 12px 0;}
}.group-title {padding: 8px 0;font-weight: bold;color: var(--el-color-primary);
}.options-grid {display: flex;flex-wrap: wrap;gap: 8px;padding: 4px 0;
}.option-item {padding: 6px 12px;background: #f5f7fa;border-radius: 4px;cursor: pointer;transition: all 0.2s;white-space: nowrap;font-size: 14px;&:hover {background: var(--el-color-primary);color: white;transform: translateY(-1px);}&.is-selected {background: var(--el-color-primary);color: white;}
}:deep(.el-divider--horizontal) {margin: 12px 0;
}/**使用示例<SmartSelectorv-model="form.symptom":options="options"placeholder="症状/主诉"/>数据传参格式纯字符串格式(最简单)const options = ['头痛', '发热', '咳嗽']简约对象格式const options = [{ label: '头痛' }, { value: '发热' }, '咳嗽' // 混合格式也可以]标准对象格式const options = [{ label: '头痛', value: 'headache' },{ label: '发热', value: 'fever' }]基础用法(分组选项):<template><SmartSelectorv-model="form.symptom":groups="symptomGroups"placeholder="症状/主诉"separator=","multiline:autosize="{ minRows: 2, maxRows: 6 }"@select="handleSelect"/></template><script setup>import { ref } from 'vue'import SmartSelector from '@/components/popup/SmartSelector.vue'const form = ref({symptom: ''})const symptomGroups = ref([{title: '常见症状',options: [{ label: '头痛', value: '头痛' },{ label: '发热', value: '发热' },{ label: '咳嗽', value: '咳嗽' }]},{title: '特殊症状',options: [{ label: '心悸', value: '心悸' },{ label: '气短', value: '气短' },{ label: '胸闷', value: '胸闷' }]}])const handleSelect = (item) => {console.log('选中:', item)}</script>高级用法(带Tab栏):<template><SmartSelectorv-model="form.symptom":tabs="symptomTabs"placeholder="症状/主诉"separator=","width="600px"@select="handleSelect"@tab-change="handleTabChange"/></template><script setup>import { ref } from 'vue'import SmartSelector from '@/components/popup/SmartSelector.vue'const form = ref({symptom: ''})const symptomTabs = ref([{name: 'common',label: '常见症状',options: [{ label: '头痛', value: '头痛' },{ label: '发热', value: '发热' },{ label: '咳嗽', value: '咳嗽' }]},{name: 'special',label: '特殊症状',groups: [{title: '心血管症状',options: [{ label: '心悸', value: '心悸' },{ label: '胸闷', value: '胸闷' }]},{title: '呼吸症状',options: [{ label: '气短', value: '气短' },{ label: '呼吸困难', value: '呼吸困难' }]}]}])const handleSelect = (item) => {console.log('选中:', item)}const handleTabChange = (tabName) => {console.log('切换到Tab:', tabName)}</script>*/
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/77205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言中冒泡排序和快速排序的区别

冒泡排序和快速排序都是常见的排序算法&#xff0c;但它们在原理、效率和应用场景等方面存在显著区别。以下是两者的详细对比&#xff1a; 一、算法原理 1. 冒泡排序 原理&#xff1a;通过重复遍历数组&#xff0c;比较相邻元素的大小&#xff0c;并在必要时交换它们的位置。…

软件信息安全性测试如何进行?有哪些注意事项?

随着信息技术的高速发展&#xff0c;软件已经成为我们生活和工作中不可或缺的一部分。然而&#xff0c;随着软件产品的广泛普及&#xff0c;软件信息安全性问题也日益凸显&#xff0c;因此软件信息安全性测试必不可少。那么软件信息安全性测试应如何进行呢?在进行过程中又有哪…

springboot集成mybaits-generator自动生成代码

文章目录 概述创建springboot项目pom文件aplication.yml代码生成类mybatis-plus提供的变量controller模板mapper模板总结 概述 创建springboot项目&#xff0c;在这里使用的是springboot 2.6.13版本&#xff0c;引入的项目依赖包如pom文件所写&#xff0c;jdk使用1.8&#xff…

数据库脱裤

假设你已经getshell 找到mysql账号密码。 网站要连接mysql&#xff0c;就需要把mysql的账号密码保存在一个php文件中&#xff0c;类似config.php、common.inc.php等&#xff0c;在shell中&#xff0c;读取这些文件&#xff0c;找到其中信息即可 下面是一些常见平台的配置文…

leetcode 337. House Robber III

用动态规划的思想解决这道题。 对于每一个节点&#xff0c;只有两种可能&#xff0c;偷或者不偷。 对于一颗以root为根节点的二叉树&#xff0c;定义rob表示偷root节点能从这棵二叉树偷到的最大金额。定义notrob表示不偷root节点能从这棵二叉树偷到的最大金额。 递推公式分析…

ES和MySQL概念对比

基本概念 ES和MySQL都属于数据库&#xff0c;不过各有各的特性&#xff0c;大致使用方法与MySQL类似并无区别。 MySQL&#xff1a;擅长事务持有ACID的特性&#xff0c;确保数据的一致性和安全。 ES&#xff1a;持有倒排索引&#xff0c;适合海量数据搜索和分析。 ES和MySQL如何…

【python】针对Selenium中弹框信息无法定位的问题,以下是综合解决方案及注意事项:

一、常见原因分析 1.1 弹窗类型不匹配 若弹窗为alert&#xff0c;需使用driver.switch_to.alert处理&#xff1b; 若为confirm或prompt&#xff0c;同样适用该方法。 1.2 窗口句柄切换问题 新窗口或弹窗可能开启新句柄&#xff0c;需先通过driver.window_handles切换到对应句…

欧拉服务器操作系统安装MySQL

1. 安装MySQL服务器​​ 1. 更新仓库缓存 sudo dnf makecache2. 安装MySQL sudo dnf install mysql-server2. 初始化数据库​ sudo mysqld --initialize --usermysql3. 启动数据库服务 # 启动服务 sudo systemctl start mysqld# 设置开机自启 sudo systemctl enable mysql…

SQLark:一款国产免费数据库开发和管理工具

SQLark&#xff08;百灵连接&#xff09;是一款面向信创应用开发者的数据库开发和管理工具&#xff0c;用于快速查询、创建和管理不同类型的数据库系统&#xff0c;目前可以支持达梦数据库、Oracle 以及 MySQL。 对象管理 SQLark 支持丰富的数据库对象管理功能&#xff0c;包括…

Spring Boot 中的自动配置原理

2025/4/6 向全栈工程师迈进&#xff01; 一、自动配置 所谓的自动配置原理就是遵循约定大约配置的原则&#xff0c;在boot工程程序启动后&#xff0c;起步依赖中的一些bean对象会自动的注入到IOC容器中。 在讲解Spring Boot 中bean对象的管理的时候&#xff0c;我们注入bean对…

Mysql8配置文件

Mysql8配置文件 修改my.cnf----配置持久化键(persistence key)配置表名不区分大小写 修改my.cnf----配置持久化键(persistence key) MySQL8初始化数据库之前配置好这些变量值&#xff0c;初始化数据库之后可能无法修改这个值。 # 服务端配置 [mysqld] ######## 数据目录和基…

关于系统架构思考,如何设计实现系统的高可用?

绪论、系统高可用的必要性 系统高可用为了保持业务连续性保障&#xff0c;以及停机成本量化&#xff0c;比如在以前的双十一当天如果出现宕机&#xff0c;那将会损失多少钱&#xff1f;比如最近几年Amazon 2021年30分钟宕机损失$5.6M。当然也有成功的案例&#xff0c;比如异地…

【Unity笔记】实现可视化配置的Unity按键输入管理器(按下/长按/松开事件 + UnityEvent绑定)

【Unity笔记】实现可视化配置的Unity按键输入管理器 适用于角色控制、技能触发的Unity按键输入系统&#xff0c;支持UnityEvent事件绑定、长按/松开监听与启用开关 一、引言 在 Unity 游戏开发中&#xff0c;处理键盘输入是最常见的交互方式之一。尤其是角色控制、技能释放、菜…

Fortran 中使用 C_LOC 和 C_F_POINTER 结合的方法来实现不同类型指针指向同一块内存区域

在 Fortran 中&#xff0c;可以使用 C_LOC 和 C_F_POINTER 结合的方法来实现不同类型指针指向同一块内存区域。以下是具体方法和示例&#xff1a; 关键步骤&#xff1a; 获取内存地址&#xff1a;用 C_LOC 获取原始数组的 C 地址。类型转换&#xff1a;用 C_F_POINTER 将地址转…

Spring Boot整合Kafka的详细步骤

1. 安装Kafka 下载Kafka&#xff1a;从Kafka官网下载最新版本的Kafka。 解压并启动&#xff1a; 解压Kafka文件后&#xff0c;进入bin目录。 启动ZooKeeper&#xff1a;./zookeeper-server-start.sh ../config/zookeeper.properties。 启动Kafka&#xff1a;./kafka-server-…

【含文档+PPT+源码】基于微信小程序的学校体育馆操场预约系统的设计与实现

课程简介&#xff1a; 本课程演示的是一款基于微信小程序的学校体育馆操场预约系统的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从…

【Leetcode-Hot100】最大子数组和

题目 解答 class Solution(object):def maxSubArray(self, nums):""":type nums: List[int]:rtype: int"""len_nums len(nums)result -1e5left_fit, right_fit 0, len_nums-1if len_nums 1:return nums[0]sum_left, sum_right 0, 0while r…

txt、Csv、Excel、JSON、SQL文件读取(Python)

txt、Csv、Excel、JSON、SQL文件读取&#xff08;Python&#xff09; txt文件读写 创建一个txt文件 fopen(rtext.txt,r,encodingutf-8) sf.read() f.close() print(s)open( )是打开文件的方法 text.txt’文件名 在同一个文件夹下所以可以省略路径 如果不在同一个文件夹下 ‘…

硬件电路设计之51单片机(2)

声明&#xff1a;绘制原理图和PCB的软件为嘉立创EDA。根据B站尚硅谷嵌入式之原理图&PCB设计教程学习所作个人用笔记。 目录 一、原理图详解 1、TypeC接口 &#xff08;1&#xff09;TypeC接口介绍 &#xff08;2&#xff09;TypeC原理图 2、5V转3.3V 3、单片机电源开…

kubernetes 入门篇之架构介绍

经过前段时间的学习和实践&#xff0c;对k8s的架构有了一个大致的理解。 1. k8s 分层架构 架构层级核心组件控制平面层etcd、API Server、Scheduler、Controller Manager工作节点层Kubelet、Kube-proxy、CRI&#xff08;容器运行时接口&#xff09;、CNI&#xff08;网络插件&…