• vue移动端封装项目单选组件ProjectRadio(前端懒加载) 吴小明


    效果:

      

    所具备的功能:

      1、切换学年

      2、项目单选

      3、前端懒加载(前端分页)

      4、打开弹框可以回显上一次选中的项目,点击取消不进行操作

      5、通过isRadioChange控制,选中后,再次点击可取消

    components/ProjectRadio.vue

    <template>
      <div class="public-project-radio">
        <van-popup class="project-radio" v-model="isShow" position="bottom" :style="{ height: '100%' }">
          <div class="close">
            <van-icon name="cross" @click="handleCancel" />
          </div>
          <div class="yearList">
            <year-select :yearOptions="yearOptions" :isCheckId="isCheckId" @on-select="selectChange"></year-select>
          </div>
          <!-- 搜索 -->
          <div class="search">
            <van-search v-model="searchValue" placeholder="输入项目名称" @input='handleInput' />
          </div>
          <div class="van-list-box">
            <van-list ref="scrollContent" :finished="finished" finished-text="没有更多了" @load="onLoad">
              <van-radio-group v-model="radioResult" @change='handleChange'>
                <van-radio v-for='ele in personData' :key='ele.value' :name="ele.projectId" @click="handleClick">
                  {{ele.projectName}}
                  <template #icon="props">
                    <div :class="props.checked ? 'activeIcon' : 'inactiveIcon'"><span></span></div>
                  </template>
                </van-radio>
              </van-radio-group>
            </van-list>
          </div>
          <div class="van-popup-btns">
            <van-button native-type="button" @click="handleCancel">取消</van-button>
            <van-button native-type="button" @click="handleConfirm">确定</van-button>
          </div>
        </van-popup>
      </div>
    </template>
    <script>
    import clonedeep from 'lodash.clonedeep'
    import { getProjectOptions, getSchoolYearOptions } from '@/api/select'
    import YearSelect from '@/components/YearSelect'
    const LOAD_NUM = 20
    export default {
      name: 'ProjectRadio',
      components: {
        YearSelect
      },
      data() {
        return {
          isShow: false,
          isRadioChange: false, // 判断单选的状态有没有变化
          radioResult: '', // 回显
          searchValue: '', // 提供v-model响应参数
          personData: [], // 用于循环展示的list数据
          initPersonData: [], // 备份:接口数据
          searchpersonData: [], // 搜索到的数据
          tileList: [], // 平铺数据
          yearOptions: [],
          isCheckId: undefined,
          finished: false,
          allProjectList: [] // 所有的学年对应的项目集合
          // list: [],
        }
      },
      created() {
        this.fetchSchoolYearOptions().then(() => {
          this.getData()
        })
      },
      methods: {
        onLoad() {
          if (this.searchValue) {
            this.loadMoreData(this.searchpersonData)
          } else {
            this.loadMoreData(this.initPersonData)
          }
        },
        // 加载更多数据到select框
        loadMoreData(dataList) {
          const renderedLen = this.personData.length // 已渲染的下拉列表长度
          const totalLen = dataList.length // 全部数据源的长度(总全部或者搜索到的全部)
          let addList = []
          // 如果 下拉列表已渲染的数据 < 全部数据 (意味着没有全部渲染完所有数据)
          if (renderedLen < totalLen) {
            // 如果 下拉列表已渲染的数据 + 每次想要渲染的数量 <= 全部数据
            // (如果小于等于 slice方法第二个参数能取到)
            if (renderedLen + LOAD_NUM <= totalLen) {
              addList = dataList.slice(renderedLen, renderedLen + LOAD_NUM)
              this.finished = false
            } else {
              // 如果长度不够 取余数为 slice最后一个参数
              addList = dataList.slice(
                renderedLen,
                renderedLen + (totalLen % LOAD_NUM)
              )
              this.finished = true
            }
            // 把截取到的后30条拼接在循环的列表尾部
            this.personData = this.personData.concat(addList)
          }
        },
        // 单选radio选中后,再次点击需要可以取消选择功能
        handleChange() {
          this.isRadioChange = true
        },
        fetchSchoolYearOptions() {
          return getSchoolYearOptions().then(({ data }) => {
            const options = data.map(({ name, id, isCheck }) => {
              if (isCheck) {
                this.isCheckId = id
              }
              return {
                name: `${name}学年`,
                value: id
              }
            })
            this.yearOptions = options
            let years = options.map(item => item.value)
            this.getAllProjectList(years)
          })
        },
        // 获取所有的学年对应的项目集合
        getAllProjectList(years) {
          let allProjectList = []
          years.forEach(async year => {
            let res = await getProjectOptions({ isUser: 1, year })
            let { projectLetterSelect } = res.data
            allProjectList = allProjectList.concat(projectLetterSelect)
            this.allProjectList = allProjectList
          })
        },
        selectChange(val) {
          this.isCheckId = val
          this.searchValue = ''
          this.handleInput('')
          this.getData()
          document.querySelector('.van-list-box').scrollTop = 0
        },
        handleClick() {
          if (!this.isRadioChange) {
            this.radioResult = ''
          }
          this.isRadioChange = false
        },
        // 打开弹框
        handleOpen(projectId) {
          this.radioResult = projectId
          this.isShow = true
        },
        // 关闭弹框
        handleCancel() {
          this.isShow = false
        },
        // 确定
        handleConfirm() {
          let tileList = clonedeep(this.allProjectList)
          let result = tileList.filter(item => item.projectId === this.radioResult)
          this.$emit('projectRadio', result)
          this.handleCancel()
        },
        // 搜索
        handleInput(val) {
          document.querySelector('.van-list-box').scrollTop = 0
          if (val) {
            this.searchpersonData = this.initPersonData.filter(item =>
              item.projectName.match(val)
            )
            this.personData = this.initPersonData
              .filter(item => item.projectName.match(val))
              .slice(0, LOAD_NUM)
          } else {
            this.searchpersonData = []
            this.personData = this.initPersonData.slice(0, LOAD_NUM)
            this.finished = false
          }
        },
        // 请求数据
        async getData() {
          const { isCheckId } = this
          let res = await getProjectOptions({ isUser: 1, year: isCheckId })
          if (res.code === '200') {
            let { projectLetterSelect } = res.data
            let arr = projectLetterSelect.sort((a, b) =>
              a.letter.localeCompare(b.letter)
            ) // 按字母排序
            // 假数据
            // for(let i = 0; i <=300; i++){
            //   arr.push({
            //     "projectId": 1,
            //     "projectName": `${i}阿坝县中学2023届(高一e网通)`,
            //     "letter": "Z",
            //     "isChecked": false
            //   })
            // }
            this.initPersonData = arr // 存储原始数据
            this.personData = arr.slice(0, LOAD_NUM)
            this.tileList = projectLetterSelect
          }
        }
      }
    }
    </script>

      css:

    <style lang="less" scoped>
    .public-project-radio {
      /deep/ .project-radio {
        box-sizing: border-box;
        padding-top: 135px;
        .close {
          height: 30px;
          position: fixed;
          top: 5px;
          left: 15px;
          font-size: 16px;
          z-index: 1005;
        }
        .yearList {
          position: fixed;
          top: 40px;
          left: 0px;
          font-size: 16px;
          z-index: 1005;
          .year-select {
            padding-top: 5px;
            padding-bottom: 15px;
          }
        }
        // 选中和未选中样式-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
        .search {
          display: flex;
          align-items: center;
          padding: 4px 15px;
          position: fixed;
          top: 80px;
          width: 100%;
          box-sizing: border-box;
          background-color: #fff;
          z-index: 1001;
          > .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 {
              padding-right: 12px;
              .van-icon {
                color: #8e8e93;
              }
              .van-field__control {
                font-size: 17px;
                color: #b5b5b5;
              }
            }
          }
        }
        .van-list-box {
          height: calc(100% - 100px);
          overflow: auto;
          .van-radio-group {
            color: red;
            .van-radio {
              margin-top: 20px;
              padding: 0 15px;
              .van-radio__label {
                margin-left: 20px;
                font-size: 16px;
              }
            }
            .van-radio:first-child {
              margin-top: 0;
            }
            .van-radio:last-child {
              margin-bottom: 10px;
            }
          }
        }
        .van-popup-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: 150px;
            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

    使用:

      引入、注册:

        import ProjectRadio from '@/components/ProjectRadio'
    
        components: { ProjectRadio, SelectUserPopup }

      DOM:(通过ref控制子组件的打开)

        <van-field v-model='params.projectName' placeholder="选择项目" readonly is-link @click="$refs.projectRadioRef.handleOpen(params.projectId)" />
     
        <!-- 项目单选弹框 -->
        <ProjectRadio @projectRadio='handleProjectRadio' ref='projectRadioRef'></ProjectRadio>

      data:

          params: {
            projectId: -1, // 项目Id 52883
            projectName: '', // 项目名称----仅做回显使用
            contactIdList: [], // 联系人
          }

      methods:(联系人options是基于项目id的,所以切换项目时要清空已选的联系人)

        // 项目单选弹层【确定】按钮
        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 = ''
          }
        }
    YearSelect.vue
    <!-- 学年横向滚动 -->
    <template>
      <div class="year-select">
        <div v-for="(item, index) in yearOptions" :key="index" :class="{
              'tag-item': true,
              'selected-item': currentIndex === index
            }" @click="() => handleItemClick(item, index)">{{ item.name }}</div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'YearSelect',
      model: {
        prop: 'value',
        event: 'on-change'
      },
      components: {},
      props: {
        yearOptions: {
          type: Array,
          default: () => []
        },
        isCheckId: {
          type: Number
        },
        value: {
          type: [String, Number]
        },
        mode: {
          type: String,
          default: 'radio' // 'radio', 'checkbox'
        }
      },
      data() {
        return { currentIndex: undefined }
      },
      watch: {
        isCheckId: {
          handler: function (val, oldVal) {
            if (val === undefined) {
              this.currentIndex = undefined
            } else {
              this.currentIndex = this.yearOptions.map(n => n.value).indexOf(val)
            }
          },
          immediate: true
        }
      },
      computed: {},
      mounted() {},
      methods: {
        handleItemClick(item, index) {
          const { mode } = this
          if (mode === 'radio') {
            this.currentIndex = index
            // console.log(item)
            this.$emit('on-select', item.value)
          }
        }
      }
    }
    </script>
    <style lang='less' scoped>
    .year-select {
      width: 95%;
      padding-left: 15px;
      padding-bottom: 5px;
      overflow-x: scroll;
      overflow-y: hidden;
      white-space: nowrap;
      &::-webkit-scrollbar {
        display: none;
      }
      .tag-item {
        display: inline;
        padding: 6px 20px;
        border-radius: 15px;
        margin-right: 10px;
        background: #e0e5f5;
        font-size: 12px;
      }
      .selected-item {
        background: @theme-color;
        color: #fff;
      }
    }
    </style>
  • 相关阅读:
    spark hbase
    Benchmark简介
    Flink的安装配置
    Hive入门及常用指令
    hadoop+yarn+hbase+storm+kafka+spark+zookeeper)高可用集群详细配置
    Linux最常用的命名
    数据库的零散的总结
    DBA总结
    MySQL主从架构配置
    mysql主从读写分离,分库分表
  • 原文地址:https://www.cnblogs.com/wuqilang/p/16080322.html
Copyright © 2020-2023  润新知