vue移动端封装选择人员组件SelectUserPopup(将请求函数和请求数据传入子组件中)

效果:

  

使用到的技术:

  1、支持分页(下拉加载更多),这里是接口支持的分页。推荐:vue基于vant封装上拉加载/下拉刷新组件ListScroller

  2、支持搜索,这个也是接口支持的。搜索支持防抖

  3、多选(可扩展成支持单选)

  4、通过sync修饰符绑定父子组件传参

  5、请求函数和请求数据传入子组件中

 

components/SelectUserPopup.vue

<template>
  <div class="select-user-popup">
    <van-popup v-model="isShow" position="bottom" :style="{ height: '100%' }">
      <div class="wrapper">
        <div class="search">
          <van-icon name="cross" @click="handleClose" />
          <van-search v-model="searchValue" :placeholder="params.placeholder||'输入姓名'" @input='handleInput' />
        </div>
        <list-scroller ref="listScrollerRef" @on-pulldown-loading="refrash" @on-pullup-loading="loadMore">
          <van-checkbox-group v-model="result">
            <van-checkbox :name="item.id" v-for="item in userOptions" :key="item.id">
              {{item.name}}{{item.empNo?`(${item.empNo})`:''}}
              <template #icon="props">
                <div :class="props.checked ? 'activeIcon' : 'inactiveIcon'"><span></span></div>
              </template>
            </van-checkbox>
          </van-checkbox-group>
        </list-scroller>
      </div>
      <div class="btns">
        <van-button @click="handleClose" native-type="button">取消</van-button>
        <van-button @click="handleConfirm" native-type="button">确定</van-button>
      </div>
    </van-popup>
  </div>
</template>
<script>
/*
  使用:
    <SelectUserPopup :selected.sync="page1Data.visitStaffList" :show.sync='isShowVisitStaffPopup' :params='visitStaffPopupParams'></SelectUserPopup>

      isShowVisitStaffPopup: false, // 弹层显隐
      visitStaffPopupParams: {
        queryParams: { page: 1, pageSize: 20, keyWord: '' },
        requestFn: getVisitStaffOptionsApi, // 接口函数
        placeholder: '输入姓名或工号'
      }

*/
import ListScroller from '@/components/ListScroller'
import debounce from 'lodash.debounce'
export default {
  watch: {
    selected: {
      handler(selected) {
        this.result = selected // 场景:当进入到弹层中时,将值回显
      },
      immediate: true
    }
  },
  computed: {
    isShow: {
      get() {
        this.show && this.handleInit()
        return this.show
      },
      set(flag) {
        this.$emit('update:show', flag)
      }
    }
  },
  props: {
    selected: { type: Array, require: true }, // script
    show: { type: Boolean, require: true }, // css
    params: { type: Object, require: true } // template
  },
  data() {
    return {
      searchValue: '', // 搜索框内容
      queryParams: this.params.queryParams, // 接口参数
      userOptions: [], // 用户列表
      totalRecords: 0, // 总条数
      result: [] // 回显选中的值
    }
  },
  methods: {
    // 初始化(打开弹层时执行)
    handleInit() {
      this.result = this.selected // 回显选中的值   场景:当选中一项后点击取消,再次进入正常回显
      this.searchValue = '' // 清空输入框
      this.handleInput('') // 刷新
    },
    // 【取消】按钮
    handleClose() {
      this.isShow = false
    },
    // 【确认】按钮
    handleConfirm() {
      this.$emit('update:selected', this.result)
      this.handleClose()
    },
    // 搜索框input事件
    handleInput(val) {
      this.$refs.listScrollerRef.$el.scrollTop = 0
      this.queryParams.page = 1
      this.queryParams.keyWord = val
      this.getUserOptions()
    },
    // 拜访人(员工)列表
    async getUserOptions() {
      const {
        queryParams,
        params: { requestFn }
      } = this
      const { success, data, totalRecords } = await requestFn(queryParams)
      if (success) {
        this.totalRecords = totalRecords
        if (this.queryParams.page === 1) {
          this.userOptions = data
        } else {
          this.userOptions.push(...data)
        }
      }
    },
    // 刷新
    refrash(cb) {
      this.queryParams.page = 1
      this.getUserOptions().then(() => cb && cb())
    },
    // 加载更多
    loadMore(cb) {
      const { userOptions, totalRecords } = this
      if (totalRecords > userOptions.length) {
        this.queryParams.page++
        this.getUserOptions().then(() => {
          cb && cb()
        })
      } else {
        cb && cb()
      }
    }
  },
  created() {
    this.getUserOptions()
    this.handleInput = debounce(this.handleInput, 200) // 搜索框防抖
  },
  components: { ListScroller }
}
</script>
<style lang="less" scoped>
.select-user-popup {
  /deep/ .van-popup {
    box-sizing: border-box;
    .wrapper {
      height: calc(100% - 144px);
      padding-top: 44px;
      .search {
        position: absolute;
        top: 0;
        width: 100%;
        display: flex;
        align-items: center;
        padding: 4px 15px;
        box-sizing: border-box;
        > .van-icon {
          width: 30px;
          color: #333333;
          font-size: 20px;
        }
        .van-search {
          flex: 1;
          padding: 0;
          height: 36px;
          border-radius: 18px;
          overflow: hidden;
          background-color: #f3f6f9;
          .van-search__content {
            .van-icon {
              color: #8e8e93;
            }
            .van-field__control {
              font-size: 17px;
              color: #b5b5b5;
            }
          }
        }
      }
      .van-checkbox-group {
        .van-checkbox {
          margin-top: 20px;
          padding: 0 15px;
          .van-checkbox__label {
            margin-left: 20px;
            font-size: 16px;
            word-break: break-all;
          }
          // 选中和未选中样式-start
          .activeIcon {
            width: 18px;
            height: 18px;
            border: 2px solid #198cff;
            border-radius: 50%;
            box-sizing: border-box;
            display: flex;
            align-items: center;
            justify-content: center;
            > span {
              display: block;
              width: 10px;
              height: 10px;
              background: #198cff;
              border-radius: 50%;
            }
          }
          .inactiveIcon {
            width: 18px;
            height: 18px;
            border: 2px solid #e0e5f5;
            border-radius: 50%;
            box-sizing: border-box;
          }
          // 选中和未选中样式-end
        }
      }
    }
    > .btns {
      margin-top: 15px;
      padding: 0 15px;
      background-color: #fff;
      display: flex;
      justify-content: space-between;
      box-sizing: border-box;
      > .van-button {
        width: calc(50% - 8px);
        height: 38px;
        line-height: 38px;
        border-radius: 19px;
        font-size: 14px;
        text-align: center;
      }
      > .van-button:first-child {
        background-color: #e0e5f5;
        color: #374e64;
      }
      > .van-button:last-child {
        background-color: #1288fe;
        color: #fff;
      }
    }
  }
}
</style>

  css:

<style lang="less" scoped>
.select-user-popup {
  /deep/ .van-popup {
    box-sizing: border-box;
    .search {
      display: flex;
      align-items: center;
      padding: 4px 15px;
      box-sizing: border-box;
      > .van-icon {
        width: 30px;
        color: #333333;
        font-size: 20px;
      }
      .van-search {
        flex: 1;
        padding: 0;
        height: 36px;
        border-radius: 18px;
        overflow: hidden;
        background-color: #f3f6f9;
        .van-search__content {
          .van-icon {
            color: #8e8e93;
          }
          .van-field__control {
            font-size: 17px;
            color: #b5b5b5;
          }
        }
      }
    }
    .van-checkbox-group {
      .van-checkbox {
        margin-top: 20px;
        padding: 0 15px;
        .van-checkbox__label {
          margin-left: 20px;
          font-size: 16px;
          word-break: break-all;
        }
        // 选中和未选中样式-start
        .activeIcon {
          width: 18px;
          height: 18px;
          border: 2px solid #198cff;
          border-radius: 50%;
          box-sizing: border-box;
          display: flex;
          align-items: center;
          justify-content: center;
          > span {
            display: block;
            width: 10px;
            height: 10px;
            background: #198cff;
            border-radius: 50%;
          }
        }
        .inactiveIcon {
          width: 18px;
          height: 18px;
          border: 2px solid #e0e5f5;
          border-radius: 50%;
          box-sizing: border-box;
        }
        // 选中和未选中样式-end
      }
    }
    .btns {
      background-color: #fff;
      display: flex;
      justify-content: space-between;
      position: fixed;
      width: 100%;
      box-sizing: border-box;
      bottom: 47px;
      padding: 0 15px;
      > .van-button {
        width: calc(50% - 20px);
        height: 38px;
        line-height: 38px;
        border-radius: 19px;
        font-size: 14px;
        text-align: center;
      }
      > .van-button:first-child {
        background-color: #e0e5f5;
        color: #374e64;
      }
      > .van-button:last-child {
        background-color: #1288fe;
        color: #fff;
      }
    }
  }
}
</style>
View Code

 

(父组件)使用:

<template>
  <van-form class="add" @submit="onSubmit">
    <van-field v-model='params.projectName' placeholder="选择项目" readonly is-link @click="$refs.projectRadioRef.handleOpen(params.projectId)" />
    <van-field v-model="contactDesc" placeholder="选择联系人" readonly is-link @click="handleOpenContactPopup" />
    <van-field v-model="workDesc" placeholder="添加协作人" readonly is-link @click="isShowWorkPopup=true" />

    <div style="margin: 16px;">
      <van-button native-type="submit" :loading='loading'>提交</van-button>
    </div>
    <!-- 项目单选弹框 -->
    <ProjectRadio @projectRadio='handleProjectRadio' ref='projectRadioRef'></ProjectRadio>
    <!-- 联系人弹层 -->
    <SelectUserPopup v-if="contactParams.queryParams.projectId>0" :selected.sync="params.contactIdList" :show.sync='isShowContactPopup' :params='contactParams'>
    </SelectUserPopup>
    <!-- 协作人弹层 -->
    <SelectUserPopup :selected.sync="params.workPerson" :show.sync='isShowWorkPopup' :params='workParams'></SelectUserPopup>
  </van-form>
</template>
<script>
import { ddNavSetTitle, ddNavSetRight, isDingTalk } from '@/utils/dd'
import ProjectRadio from '@/components/ProjectRadio'
import SelectUserPopup from '@/components/SelectUserPopup'
import { getVisitPersonOptionsApi, getVisitStaffOptionsApi } from '@/api/visitManage'
export default {
  data() {
    return {
      isShowContactPopup: false, // 联系人弹层显隐
      // 联系人请求参数
      contactParams: {
        queryParams: { page: 1, pageSize: 30, keyWord: '', projectId: -1 },
        requestFn: getVisitPersonOptionsApi
      },
      isShowWorkPopup: false, // 协作人弹层显隐
      // 协作人请求参数
      workParams: {
        queryParams: { page: 1, pageSize: 30, keyWord: '' },
        requestFn: getVisitStaffOptionsApi, // 接口函数
        placeholder: '输入姓名或工号'
      },
      params: {
        projectId: -1, // 项目Id 52883
        projectName: '', // 项目名称----仅做回显使用
        contactIdList: [], // 联系人
        workPerson: [] // 协作人
      },
      loading: false // 提交loading
    }
  },
  computed: {
    // 联系人选中情况
    contactDesc() {
      const { contactIdList } = this.params
      return contactIdList.length ? `已选${contactIdList.length}个` : ''
    },
    // 协作人选中情况
    workDesc() {
      const { workPerson } = this.params
      return workPerson.length ? `已选${workPerson.length}个` : ''
    }
  },
  methods: {
    // 项目单选弹层【确定】按钮
    handleProjectRadio(val) {
      if (val.length) {
        const { projectId, projectName } = val[0]
        if (projectId !== this.params.projectId) this.params.contactIdList = [] // 清空联系人列表
        this.params.projectId = projectId
        this.params.projectName = projectName
      } else {
        this.params.contactIdList = [] // 清空联系人列表
        this.params.projectId = -1
        this.params.projectName = ''
      }
    },
    // 打开联系人弹层
    handleOpenContactPopup() {
      const { projectId } = this.params
      if (projectId <= 0) {
        this.$toast('请先选择项目')
      } else {
        this.contactParams.queryParams.projectId = projectId
        this.isShowContactPopup = true
      }
    },
    // 提交
    onSubmit(values) {
      console.log('submit', values, this.params)
    }
  },
  created() {
    ddNavSetTitle('新建日程')
  },
  components: { ProjectRadio, SelectUserPopup }
}
</script>
<style lang="less" scoped>
@import './index.less';
</style>

注意:

  1、联系人基于项目id,初始化时SelectUserPopup组件内直接在created中请求数据,但此时projectId的值还没有,所以用v-if做了判断,可以在子组件内做这一块的优化

  2、选择项目(单选)组件见此篇:vue移动端封装项目单选组件ProjectRadio(前端懒加载)

页面结构:

  

 

 

 

 

 

 

  

posted @ 2022-03-30 16:46  吴小明-  阅读(1131)  评论(0编辑  收藏  举报