H5选择器组件,支持分页加载选项,支持模糊搜索

196 阅读2分钟

选择器选项太多时,需要分页加载选项,且需要支持模糊搜索,目前Vant UI 提供的选择器组件不支持,因此封装一个H5选择器组件。

image.png

组件封装

<template>
  <div class="picker-by-paging">
    <van-popup
      v-model:show="props.show"
      round
      position="bottom"
      style="height: 95%"
      :close-on-click-overlay="false"
    >
      <div class="picker-hidder">
        <div @click="onCancel">取消</div>
        <div class="title">{{ pickerTitle }}</div>
        <div @click="onConfirm" style="color: #0267ff">确定</div>
      </div>
      <van-search
        v-model="searchValue"
        show-action
        @search="search"
        v-if="showSearch"
      >
        <template #action>
          <div @click="search">搜索</div>
        </template>
      </van-search>
      <div v-else style="height: 15px"></div>
      <div class="picker-content" :class="{ 'no-search': !showSearch }">
        <van-list
          v-model:loading="loading"
          :finished="finished"
          finished-text="没有更多了"
          @load="getData"
        >
          <van-cell
            v-for="(item, index) in list"
            :key="index"
            :title="item.text"
            class="picker-item"
            :class="{ active: item.value === selected?.value }"
            @click="handleSelect(item)"
          >
          </van-cell>
        </van-list>
      </div>
    </van-popup>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { ITextValue } from "@/type/base";
import { useRequest } from "@xus/vue-reuse";
import { Toast } from "vant";
interface props {
  show: boolean;
  selectedItem?: ITextValue | null;
  pickerTitle?: string;
  showSearch?: boolean;
  initParams?: any;
  pageSize?: number;
  searchKey?: string;
  getListApi: (any) => any;
}
const props = withDefaults(defineProps<props>(), {
  show: false,
  selectedItem: null,
  pickerTitle: "",
  showSearch: true,
  initParams: {},
  pageSize: 20,
  searchKey: "",
  getListApi: () => Promise.resolve([]),
});
const emit = defineEmits(["update:show", "on-confirm"]);
const searchValue = ref("");
const list = ref<ITextValue[]>([]);
const loading = ref(false);
const finished = ref(false);
const page = ref({
  pageNum: 1,
  pageSize: props.pageSize,
});
const { run: getList } = useRequest(props.getListApi, {
  manual: true,
  onSuccess: (res) => {
    if (page.value.pageNum >= res.pages) {
      finished.value = true;
    }
    list.value.push(...res.data);
    loading.value = false;
    page.value.pageNum++;
  },
  onError: (err) => {
    loading.value = false;
    finished.value = true;
    Toast(err.message);
  },
});
const getData = () => {
  const req = {
    ...props.initParams,
    ...page.value,
  };
  req[props.searchKey] = searchValue.value || undefined;
  getList(req);
};
const search = () => {
  list.value = [];
  page.value.pageNum = 1;
  getData();
};
const selected = ref(props.selectedItem);
const handleSelect = (item) => {
  selected.value = item;
};
const onCancel = () => {
  emit("update:show", false);
};
const onConfirm = () => {
  if (!selected.value?.value) {
    Toast("请选择");
    return;
  }
  emit("on-confirm", selected.value);
  emit("update:show", false);
};
</script>

<style lang="scss" scoped>
.picker-by-paging {
  color: #555;
}
.picker-hidder {
  padding: 10px;
  display: flex;
  justify-content: space-between;
  line-height: 16px;
  .title {
    font-size: 16px;
    font-weight: bold;
  }
}
.picker-content {
  min-height: 50%;
  max-height: calc(100% - 92px);
  overflow-y: scroll;
  .picker-item {
    text-align: center;
  }
  .active {
    color: #02aefdf9;
    font-weight: bold;
    font-size: 15px;
  }
}
.no-search {
  max-height: calc(100% - 38px);
}
</style>

使用组件

<PickerByPaging
  v-if="showPickerByPaging"
  v-model:show="showPickerByPaging"
  :selectedItem="selectedItem"
  :pickerTitle="pickerTitle"
  :showSearch="showSearch"
  :searchKey="searchKey"
  :initParams="initParams"
  :pageSize="30"
  :getListApi="getListByPage"
  @on-confirm="pickerConfirm"
/>
<script lang="ts" setup>
  import PickerByPaging from "@/components/Picker/PickerByPaging.vue";
</script>
export const getPolicyListByPage = async (req: any): Promise<any> => {
  const res: any = await request.get(
    `/xxx/xxx`,
    { params: req }
  );
  const data = res.records.map((item) => {
    return {
      value: item.code,
      text: item.name,
    };
  });
  return { data, pages: res.pages, total: res.total };
};

组件参数配置

参数说明类型默认值是否必传
show是否弹出选择器booleanfalse
selectedItem当前选中的选项ITextValuenull
pickerTitle弹窗标题String''
showSearch是否显示搜索框booleantrue
searchKey搜索的字段名称String''
initParams请求时的附加参数object{}
pageSize每页请求的数据条数number20
getListApi请求方法Function() => Promise.resolve([])

组件事件

方法名说明回调参数是否必传
onConfirm选中选项,点击确定时出发当前选中的选项