VUE工具之“vue3+ts的多选框搜索组件”

一、vue3+ts的多选框搜索组件

【ProductSKU】产品SKU查询使用【多个搜索】组件

<template>
  <MultipleSearch @search="handleSearch"/>
</template>
<script setup lang="ts">
import { defineEmits } from 'vue'
import { MultipleSearch } from '@/components/ProductSKU'
import { ProductApi } from "@/api/product/product";

const emit = defineEmits(['search'])

const handleSearch = async (skuList) => {
  try {
    const data = await ProductApi.getProductPage({skuKeywordList:skuList})
    emit('search', data)
  }
  catch (e) {
    console.error(e)
  }
}
</script>

<style scoped lang="scss">
</style>

index.ts路由指定

import MultipleSearch from './src/MultipleSearch.vue'
import ProductSKU from './src/ProductSKU.vue'

export { MultipleSearch, ProductSKU }

 

【MultipleSearch】多个搜索组件

<template>
  <el-popover
    :visible="isShowDropdown"
    class="box-item"
    placement="bottom-end"
    popper-style="width: auto;"
  >
    <template #reference>
      <el-button :class="{'is-active': isShowDropdown}" @click="handleShowDropdown">
        <el-icon><More /></el-icon>
      </el-button>
    </template>
    <div class="popover-container">
      <el-scrollbar height="300px">
        <div class="input-box">
          <div class="input-item">
            <div class="label">{{itemsLength}}</div>
            <el-input
              v-model="textareaValue"
              :autosize="true"
              type="textarea"
              placeholder="精确搜索,一行一项,最多支持2000行"
              @input="handleInput"
            />
          </div>
        </div>
      </el-scrollbar>
      <div class="action-box">
        <el-button @click="clearAll">清空</el-button>
        <el-button class="btn-close" @click="closePopover">关闭</el-button>
        <el-button @click="handleSearch">搜索</el-button>
      </div>
    </div>
  </el-popover>
</template>

<script setup lang="ts">
import { ref, onMounted, defineProps, defineEmits, computed } from 'vue'
import { More } from '@element-plus/icons-vue'
interface Props {
  // 每项的分隔符
  delimiters?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  delimiters: () => [',', '\n']
})

// 2. 明确 Emits 类型定义(指定事件参数类型)
const emit = defineEmits<{
  /** 搜索事件,触发时返回去重后的数组 */
  (e: 'search', value: string[]): void
}>()

// 3. 状态变量补充类型定义
const isShowDropdown = ref<boolean>(false) // 下拉框显示状态
const textareaValue = ref<string>('') // 文本框输入值
const convertedList = ref<string[]>([]) // 分隔后去重的数组

// 4. 计算属性:有效项数(过滤空字符串)
const itemsLength = computed((): number => {
  return convertedList.value.filter(item => item.trim() !== '').length
})

// 5. 工具函数:创建分隔符正则表达式(避免重复创建)
const createDelimiterRegExp = (delimiters: string[]): RegExp => {
  // 转义特殊正则字符(如 . * + ? 等),避免正则语法错误
  const escapedDelimiters = delimiters.map(d => d.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
  return new RegExp(`[${escapedDelimiters.join('|')}]+`, 'g')
}

// 6. 核心逻辑:将文本框值按分隔符转换为去重数组
const convertValueToList = (value: string): string[] => {
  const delimiterReg = createDelimiterRegExp(props.delimiters ?? [',', '\n'])
  // 步骤1:用正则替换所有分隔符为逗号
  // 步骤2:去除首尾逗号
  // 步骤3:按逗号分割为数组
  // 步骤4:去重(保持原始顺序)
  const rawList = value.replace(delimiterReg, ',').replace(/^,|,$/g, '').split(',')
  return Array.from(new Set(rawList)) // 去重并保持数组类型
}

// 7. 事件处理:文本框输入变化时更新数组
const handleInput = (): void => {
  convertedList.value = convertValueToList(textareaValue.value)
}

// 8. 事件处理:清空所有内容
const clearAll = (): void => {
  textareaValue.value = ''
  convertedList.value = []
}

// 9. 事件处理:关闭下拉框并清空
const closePopover = (): void => {
  isShowDropdown.value = false
  clearAll()
}
const handleShowDropdown =(): void=>{
  isShowDropdown.value = !isShowDropdown.value
}
// 10. 事件处理:触发搜索(传递去重后的有效数组)
const handleSearch = (): void => {
  // 过滤空字符串,只传递有效项
  const validList = convertedList.value.filter(item => item.trim() !== '')
  emit('search', validList)
  closePopover()
}

// 11. 生命周期:组件挂载后初始化(可根据需求添加逻辑)
onMounted(() => {
  // 示例:初始化时若有默认值,可在这里处理
  // convertedList.value = convertValueToList(textareaValue.value)
})
</script>

<style scoped lang="scss">
.el-button {
  padding: 0 5px;
  margin: 0;
  &.is-active,
  &:hover {
    background-color: #c5e0fa;
    color: #0655d5;
  }
}

.popover-container {
  width: 350px;
  .input-box {
    padding-top: 10px;
    .input-item {
      position: relative;
      margin-bottom: 10px;
      .label {
        position: absolute;
        top: 0;
        left: 10px;
        transform: translateY(-50%);
        font-size: 10px;
        z-index:1;
        color: #999;
        background-color: #fff;
        padding: 0 5px;
      }
    }
  }
  .action-box {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0 0;
    box-sizing: border-box;
    border-top: 1px solid #e0e0e0;
    .el-button{
      margin: 0;
    }
    .btn-close {
      margin-left: auto;
    }
  }
}
</style>

 

posted @ 2025-09-11 19:37  骚哥  阅读(27)  评论(0)    收藏  举报