用vue2+elementUI封装手机端选择器picker组件,支持单选、多选、远程搜索多选

news2024/10/6 9:21:58

单选注意点:

  1. @touchmove.prevent: 在 touchmove 事件上添加 .prevent 修饰符,以阻止默认的滚动行为。

  2. handleTouchStart: 记录触摸开始的 Y 坐标和当前的 translateY 值。

  3. handleTouchMove: 计算触摸移动的距离,并更新 translateY 值。

  4. handleTouchEnd: 根据 translateY 计算当前选中的索引,并更新 translateY 值。

  5. handleCancel: 触发取消事件。

  6. handleConfirm: 触发确认事件,并传递当前选中的选项。

  7. clampTranslateY: 确保 translateY 值在合理范围内。

多选注意点:

  1. clickItem: 为选中的选项改变样式

  2. handleConfirm: 触发确认事件,并传递当前选中的选项。

远程搜索多选注意点:

     1. 单独给el-popper设置样式,发现无效,原因是el-popper和<div id="app">...</div>组件处于同一层级,解决方法是使用popper-class属性给el-popper定义一个class,另外在style中去掉scoped。

效果如下:

        

单选/多选picker.vue组件:

<template>
  <div>
    <div class="picker-mask"></div>
    <div
      class="picker"
      @touchstart="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchmove.prevent="handleTouchMove"
      @touchend="handleTouchEnd"
    >
      <div class="picker-actions">
        <el-button type="text" @click="handleCancel">取消</el-button>
        <el-button type="text" @click="handleConfirm">确认</el-button>
      </div>
      <div class="picker-box" v-if="chooseOptions.length">
        <div
          class="picker-content"
          :style="{ transform: `translateY(${translateY}px)` }"
        >
          <div
            v-for="(item, index) in chooseOptions"
            :key="index"
            :class="item.chooseFlag ? 'choose-item' : 'picker-item'"
            @click="multiple ? clickItem(index) : null"
          >
            {{ labelKey ? item[labelKey] : item }}
          </div>
        </div>
      </div>
      <div class="empty" v-else>暂无数据</div>
      <div
        v-if="chooseOptions.length && !multiple"
        class="picker-highlight"
      ></div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    options: {
      type: Array,
      default: () => []
    },
    labelKey: {
      type: String,
      default: ''
    },
    selectedOption: {
      type: [Object, Array],
      default: () => []
    },
    multiple: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      startY: 0,
      translateY: 40,
      currentIndex: 0,
      startTranslateY: 40,
      chooseOptions: this.multiple
        ? this.options?.map(v => ({ ...v, chooseFlag: false })) || []
        : this.options || []
    };
  },

  mounted() {
    if (this.multiple && this.selectedOption?.length) {
      console.log('selectedOption', this.selectedOption);
      console.log(this.options);
      this.chooseOptions =
        this.options?.map(v => ({
          ...v,
          chooseFlag: this.selectedOption?.some(
            item => item[this.labelKey] === v[this.labelKey]
          )
        })) || [];
    }
    // 根据选项列表和当前选中项,设置当前索引和滚动位置
    if (!this.multiple && this.options.indexOf(this.selectedOption) !== -1) {
      this.currentIndex = this.options.indexOf(this.selectedOption);
      this.translateY = -40 * this.currentIndex + 40;
    }
  },
  methods: {
    // 记录触摸开始的 Y 坐标和当前的 translateY 值
    handleTouchStart(event) {
      this.startY = event.touches[0].clientY;
      this.startTranslateY = this.translateY ?? 0;
    },
    // 计算触摸移动的距离,并更新 translateY 值。
    handleTouchMove(event) {
      const deltaY = event.touches[0].clientY - this.startY;
      this.translateY = this.startTranslateY + deltaY;
      this.clampTranslateY();
    },
    // 根据 translateY 计算当前选中的索引,并更新 translateY 值
    handleTouchEnd() {
      const index = Math.round(this.translateY / 40);
      this.translateY = index * 40;
      this.currentIndex = -Math.round((this.translateY - 40) / 40);
    },
    // 确保 translateY 值在合理范围内
    clampTranslateY() {
      const itemHeight = 40;
      const maxTranslateY = 40;
      const minTranslateY =
        maxTranslateY - (this.options.length - 1) * itemHeight;
      this.translateY = Math.max(
        minTranslateY,
        Math.min(maxTranslateY, this.translateY)
      );
    },
    clickItem(index) {
      this.chooseOptions[index].chooseFlag =
        !this.chooseOptions[index].chooseFlag;
    },
    handleCancel() {
      console.log('cancel');
      this.$emit('cancel');
    },
    handleConfirm() {
      const result = this.multiple
        ? this.chooseOptions?.filter(v => v.chooseFlag)
        : this.chooseOptions?.[this.currentIndex];
      this.$emit('confirm', result);
    }
  }
};
</script>

<style scoped>
.picker-mask {
  z-index: 2014;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.7);
  border-radius: 8px;
}
.picker {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 200px;
  overflow: hidden;
  background-color: #fff;
  z-index: 2014;
  color: #323233;
  font-size: 16px;
  border-radius: 8px;
}
.picker-box {
  flex: 1;
  overflow: hidden;
}

.picker-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform 0.3s ease;
}

.picker-item {
  height: 40px;
  line-height: 40px;
  text-align: center;
  width: 100%;
}

.picker-highlight {
  position: absolute;
  top: 60%;
  left: 0;
  width: 100%;
  height: 40px;
  transform: translateY(-50%);
  background-color: rgba(255, 255, 255, 0.7);
  border-top: 1px solid #ccc;
  border-bottom: 1px solid #ccc;
}

.picker-actions {
  width: 100%;
  display: flex;
  justify-content: space-between;
  padding: 10px 0;
  z-index: 2024;
  background-color: #fff;

  .el-button {
    margin: 0 8px;
  }
}

.choose-item {
  height: 40px;
  line-height: 40px;
  text-align: center;
  color: #2c68ff;
  width: 100%;
}

.choose-item::after {
  position: absolute;
  right: 20px;
  font-family: 'element-icons';
  content: '';
  font-size: 12px;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
}

.empty {
  text-align: center;
}
</style>

远程搜索多选picker组件,可以增加远程搜索属性,自己改造使用:

<template>
  <div>
    <div class="picker-mask"></div>
    <div class="picker">
      <div class="picker-actions">
        <el-button type="text" @click="handleCancel">取消</el-button>
        <el-button type="text" @click="handleConfirm">确认</el-button>
      </div>
      <div class="picker-box">
        <el-select
          collapse-tags
          filterable
          multiple
          value-key="id"
          default-first-option
          :clearable="true"
          v-model="selectList"
          placeholder="请输入"
          popper-class="select-popper"
          @change="handleChange"
        >
          <el-option
            v-for="item in userSuccessorNowList"
            :key="item.id"
            :value="item"
            :label="item.vname"
          />
        </el-select>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    selectedOption: {
      type: Array,
      default: () => []
    },
    labelKey: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      selectList: [],
      userSuccessorNowList: [
        { id: 1, vname: 'aaa' },
        { id: 2, vname: 'bbb' },
        { id: 3, vname: 'ccc' },
        { id: 4, vname: 'aaa' },
        { id: 5, vname: 'bbb' },
        { id: 6, vname: 'ccc' }
      ]
    };
  },

  mounted() {
    if (this.selectedOption?.length) {
      console.log('selectedOption', this.selectedOption);
      this.selectList = this.selectedOption;
    }
  },
  methods: {
    handleChange(val) {
      this.selectList = val;
      console.log('selectList', this.selectList);
    },
    handleCancel() {
      this.$emit('cancel');
    },
    handleConfirm() {
      this.$emit('confirm', this.selectList);
    }
  }
};
</script>
<style>
.select-popper {
  width: 100vw !important;
  z-index: 2014 !important;
  left: 0 !important;
  box-shadow: none;
  height: 120px !important;
  overflow: auto;
  .popper__arrow {
    display: none !important;
  }
  .el-scrollbar__view {
    text-align: center;
  }
}
</style>

<style scoped>
.picker-mask {
  z-index: 1014;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.7);
  border-radius: 8px;
}
.picker {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 250px;
  overflow: hidden;
  background-color: #fff;
  z-index: 1014;
  color: #323233;
  font-size: 16px;
  border-radius: 8px;

  .el-select {
    width: 100%;
    .el-input__suffix {
      display: none;
    }
  }
}

.picker-actions {
  width: 100%;
  display: flex;
  justify-content: space-between;
  padding: 10px 0;
  z-index: 2024;
  background-color: #fff;

  .el-button {
    margin: 0 8px;
  }
}

.picker-box {
  flex: 1;
  overflow: hidden;
}
</style>

选择器父组件:

<template>
  <div>
    <div class="select-box" @click="togglePicker">
      <div class="select-content">
        <div class="select-label">
          <span class="label">
            {{ label }}
            <span v-if="required" style="color: rgba(253, 75, 76, 1)"> * </span>
          </span>
        </div>
        <span
          class="select-value"
          :style="{ color: `${!selectedOption ? 'rgba(0,0,0,0.25)' : ''}` }"
          >{{ selectedOption ? showResults(selectedOption) : placeholder }}
        </span>
      </div>
      <i class="el-icon-arrow-right"></i>
    </div>
    <div v-if="showPicker">
      <SearchPicker
        v-if="pickerType === 'search'"
        :options="options"
        :labelKey="labelKey"
        @confirm="handleConfirm"
        @cancel="handleCancel"
        :selectedOption="selectedOption"
      />
      <Picker
        v-else
        :options="options"
        :labelKey="labelKey"
        @confirm="handleConfirm"
        @cancel="handleCancel"
        :selectedOption="selectedOption"
        :multiple="multiple"
      />
    </div>
  </div>
</template>

<script>
import Picker from '../Picker';
import SearchPicker from '../SearchPicker';

export default {
  components: {
    Picker,
    SearchPicker
  },
  props: {
    label: {
      type: String,
      default: ''
    },
    pickerType: {
      type: String,
      default: ''
    },
    required: {
      type: Boolean,
      default: false
    },
    options: {
      type: Array,
      default: () => []
    },
    labelKey: {
      type: String,
      default: ''
    },
    placeholder: {
      type: String,
      default: '请选择'
    },
    multiple: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      selectedOption: null,
      showPicker: false
    };
  },

  mounted() {
    this.getResults();
  },

  methods: {
    getResults() {
      if (this.multiple) {
        this.selectedOption = this.selectedOption || null;
      } else {
        this.selectedOption = this.selectedOption?.[this.labelKey];
      }
    },
    showResults(selectedOption) {
      if (this.multiple) {
        return selectedOption.map(v => v[this.labelKey]).join(',');
      } else {
        return selectedOption;
      }
    },
    togglePicker() {
      this.showPicker = !this.showPicker;
    },
    handleConfirm(selectedOption) {
      if (this.multiple) {
        this.selectedOption = selectedOption?.length ? selectedOption : null;
      } else {
        this.selectedOption = selectedOption;
      }
      this.showPicker = false;
      this.getResults();
    },
    handleCancel() {
      this.showPicker = false;
    }
  }
};
</script>

<style lang="scss" scoped>
.select-box {
  width: 100%;
  height: 48px;
  line-height: 40px;
  text-align: left;
  background-color: #fff;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;

  .select-content {
    display: flex;
    align-items: center;
    .select-label {
      width: 90px;
    }
    .select-value {
      @include textElipsis(1);
      width: calc(100% - 90px);
    }
  }

  span {
    font-family: PingFangSC, PingFang SC;
    font-weight: 400;
    font-size: 16px;
    color: rgba(0, 0, 0, 0.85);
    line-height: 24px;
    text-align: left;
    font-style: normal;
    text-transform: none;
  }
  i {
    color: rgba(0, 0, 0, 0.45);
  }
}
</style>

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

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

相关文章

「技术分享」FDL对接金蝶云API取数

很多企业的ERP系统都在用金蝶云星空&#xff0c;金蝶云星空API是IT人员获取数据的重要来源&#xff0c; 常常用来生成定制化报表&#xff0c;进行数据分析&#xff0c;或是将金蝶云的数据与OA系统、BI工具集成。 通常情况下&#xff0c;IT人员需要使用Python、Java等语言编写脚…

【机器学习】(基础篇一) —— 什么是机器学习

什么是机器学习 本系列博客为你从机器学习的介绍开始&#xff0c;使用大量的代码实战和验证&#xff0c;最终帮助你完全掌握什么是机器学习 人工智能、机器学习和深度学习的关系 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;&#xff1a;是一门研…

algorithm算法库学习之——不修改序列的操作

algorithm此头文件是算法库的一部分。本篇介绍不修改序列的操作函数。 不修改序列的操作 all_ofany_ofnone_of (C11)(C11)(C11) 检查谓词是否对范围中所有、任一或无元素为 true (函数模板) for_each 应用函数到范围中的元素 (函数模板) for_each_n (C17) 应用一个函数对象到序…

上位机图像处理和嵌入式模块部署(mcu项目1:假设用51单片机实现)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 到目前位置&#xff0c;我们借助于qt和apm32 mcu芯片&#xff0c;实现了大多数功能。大家如果回过头来看&#xff0c;其实这些功能并不复杂。从固件…

Java(七)——多态

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

Arcgis Api 三维聚合支持最新版API

Arcgis Api 三维聚合支持最新版API 最近有同学问我Arcgis api 三维聚合&#xff0c;官方还不支持三维聚合API&#xff0c;二维可以。所以依旧是通过GraphicLayers 类来实现&#xff0c;可支持最新Arcgis Api版本 效果图&#xff1a;

【APK】SDKManager运行后闪退

本地JDK已安装&#xff0c;且配置了环境变量&#xff0c;未安装 android studiio 问题描述&#xff1a;右键以管理员身份运行 SDKManager&#xff0c;终端窗口闪退 问题原因&#xff1a;未找到正确的Java路径 解决办法&#xff1a; 1.修改tools目录下的 android.bat 文件&am…

0301STM32GPIO外设输出

STM32GPIO外设输出 STM32内部的GPIO外设GPIO简介基本结构GPIO位结构输入部分&#xff1a;输出部分&#xff1a; GPIO八种工作模式浮空/上拉/下拉输入模拟输入开漏/推挽输出复用开漏/推挽输出 手册寄存器描述GPIO功能描述外设的GPIO配置GPIO寄存器描述端口输入数据寄存器端口输出…

Kafka第四篇——生产数据总体概括,源码解析分区策略,数据收集器,Sender发送线程,key值

目录 流程图以及总体概述 拦截器 分区器以及分区计算策略 为啥进行分区计算&#xff1f; producer生产者怎么知道有哪些分区&#xff1f; 分区计算 如何自定义实现分区器&#xff1f; 想说的在图里啦&#xff01;宝宝&#xff01;&#x1f4a1; ​编辑 如果key值忘记传递了呢&a…

Vue移动端地图App:van-uploader导致的卡顿问题

问题描述 基于Vue3+Vant IU 4开发的移动端地图App,在进行地图点位上报、上报记录查看过程中,出现App卡顿、甚至闪退的问题,进行问题定位之后,发现是van-uploader组件导致的问题。 van-uploader文件上传组件 van-uploader组件用于将本地的图片或文件上传至服务器,并在上传…

园区、社区、乡村的智能管理

智慧园区、社区、乡村管理系统是现代信息技术在城市化进程中的重要应用,它们通过集成多种技术手段,实现对园区、社区、乡村的全面、高效、智能化管理。以下是对这三种管理系统的详细阐述: 一、智慧园区管理系统 1. 定义与目的 智慧园区管理系统是运用物联网、云计算、大数…

Canal架构以及使用规范

Canal架构以及使用规范 一、Canal的作用 相关文档&#xff1a;GitHub - alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&消费组件 MySQL主备复制原理 MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events&#xff0c;可…

【做一道算一道】和为 K 的子数组

给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2 示例 2&#xff1a; 输入&#xff1a;nums [1,2,3],…

分布式整合

一、分布式架构介绍 什么是分布式系统 分布式系统指一个硬件或软件组件分布在不同的网络计算机上&#xff0c;彼此之间仅仅通过消息传递进行通信和协调的系统。 通俗的理解&#xff0c;分布式系统就是一个业务拆分成多个子业务&#xff0c;分布在不同的服务器节点&#xff0…

【数据结构与算法】快速排序霍尔版

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​

找不到x3daudio1_7.dll怎么修复?一招搞定x3daudio1_7.dll丢失问题

当你的电脑突然弹出提示&#xff0c;“找不到x3daudio1_7.dll”&#xff0c;这时候你就需要警惕了。这往往意味着你的电脑中的程序出现了问题&#xff0c;你可能会发现自己无法打开程序&#xff0c;或者即便打开了程序也无法正常使用。因此&#xff0c;接下来我们要一起学习一下…

【简单介绍下Memcached】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

LabVIEW从测试曲线中提取特征值

在LabVIEW中开发用于从测试曲线中提取特征值的功能时&#xff0c;可以考虑以下几点&#xff1a; 数据采集与处理&#xff1a; 确保你能够有效地采集和处理测试曲线数据。这可能涉及使用DAQ模块或其他数据采集设备来获取曲线数据&#xff0c;并在LabVIEW中进行处理和分析。 特…

Wormhole Filters: Caching Your Hash on Persistent Memory——泛读笔记

EuroSys 2024 Paper 论文阅读笔记整理 问题 近似成员关系查询&#xff08;AMQ&#xff09;数据结构可以高效地近似确定元素是否在集合中&#xff0c;例如Bloom滤波器[10]、cuckoo滤波器[23]、quotient滤波器[8]及其变体。但AMQ数据结构的内存消耗随着数据规模的增长而快速增长…

Kubernetes集群性能测试之kubemark集群搭建

Kubernetes集群性能测试之kubemark集群搭建 Kubemark是K8s官方提供的一个对K8s集群进行性能测试的工具。它可以模拟出一个K8s cluster&#xff08;Kubemark cluster&#xff09;&#xff0c;不受资源限制&#xff0c;从而能够测试的集群规模比真实集群大的多。这个cluster中ma…