jjzjj

基于vant UI设计一个下拉选择跟搜索功能的组件

Kepler_明 2024-06-07 原文

theme: smartblue

简介:

这个是基于 vant3.0跟vue3.0的项目,在开发中封装的一个组件

效果图:



组件功能

1. 组件的下拉框数据进行排序从小到大,分别为四个字体及以下,一行占四个位置,四个字体以上十个字体以下,一行占2个位置均匀分布,超过十个字体,一行占一个位置,本来想过说用padding自动设置数据的宽度,但是效果不太好看,最后选择了这种方法

2.防抖跟节流,这个是在搜索框加的,进行模糊查询,输入字符,结束一秒后没有任何操作在进行请求

3.进行下拉框的遍历,下拉框为一个数组,根剧数据的数据遍历下拉框,以及下拉框的隐藏跟搜索框的隐藏

开发

第一步

首先先将下拉框改造成可以进行循环渲染的

参数:

title: 菜单项标题

menuList: 父组件传来的数据

<template>
     <div
        class="menu-list">
        <van-dropdown-menu>
          <van-dropdown-item
            v-for="(item,index) in menuList"
            :key="item.name"
            :title="self.activeName[index]||item.name"
            :ref="el => { nodes[index] = el }"
          >
              <div>自定义内容</div>
              <div class="dropdown-bottom">
              <van-button
                color="#F8F8F8"
                type="default"
                class="button"
                style="color:rgba(107,107,107,1)"
                @click="onfailed(index)"
              >
                重置
              </van-button>
              <van-button
                type="primary"
                class="button"
                @click="onConfirm(index,item.data)"
              >
                确定
              </van-button>
            </div>
          </van-dropdown-item>
        </van-dropdown-menu>
      </div>
  </template>
  <script>
import { onActivated, reactive, toRefs, ref } from 'vue'
export default {
props: {
    menuList: {
      type: Array,
      default: () => []
    },
  },
  setup (props, ctx) {
      const node = ref({})
      // 关闭的方法
      const onfailed = (index) => {
      // 置空title值
          self.activeName[index] = ''
          nodes.value[index].toggle()
          ctx.emit('onfailed', index)
    }
    // 确定方法
     const onConfirm = (index, item) => {
      if (self.menuValue[index] !== '') {
        const id = self.menuValue[index]
        // 确定以后修改title值
        self.activeName[index] = item[self.menuValue[index]].name
        // 数组的下标以及数据的下标
        const obj = {
          index,
          id
        }
        nodes.value[index].toggle()
        ctx.emit('menuChange', obj)
      }
    }
    return {
      ...toRefs(self),
      nodes,
      onfailed,
      onSelect,
      onConfirm,
      gridList
    }
  }
}
  </script>

第二步

添加搜索框以及搜索框的防抖节流方法

封装防抖节流

//common.js
// 节流函数
export function throttle (fn, wait) {
  // previous是上一次执行fn的时间,timer是定时器
  let previous = 0
  let timer = null
  // 将throttle处理结果当作函数返回
  return function (...args) {
    // 获取当前时间戳
    let now = +new Date()
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔
    if (now - previous < wait) {
      // 如果小于,则为本次触发操作设定一个新的定时器,定时器时间结束后执行函数fn
      if (timer) clearTimeout(timer)
      timer = setTimeout(() => {
        previous = now
        fn.apply(this, args)
      }, wait)
    } else {
      // 第一次执行或者时间间隔超出了设定的时间间隔,执行函数fn
      previous = now
      fn.apply(this, args)
    }
  }
}
// 防抖
export function debounce (fun, delay) {
  let t = ''
  return (args) => {
    let that = this
    let _args = args
    clearTimeout(t)
    t = setTimeout(function () {
      fun.call(that, _args)
    }, delay)
  }
}

添加搜索框

参数:

search: 判断搜索框是否显示

searchValue: 搜索框的值

formatter: 搜索的值去除前后空格的方法

placeholder: 占位符的值

onSearch: 确定搜索时触发的事件

updateSearch: 字符变动时触发的事件

<template>
<van-search
  v-if="search"
  v-model="searchValue"
  autocomplete="off"
  class="search"
  :formatter="formatter"
  :placeholder="placeholder"
  @search="onSearch"
  @update:model-value="updateSearch"
/>
</template>
<script>
import { onActivated, reactive, toRefs, ref } from 'vue'
// 引入刚才写的节流跟防抖函数
import { throttle, debounce } from '@/utils/common.js'
export default {
    props: {
        placeholder: {
          type: String,
          default: ''
        }
        search: {
          type: Boolean,
          default: true
        }
     }
     const self = reactive({
      searchValue: '',
     })
       const formatter = (val) => {
      return val.trim()
    }
    /**
     * @description: 点击搜索或者确定的键盘触发
     * @param {*} val 当前的值
     * @return {*}
     */
    const onSearch = throttle((val) => {
      ctx.emit('updateSearch', val)
    }, 1000)
    /**
     * @description: 搜索的触发
     * @param {*} item
     * @return {*}
     */
    const updateSearch = debounce((item) => {
      ctx.emit('updateSearch', item)
    }, 1000)
    return {
      ...toRefs(self),
      updateSearch,
      onSearch,
      formatter
    }
}
</script>

结尾

这个搜索框以及下拉其实就是,在选中以后将当前选中的对象的下标以及对象里边渲染的下标传给父组件,父组件进行筛选获得当前选中的值,进行下一步操作,简洁了每次需要下拉的时候写一大堆重复代码,并且搜索的时候模增加了防抖与节流
附上组件代码

<template>
  <div class="container-view">
    <div class="search-menu">
      <div
        v-if="menu"
        class="menu-list"
      >
        <van-dropdown-menu>
          <van-dropdown-item
            v-for="(item,index) in menuList"
            :key="item.name"
            :ref="el => { nodes[index] = el }"
            :disabled="disabled"
            :title-class="activeName[index] !== ''?'title-active':''"
            :title="activeName[index] || item.name"
          >
            <div class="status-list">
              <div
                v-for="(v,i) in item.data"
                :key="i"
                :class="menuValue[index] === i?`${gridList(v.name)} list status`:`${gridList(v.name)} list`"
                @click="onSelect(index,i)"
              >
                <span>
                  {{ v.name }}
                </span>
              </div>
            </div>
            <div class="dropdown-bottom">
              <van-button
                color="#F8F8F8"
                type="default"
                class="button"
                style="color:rgba(107,107,107,1)"
                @click="onfailed(index)"
              >
                重置
              </van-button>
              <van-button
                type="primary"
                class="button"
                @click="onConfirm(index,item.data)"
              >
                确定
              </van-button>
            </div>
          </van-dropdown-item>
        </van-dropdown-menu>
      </div>

      <van-search
        v-if="search"
        v-model="searchValue"
        autocomplete="off"
        class="search"
        :formatter="formatter"
        :placeholder="placeholder"
        @search="onSearch"
        @update:model-value="updateSearch"
      />
    </div>
  </div>
</template>

<script>
import { onActivated, onDeactivated, reactive, toRefs, ref, watch } from 'vue'
import { throttle, debounce } from '@/utils/common.js'
export default {
  name: 'MenuSearch',
  props: {
    placeholder: {
      type: String,
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    },
    menu: {
      type: Boolean,
      default: true
    },
    search: {
      type: Boolean,
      default: true
    },
    // 这里使用的时候将默认值去掉,这里只是说明需要什么样子的数据
    menuList: {
      type: Array,
      default: () =>  [
  {
    name: '楼栋类型',
    data: [
      {
        value: '居住小区',
        name: '居住小区'
      },
      {
        value: '城中村',
        name: '城中村'
      },
      {
        value: '其他',
        name: '其他'
      }
    ]
  },
  {
    name: '使用用途',
    data: [
      {
        value: 1,
        name: '综合'
      },
      {
        value: 2,
        name: '住宅'
      },
      {
        value: 3,
        name: '商住'
      },
      {
        value: 4,
        name: '商业'
      },
      {
        value: 5,
        name: '厂房'
      },
      {
        value: 6,
        name: '仓库'
      },
      {
        value: 7,
        name: '办公'
      },
      {
        value: 8,
        name: '其他'
      },
      {
        value: 9,
        name: '公共设施'
      }
    ]
  }
]
    }
  },
  setup (props, ctx) {
    const nodes = ref({})
    const self = reactive({
      searchValue: '',
      timeer: null,
      flg: false,
      menuValue: [],
      activeMenu: [],
      activeName: ['', ''],
      list: props.menuList
    })
    const formatter = (val) => {
      return val.trim()
    }
    /**
     * @description: 点击搜索或者确定的键盘触发
     * @param {*} val 当前的值
     * @return {*}
     */
    const onSearch = throttle((val) => {
      ctx.emit('updateSearch', val)
    }, 1000)
    /**
     * @description: 搜索的触发
     * @param {*} item
     * @return {*}
     */
    const updateSearch = debounce((item) => {
      ctx.emit('updateSearch', item)
    }, 1000)
    /**
     * @description: onfailed 清空
     * @param {*} index 当前数组的下标
     * @return {*}
     */
    const onfailed = (index) => {
      self.activeName[index] = ''
      nodes.value[index].toggle()
      self.menuValue[index] = ''
      self.activeMenu[index] = ''
      ctx.emit('onfailed', index)
    }
    // 遍历获取元素,然后放在一个数组里
    /**
     * @description: onSelect 选择
     * @param {*} index 当前数组的下标
     * @param {*} i 当前选择的数
     * @return {*}
     */
    const onSelect = (index, i) => {
      self.menuValue[index] = i
      self.activeMenu[index] = i
    }
    /**
     * @description: onConfirm 确定方法
     * @param {*} index 当前数组的下标
     * @param {*} item 当前选择的数的数组
     * @return {*}
     */
    const onConfirm = (index, item) => {
      if (self.menuValue[index] !== '') {
        const id = self.menuValue[index]
        self.activeName[index] = item[self.menuValue[index]].name
        const obj = {
          index,
          id
        }
        nodes.value[index].toggle()
        ctx.emit('menuChange', obj)
      }
    }
    /**
     * @description: 动态修改宽
     * @param {*} value 当前显示的字符串
     * @return {*}
     */
    const gridList = (value) => {
      const length = value.replace(/[^\u4e00-\u9fa5]/gi, '').length
      return length <= 4 ? 'text-four' : length <= 10 ? 'text-two' : 'text-one'
    }
    onActivated(() => {
      self.searchValue = localStorage.getItem('searchValue') || ''
      self.activeName = localStorage.getItem('activeName') ? JSON.parse(localStorage.getItem('activeName')) : ['', '']
      props.menuList.forEach(v => {
        const obj = v
        obj.data.sort((a, b) => {
          return a.name.length - b.name.length
        })
        // return obj
      })
      self.menuValue = localStorage.getItem('activeName') ? self.activeMenu : []
    })
    onDeactivated(() => {
      localStorage.setItem('searchValue', self.searchValue)
      localStorage.setItem('activeName', JSON.stringify(self.activeName))
      self.menuValue = []
    })
    watch(() => props.menuList, (newValue) => {
      if (newValue.length > 0 && newValue) {
        props.menuList.forEach(v => {
          const obj = v
          obj.data.sort((a, b) => {
            return a.name.length - b.name.length
          })
          // return obj
        })
      }
    })
    return {
      ...toRefs(self),
      nodes,
      updateSearch,
      onSearch,
      formatter,
      onfailed,
      onSelect,
      onConfirm,
      gridList
    }
  }
}

</script>
<style scoped lang='less'>
.container-view {
  width: 100%;
  .search-menu {
    width: 100%;
    height: 48px;
    background: #ffffff;
    border-radius: 16px 16px 0px 0px;
    display: flex;
    align-items: center;
    // justify-content: space-around;
    padding: 0 12px;
    .menu-list {
      display: flex;
      height: 100%;
      .status-list {
        display: flex;
        background: #f8f8f8;
        align-content: flex-start;
        flex-wrap: wrap;
        overflow: auto;
        row-gap: 10px;
        min-height: 0px;
        max-height: 178px;
        padding-bottom: 10px;
        &::-webkit-scrollbar {
          display: none;
        }

        .list {
          // padding: 0px 0 16px 14px;
          display: flex;
          align-items: center;
          justify-content: center;
          background: #ffffff;
          border-radius: 2px;
          font-size: 14px;
          color: #666666;
          letter-spacing: 0;
          font-weight: 400;
          display: flex;
          align-items: center;
          white-space: nowrap;
          height: 29px;
          margin-top: 10px;
          &.status {
            // span {
            color: #ffffff;
            background: #3388ff;
            border-radius: 2px;
            // }
          }
          &.text- {
            &one {
              width: 90%;
              margin-left: 5%;
              margin-right: 5%;
            }
            &two {
              width: calc(100% / 2 - 20px);
              margin: 10px 10px 0 10px;
            }
            &four {
              width: calc(100% / 4 - 20px);
              margin: 10px 10px 0 10px;
            }
          }
        }
      }
      .dropdown-bottom {
        padding: 10px 12px;
        display: flex;
        justify-content: space-between;
        .button {
          width: 169px;
          height: 46px;
          font-size: 16px;
          letter-spacing: 0;
          font-weight: 400;
        }
      }
      .van-dropdown-item {
        top: 168px !important;
        position: fixed;
      }

      :deep(.van-dropdown-menu) {
        display: flex;
        align-items: center;

        .van-dropdown-menu__bar {
          .van-dropdown-menu__title {
            &.title-active {
              color: #327ee7;
            }
          }
        }

        .van-dropdown-menu__bar {
          height: 28px;

          .van-dropdown-menu__item {
            width: 90px;
            margin-right: 6px;
            background: #f2f2f2 !important;
             border-radius: 4px;
          }
          .van-dropdown-menu__title {
            &--active {
              color: #3388ff;
              &:after {
                border-color: transparent transparent #3388ff #3388ff !important;
              }
            }
            .van-ellipsis {
              width: 60px;
            }
            &:after {
              right: 0px;
            }
            padding: 0 10px;
            font-size: 14px;
            color: #666666;
            letter-spacing: 0;
            font-weight: 400;
          }
        }
      }
    }

    :deep(.search) {
      background: #f2f2f2;
      border-radius: 4px;
      flex: 1;
      height: 28px;
      padding: 0 10px;
      .van-search__content {
        width: 100%;
        padding: 0;
      }
      .van-cell {
        height: 100%;
        padding: 0;
      }
    }
  }
}
</style>

有关基于vant UI设计一个下拉选择跟搜索功能的组件的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  7. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby-on-rails - Nokogiri:使用 XPath 搜索 <div> - 2

    我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll

  10. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

随机推荐