el-popover实现下拉滚动刷新

效果图:

下拉组件实现: wb-select-scroll.vue

复制代码
<!--
  
<wb-select-scroll :parentValue.sync="dataList.wbCombinIVos[index].subid" urls="subjectEndScroll" :rowIndex="index" @selectBackFun="dealSelectBackFun"
                            placeholders="请选择折扣科目" clearable></wb-select-scroll >
                            
  5、新增使用那个组件,assembly
  shop是分机机构,department是部门,employ是业务员,subject是科目,remark是摘要,billkind100是运输方式,project是项目,customer是客户,money是币种,
  paymethod是结算方式,trader是供应商,iotype是出入库类型,assetsType是固定资产类别,assetsAddType是固定资产增加方式,assetsReduceType是固定资产减少方式
  voucherRemark是凭证摘要,goodstype是货品类别,brand是品牌,

  新改动;下拉刷新,根据传过来的urls值,请求后端接口,要求,所有的接口都要按照分页的格式进行处理,可以参考 goodsList 接口进行开发

  urls="goodsList" 货品下拉列表
  urls="subjectEndScroll" 末级科目下拉列表
  urls="getTraderCustomerScroll" 客户下拉列表
 -->




<template>
  <div >
    <el-popover placement="bottom" :width="width" trigger="click" popper-class="wb-padding" v-model="visible"
      :disabled="disabled">
      <div ref="divRef">
        
        <div style="overflow: auto; height: 200px; border: 1px solid #ddd;" ref="scrollContainer" v-if="urls == 'goodsList'">
          <!-- <el-radio-group v-model="goodstype" style="margin-top: 5px;">
            <el-radio :label="3">A类</el-radio>
            <el-radio :label="6">B类</el-radio>
            <el-radio :label="9">C类</el-radio>
          </el-radio-group> -->
          <ul class="el-scrollbar__view el-select-dropdown__list" style="list-style: none; padding: 0; margin: 0;">
            <!-- 表头 -->
            <div>
              <li class="el-select-dropdown__item" 
                  style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; padding: 8px 0; background-color: #f8f4f4;font-weight: bold">
                <span style="padding:5px;display: inline-block; width: 200px;text-align:center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;">
                  编码
                </span>
                <span style="padding:5px; width: 250px;text-align:center;  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;">
                  名称
                </span>
                <span style="padding:5px; width: 100px;text-align:center;  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
                  规格
                </span>
              </li>
            </div>        

            <!-- 数据行 -->
            <div v-for="(it, index) in listArr" :key="index" @click="upData(it)">
              <li class="el-select-dropdown__item" 
                  style="padding:5px;display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; padding: 8px 0;">
                <span style="padding:5px;display: inline-block; width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;" 
                      :title="it.code">
                  {{ it.code }}
                </span>
                <span style="padding:5px;width: 250px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;" 
                      :title="it.name">
                  {{ it.name }}
                </span>
                <span style="padding:5px; width: 100px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" 
                      :title="it.specs">
                  {{ it.specs }}
                </span>
              </li>
            </div>

            <!-- 无数据时显示 -->
            <div v-if="listArr == null || listArr.length <= 0"
                style="display: flex; justify-content: center; align-items: center; text-align: center; width: 100%; height: 200px; border: 1px solid #ddd;">
              <span style="padding:5px;">无数据</span>
            </div>
          </ul>
        </div>


        <div style="overflow: auto; height: 200px;" ref="scrollContainer" v-else>
          <ul class="el-scrollbar__view el-select-dropdown__list" style="list-style: none; padding: 0; margin: 0;">
            <!-- 表头 -->
            <div>
              <li class="el-select-dropdown__item" 
                  style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; padding: 8px 0; background-color: #f8f4f4;font-weight: bold">
                <span style="padding:5px;display: inline-block; width: 200px;text-align:center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;">
                  编码
                </span>
                <span style="padding:5px; width: 250px;text-align:center;  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;">
                  名称
                </span>
              </li>
            </div>        

            <!-- 数据行 -->
            <div v-for="(it, index) in listArr" :key="index" @click="upData(it)">
              <li class="el-select-dropdown__item" 
                  style="display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #ddd; padding: 8px 0;">
                <span style="padding:5px;display: inline-block; width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;" 
                      :title="it.code">
                  {{ it.code }}
                </span>
                <span style="padding:5px;width: 250px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-right: 1px solid #ddd; padding-right: 5px;" 
                      :title="it.name">
                  {{ it.name }}
                </span>
              </li>
            </div>
            <div v-if="listArr == null || listArr.length <= 0"
              style="display: flex; justify-content: center; align-items: center; text-align: center; width: 100%; height: 200px;">
              <span style="padding:5px;">无数据</span>
            </div>
          </ul>
        </div>





        <div :style="'background-color: #fff;width:' + width + ' px;'">
          <div v-if="assembly" style="display: flex; line-height: 30px;cursor: pointer;">
            <div class="addBorder" style="width: 50%;" @click="adds">新增</div>
            <div class="delBorder" style="width: 50%;" @click="empty">重置</div>
          </div>
          <div v-else style="display: flex; line-height: 30px;cursor: pointer;">
            <div class="delBorder" style="width: 100%;" @click="empty">重置</div>
          </div>
        </div>
      </div>
      <el-input type="text" ref="myInput" v-model="name" @change="changes" @input="remoteName"
        slot="reference" size="mini" style="width: 100%;" :placeholder="placeholders" :disabled="disabled">
        <i v-if="disabled == false" slot="suffix"
          :class="showIcon ? 'el-input__icon el-icon-arrow-up' : 'el-input__icon el-icon-arrow-down'"
          @click="handleClick"></i>
        <i v-if="disabled == true" class="el-input__icon el-icon-arrow-up"></i>
      </el-input>
    </el-popover>
    <!-- 组件化新增 -->
    <component :is="currentComponent" ref="assemblyAll" @on-added="handleAdded"></component>

  </div>
</template>

<script>

export default {
  components: {
    "areas": () => import('./assembly/area.vue'),
  },
  props: {
    parentValue: "",
    placeholders: "",
    list: [],
    widths: "",
    urls: "",
    defaultId: "",
    assembly: "",
    disabled: {
      default: false
    },
    voucherTemSubject: "",
    params:{
      page: 1,
      pageSize: 20
    },
    rowIndex: {
      type: Number,
      default: -999
    },
  },
  watch: {
    visible(newVal) {
      this.showIcon = newVal; // 如果visible为true,箭头向上;false时箭头向下
      if (newVal) {
        //设置宽度
        this.focusSelect();

        this.$nextTick(() => {
          const scrollContainer = this.$refs.scrollContainer;
          if (scrollContainer) {
            scrollContainer.addEventListener('scroll', this.handleScroll);
          }
          this.paramsData.page = 1; // 初始化页码
          this.listArr = []; // 清空数据
          this.hasMore = true; // 允许加载更多
         // this.fetchOptions(); // 加载第一页数据
        //  console.log('watch监听打开下拉后查询-------newVal--',newVal);
         this.selectByKeyWord();
        });
      } else {
        const scrollContainer = this.$refs.scrollContainer;
        if (scrollContainer) {
          scrollContainer.removeEventListener('scroll', this.handleScroll);
        }
      }
    },
    list(newVal) {
      //this.listArr = newVal;
    },
    parentValue: {
      handler(newvalue) {
        this.$nextTick(() => {
          if(this.parentValue){
            //默认值
            // console.log('watch监听parentValue后查询---------',this.parentValue);
            this.selectByKeyWord(1);
          }else {
            this.name = "";
          }
        });
      }
    },
  },
  data() {
    return {
      name: '',
      goodstype: null,
      width: "200",
      dataList: { "mshopid": "" },
      visible: false,
      parentids: this.defaultId,
      id: "",
      index: 1,
      listCp: [],
      currentComponent: "",
      listArr: [],
      inpIndex: true,
      showIcon: false,
      parentValueInfo: this.parentValue,
      paramsData:{
        page: 1,               // 当前页码
        pageSize: 20, 
      },
               // 每页数据量
      hasMore: true,         // 是否还有更多数据
      loading: false,
      had_id_one: [],

      // list: [{'id':1,"name":"dade"},
      // {'id':4,"name":"dade4"}]
    };
  },
  created() {
    this.listArr = this.list
    
  },
  mounted() {
    // console.log('mounted---this.parentValue===',this.parentValue);
    
    // 存在加载下拉框,降低io
    if(this.parentValue){
      //默认值
      // console.log('挂载完成后查询---------',this.parentValue);
      
      this.selectByKeyWord(1);
    }
    //动态引入组件
    if (this.assembly) {
      //引入组件
      this.currentComponent = this.assembly;
    }

    document.addEventListener('click', this.selectBlur, true)
  },
  beforeDestroy() {
    document.removeEventListener('click', this.selectBlur, true)

    // 移除滚动事件监听,防止内存泄漏
    const scrollContainer = this.$refs.scrollContainer;
    if (scrollContainer) {
      scrollContainer.removeEventListener('scroll', this.handleScroll);
    }

  },
  methods: {
    handleScroll(event){
     // console.log('------滚动了---------');
      const { scrollTop, scrollHeight, clientHeight } = event.target;
      //console.log('scrollTop + clientHeight=======',scrollTop + clientHeight)
     // console.log('scrollHeight=======',scrollHeight)
      // 判断是否滚动到底部
      if (scrollTop + clientHeight >= scrollHeight-1 && this.hasMore ) {
        this.paramsData.page++; // 增加页码
       // this.fetchOptions(); // 请求下一页数据
       // console.log('到底了,查询下一页------');

       this.select();
      }
    },
    // 输入框失去焦点
    selectBlur(e) {
      if (
        this.visible === true
        && this.$refs.divRef
        && this.$refs.myInput
        && this.$refs.myInput.$el
        && !this.$refs.divRef.contains(e.target)
        && !this.$refs.myInput.$el.contains(e.target)
      ) {
        this.visible = false
      }
    },
    async handleAdded(data) {
      this.parentids = data.id
      // console.log('新增--handleAdded---查询----');
      await this.select()
      this.$emit('update:parentValue', data.id);
      // 处理新增数据的回调方法
      this.$emit('add-callback', { ...data })
    },

    handleClick() {
      // if (this.showIcon === false) {
      //   this.focusSelect()
      // }
    },

    // 输入框获得焦点
    focusSelect() {
      const inputElement = this.$refs.myInput.$el;
      if (this.widths == undefined) {
        this.width = inputElement.offsetWidth;
      } else {
        this.width = this.widths;
      }
    },
    //失去焦点,或者按回车,返回
    changes() {
      if (this.urls == 'remark') {
        this.inpIndex = false;
        this.$emit('update:parentValue', this.name);
      }
    },
    // 输入框数据改变
    remoteName(e) {
      this.paramsData.keyWord = e;
      this.selectByKeyWord();

    },
    //点击选中,返回值,数据双向绑定
    upData(it) {
      this.name = it.name;
      this.$emit('update:parentValue', it.id);
      this.$emit('selectBackFun', it,this.rowIndex);//选中调用父类方法
      this.visible = false;
    },
    // 新增
    adds() {
      this.$refs.assemblyAll.assembly();
      this.visible = false;
    },
    // 重置
    empty() {
      this.name = "";
      this.$emit('update:parentValue', "");
      this.$emit('accountFn', {})
      this.visible = false;
    },
    // 查询下拉框
    async select() {
      if (this.urls) {
       
        await this.$myhttp.post("/main/dropDowm/" + this.urls, this.paramsData).then((res) => {
          // console.log('select查询--res.data.code----',res.data.code);
          if(res.data.code == 2000){
            let datalist = res.data.data;
            if(datalist != null && datalist.length > 0){
              this.listArr = [...this.listArr,...datalist]
              this.listCp = [...this.listCp,...datalist]
            }
            // console.log('select查询结果this.listArr---',this.listArr);
          }
        })
      }
    },
    retParamsData(){
      this.paramsData.page = 1;
      this.paramsData.pageSize = 20;
    },
    // 查询下拉框
    async selectByKeyWord(val) {
      if (this.urls) {
        if(this.parentValue && '' !== this.parentValue && val == 1){
          this.paramsData.parentValue = this.parentValue;
        }else {
          this.paramsData.parentValue = null;
        }
        this.retParamsData();//还原查询分页数据
        await this.$myhttp.post("/main/dropDowm/" + this.urls, this.paramsData).then((res) => {
          if(res.data.code == 2000){
            let datalist = res.data.data;
            let count = res.data.count;
            if(val == 1){
              this.listArr = [...datalist];
              this.listCp = [...datalist];
              this.had_id_one =  [...datalist];
              
              if(this.had_id_one && this.had_id_one.length == 1){
                this.name = this.had_id_one[0].name;
              }
              
            }else {
              // 过滤出 this.had_id_one 中那些 id 不在 datalist 中的对象
              let filteredDatalist = datalist.filter(item => 
                !this.had_id_one.some(dataItem => dataItem.id === item.id)
              );

              // 合并过滤后的结果
              this.listArr = [...filteredDatalist,...this.had_id_one];
              this.listCp = [...datalist];
            }
          }
        })
      }else {
        this.listArr = [];
      }

    },
  }
};
</script>

<style>
.el-select-dropdown__wrap {
  max-height: 200px !important;
}

.wb-padding {
  padding: 0px;
  min-width: 300px;
}

.addBorder {
  text-align: center;
  border-top: 1px solid rgb(221 221 221);
}

.delBorder {
  text-align: center;
  border-top: 1px solid rgb(221 221 221);
  border-left: 1px solid rgb(221 221 221);
}
</style>
<style scoped>
.scroll-container {
  height: 300px; /* 设置一个固定高度来模拟滚动 */
  overflow-y: auto; /* 允许垂直滚动 */
}
</style>

在别的页面使用:

1、引入:

复制代码
components: {
    "wb-select-scroll": () => import('@/views/comm/wb-select-scroll.vue'),
  },

2、使用:

复制代码
<el-descriptions size="small" :column="1" border :labelStyle="{ width: '80px', color: '#000000' }"
              :contentStyle="{ width: 'calc(100% - 80px)' }">
                <el-descriptions-item label="货品名称">
                  <wb-select-scroll :parentValue.sync="goods.goodsid" urls="goodsList" @selectBackFun="dealSelectBackFun"
                      placeholders="请选择货品" clearable></wb-select-scroll >
                </el-descriptions-item>
              </el-descriptions>

重要说明:

parentValue.sync为绑定的变量,必填

:parentValue.sync="goods.goodsid"

urls为请求到后端接口的url,根据自己实际项目调整组合

urls="goodsList"

@selectBackFun点击下拉内容返回的回调函数,根据实际业务处理

@selectBackFun="dealSelectBackFun"

相关推荐
咪库咪库咪3 分钟前
使用Fetch api发起请求
前端
东华帝君5 分钟前
nuxt + nuxt ui + nuxt i18n
前端
jingling5559 分钟前
前端开发核心知识详解:Vue2、JavaScript 与 CSS
javascript·css·vue.js
AronTing13 分钟前
单例模式:确保唯一实例的设计模式
java·javascript·后端
鹿九巫18 分钟前
【CSS】超详细!一篇文章带你学会CSS的层叠,优先级与继承
前端
工业互联网专业39 分钟前
基于springboot+vue的医院管理系统
java·vue.js·spring boot·毕业设计·源码·课程设计·医院管理系统
天天码行空40 分钟前
UnoCSS原子CSS引擎-前端CSS救星
前端
1_2_3_41 分钟前
抛弃 if-else,让 JavaScript 代码更高效
前端
火星思想41 分钟前
再来看看「从输入 URL 到看到页面」的整个流程
前端·面试
张开心_kx41 分钟前
不要再代码中滥用 useCallback 和useMemo
前端·react.js