• 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(前端懒加载)

    页面结构:

      

      

  • 相关阅读:
    人月神话 画蛇添足
    人月神话 贵族专制和民主政治
    人月神话 外科手术队伍
    人月神话 焦油坑
    体温填报(五)
    体温填报(四)
    qwb与学姐
    qwb VS 去污棒
    1045 快速排序(25 分)
    LibreOJ #107. 维护全序集
  • 原文地址:https://www.cnblogs.com/wuqilang/p/16077826.html
Copyright © 2020-2023  润新知