哪怕使用VantUI,需封装一手风琴与复选框相结合的对话框组件,我一样无敌于世间

引言

在许多业务场景中,如库存管理、物流追踪等,条码的选择和处理是一项常见的需求。为了提高用户体验并简化开发流程,我们设计并实现了一款功能丰富的移动端页面条码选择对话框组件。本篇博客将深入介绍该组件的构建思路、核心功能及实际应用场景,帮助开发者理解其设计原理并掌握如何在项目中集成使用。

组件功能概述

本组件名为 ChooseBarcode,基于Vue框架和Vant UI库,旨在为用户提供一种灵活多样的条码选择界面,支持“手风琴展开”、“复选框”、“单选”三种展示模式,适用于多种业务场景下的条码选择需求。通过传递不同的props,开发者可以轻松定制对话框的展现形式和行为逻辑。

核心功能与技术要点

  1. 多样化的展示模式

    • Collapse(手风琴)模式:适用于分类展示和批量选择条码。
      这边的业务需求是查询符合条件的单据,从其中某个单据下选择条码提交,选择单据时默认选中该单据下所有条码。
      可根据自己项目中的业务场景进行拓展或修改。
      请添加图片描述

    • Checkbox模式:允许用户自由勾选多个条码,且提供条码勾选回显功能。

    • 请添加图片描述

    • Radio模式:适用于需要从列表中单选一个条码的情况。

    • 请添加图片描述

<template>
  <van-dialog
    v-model="localShowDialog"
    @open="openDialog"
    title="请选择条码"
    :show-cancel-button="true"
    confirm-button-text="确定"
    :before-close="handleClose"
  >
    <div class="dialog-body">
      <!-- 手风琴 -->
      <template v-if="componentType === 'collapse'">
        <van-collapse v-model="activeName" accordion>
          <van-collapse-item v-for="(item, index) in dataList" :key="index"
            :title="item.sDistNo" :name="item.sDistId"
          >
            <template slot="title">
              <div class="check-title">
                <div @click.stop>
                  <van-checkbox v-model="item.orderCheck" shape="square" @change="handleCheck(item)"
                    :label-disabled="true"
                  >
                    <template #icon>
                      <img class="img-icon" :src="checkedIcon" v-if="item.orderCheck && !item.isPartiallyChecked" />
                      <img class="img-icon" :src="partiallyCheckedIcon" v-if="(item.orderCheck || !item.orderCheck) && item.isPartiallyChecked" />
                      <img class="img-icon" :src="noCheckedIcon" v-if="!item.orderCheck && !item.isPartiallyChecked" />
                    </template>
                  </van-checkbox>
                </div>
                <div class="title-content">{{ item.sDistNo }}</div>
              </div>
            </template>
            <van-checkbox v-for="(ele, index) in item.sBarCodeList" :key="index"
              v-model="ele.codeCheck" class="checkbox-item" shape="square" @click.stop="handleCodeCheck(ele, item.sBarCodeList, item)"
            >
              {{ ele.sBarCode }}
            </van-checkbox>
          </van-collapse-item>
        </van-collapse>
      </template>
      <!-- 复选框 -->
      <template v-if="componentType === 'checkbox'">
        <van-checkbox-group v-model="checkList" ref="checkboxGroup">
          <van-cell-group>
            <van-cell
              v-for="(item, index) in dataList"
              clickable
              :key="index"
              @click="toggle(index, item.disabled)"
            >
              <van-checkbox shape="square" :disabled="item.disabled" :name="item.sBarCode" ref="checkboxes">
                条码:{{ item.sBarCode }}
              </van-checkbox>
            </van-cell>
          </van-cell-group>
        </van-checkbox-group>
      </template>
      <!-- 单选框 -->
      <template v-if="componentType === 'radio'">
        <van-list
          finished-text="没有更多了"
        >
          <van-cell
            v-for="(item, index) in dataList"
            :key="index"
            @click="selectSingleItem(item)"
            :class="{ selected: item === selectedItem }"
          >
            <div style="display: flex;justify-content: space-between;">
              <div>条码:{{item}}</div>
            </div>
          </van-cell>
        </van-list>
      </template>
    </div>
  </van-dialog>
</template>
  1. 精细的UI与交互设计
    使用Vant UI组件确保界面美观且符合移动端标准。通过自定义图标(部分选中、全选、未选中)增强视觉反馈,同时通过监听各种交互事件(如打开对话框、选择条码等),实现流畅的用户交互体验。
<style lang="scss" scoped>
.dialog-body {
  height: 320px; /* 调整高度以适应你的需求 */
  overflow-y: auto;
}
.check-title {
  display: flex;
  .img-icon {
    height: 20px;
  }
}
.title-content {
  margin-left: 10px;
}
.checkbox-item {
  margin-left: 10px;
  padding: 5px 0 10px 0;
  margin-bottom: 5px;
  border-bottom: 1px solid #ebedf0;
}
/* 确保van-list的高度能自适应内容 */
.van-dialog__body {
  padding: 0 !important;
}
.van-cell.selected {
  background-color: #76cff1; /* 你可以根据需要自定义颜色 */
  color: #292d3e; /* 你可以根据需要自定义颜色 */
}
</style>
  1. 逻辑处理与事件触发
    • 组件内部通过监听open事件初始化已选条码状态。
    • 提供@commitPdaDist自定义事件,当用户点击“确定”时,根据选择的条码类型(单选或多选),以数组或字符串形式向外传递选中数据,方便父组件处理后续逻辑。
    • 支持关闭对话框前的验证,如检查是否至少选择了一项,未选择则通过弹窗提示用户,保证数据的完整性。
// 核心事件

/**
 * 打开对话框函数
 * 该函数主要用于在特定条件下操作对话框的打开状态以及数据项的禁用状态。
 * 条件检查包括组件类型是否为复选框,条形码列表和数据列表是否存在。
 * 当满足条件时,会对数据列表中的项进行遍历,检查是否存在与条形码列表中相同的条形码。
 * 如果找到匹配的条形码,则相应地启用或禁用该复选框,并设置该项的禁用状态为true。
 */
openDialog() {
  // 咱们这边的需求要求复选框对应的场景需要做一个数据回显
  // 检查组件类型是否为checkbox,且条形码列表和数据列表不为空
  if (this.componentType === 'checkbox' && this.barCodeList.length && this.dataList) {
  	  // 遍历单据列表中的每个项
	  this.dataList.forEach((item, index) => {
	    // 遍历条形码列表,寻找匹配的条形码
	    this.barCodeList.forEach(ele => {
	      if (item.sBarCode === ele.sBarCode) {
	        // 使用$nextTick确保DOM更新后操作复选框
	        this.$nextTick(() => {
	          // 切换复选框的选中状态
	          this.$refs.checkboxes[index].toggle();
	          // 设置匹配条形码的项为禁用状态
	          this.$set(item, 'disabled', true);
	        });
	      }
	    });
	  });
	}
},

/**
 * 处理代码检查操作。
 * 根据列表中所有项目的codeCheck状态,更新对象的orderCheck和isPartiallyChecked属性。
 * 同时,根据对象的状态,更新dataList中其他项目的orderCheck和isPartiallyChecked属性。
 * 
 * @param {boolean} val - 当前项的codeCheck值。
 * @param {Array} list - 包含所有项目的数据列表。
 * @param {Object} obj - 当前正在处理的对象。
 */
handleCodeCheck(val, list, obj) {
	// 检查列表中所有项目的codeCheck是否都为true
    const allCodeChecked = list.every(item => item.codeCheck === true);
    // 检查列表中所有项目的codeCheck是否都为false
    const allCodeUnchecked = list.every(item => item.codeCheck === false);

	// 如果所有项目都已检查
    if (allCodeChecked) {
        obj.orderCheck = true;
        obj.isPartiallyChecked = false;
    // 如果所有项目都未检查
    } else if (allCodeUnchecked) {
        obj.orderCheck = false;
        obj.isPartiallyChecked = false;
    // 如果项目状态混合
    } else {
        obj.isPartiallyChecked = true;
    }

	// 遍历dataList,根据当前对象的状态,更新其他项目的状态
    this.dataList.forEach(item => {
    	// 如果当前对象的orderCheck或isPartiallyChecked为true,并且当前项不属于当前对象
        if ((obj.orderCheck || obj.isPartiallyChecked) && (item.sDistId !== obj.sDistId)) {
            item.orderCheck = false;
            item.isPartiallyChecked = false;
            // 遍历当前项的条码列表,设置所有条码的codeCheck为false
            item.sBarCodeList.forEach(ele => {
                this.$set(ele, 'codeCheck', false);
            });
        }
    });
}

/**
 * 处理复选框的点击事件。
 * 当一个复选框被选中或取消选中时,此函数将更新相关项的选中状态。
 * 如果一个项的部分条码被选中,则该项将被标记为部分选中状态。
 * 
 * @param {Object} val - 被点击的复选框对应的项对象。
 *         val.orderCheck - 该项的选中状态。
 *         val.sDistId - 该项的分布ID。
 *         val.isPartiallyChecked - 该项是否被部分选中。
 *         val.sBarCodeList - 该项包含的条码列表。
 */
handleCheck(val) {
  // 遍历数据列表中的每一项
  this.dataList.map(ele => {
    // 如果当前项的选中状态为true,并且其分布ID与被点击项的分布ID不同
    if (val.orderCheck && (ele.sDistId !== val.sDistId)) {
      // 将当前项的选中状态和部分选中状态设置为false
      this.$set(ele, 'orderCheck', false);
      this.$set(ele, 'isPartiallyChecked', false);
      // 遍历当前项的条码列表,将每个条码的选中状态设置为false
      ele.sBarCodeList.forEach(item => {
        this.$set(item, 'codeCheck', false)
      })
    }
  })
  // 如果被点击项的部分选中状态为true,则将其部分选中状态设置为false,选中状态设置为true
  if (val.isPartiallyChecked) {
    val.isPartiallyChecked = false;
    val.orderCheck = true;
  }
  // 遍历被点击项的条码列表,将每个条码的选中状态设置为被点击项的选中状态
  val.sBarCodeList.forEach(item => {
    this.$set(item, 'codeCheck', val.orderCheck)
  })
},

/**
 * 处理关闭操作的逻辑。
 * @param {string} action 关闭操作的类型,可以是'confirm'确认或'cancel'取消。
 * @param {Function} done 回调函数,根据操作结果进行处理。
 */
handleClose(action, done) {
  if (action === 'confirm') {
    let selectItem = [];
    if (this.componentType === 'collapse') {
      this.dataList.forEach(item => {
        if (item.orderCheck || item.isPartiallyChecked) {
          item.sBarCodeList.forEach(ele => {
            if (ele.codeCheck) {
              selectItem.push(ele.sBarCode);
            }
          })
        }
      })
    }
    if (this.componentType === 'checkbox') {
      // 使用Set存储checkList以便快速查找
      const checkSet = new Set(this.checkList);
      // 过滤dataList,找出sBarCode存在于checkSet中的对象
      const matchedObjects = this.dataList.filter(item => checkSet.has(item.sBarCode));
      selectItem = [...matchedObjects];
    }
    if (this.componentType === 'radio' && this.selectedItem) {
      selectItem.push(this.selectedItem)
    }
    
    // 根据选择的条码执行相应操作
    if (selectItem.length) {
      if (this.componentType === 'checkbox') {
        this.$emit('commitPdaDist', selectItem)
        this.checkList = [];
      } else this.$emit('commitPdaDist', (selectItem.join(',')));
      done(true)
    } else {
      // 没有选择条码时的处理
      done(false)
      this.$failPlay('请先选择条码');
    }
  }
  if (action === 'cancel') {
    // 取消操作时的处理逻辑
    done(true)
    this.dataList = [];
    this.checkList = [];
    this.activeName = '';
    this.selectedItem = [];
  }
},
  1. 响应式设计与性能优化
    通过设置.van-dialog__body的高度自适应,确保不同设备下组件的表现一致。此外,对大量条码展示采用van-list分页加载,避免一次性渲染大量数据导致的性能问题。

组件用法

基本使用
<choose-barcode 
  ref="chooseBarcode"
  :title="dialogTitle"
  :udi-list="barcodesData"
  component-type="checkbox"
  @commitPdaDist="handleSelectedBarcodes"
/>
const handleSelectedBarcodes = (code="") => {
	...
	$axios(url, params).then(res => {
		barcodesData = res.data;
		$refs.chooseBarcode.localShowDialog = true;
		...
	})
	...
}
Props
  • title: 对话框标题,默认为"请选择条码"。
  • udiList: 数据源数组,包含条码信息。
  • componentType: 显示模式,可选值有collapse, checkbox, radio
  • barCodeList: 父组件已选择过的数据,用于复选框模式数据回显。
Events
  • commitPdaDist(selectedBarcodes): 当用户确认选择后触发,selectedBarcodes为选中条码数据。

实现细节

组件内部使用了 Vant UIvan-dialog 和其他组件来构建界面。通过 v-model 和自定义事件,实现了数据的双向绑定和交互逻辑。特别地,手风琴模式下,我们使用了 van-collapsevan-collapse-item 来展示条码分类,并通过 van-checkbox 控制子项的选中状态。

结语

通过上述介绍,我们可以看到,自定义条码选择对话框组件不仅提供了丰富的功能,还具有高度的灵活性和可扩展性。它能够适应各种应用场景,极大地提升了条码管理和选择的效率。希望本文能帮助你在项目中更好地应用和定制此类组件,提升用户体验和开发效率。

在实际应用中,这个组件可以进一步扩展,例如支持更多展示类型、优化性能和提升用户体验等。希望这篇博客能够为你在开发中提供一些启发和帮助!

如果你在使用这个组件时遇到任何问题,或者有改进建议,欢迎在评论区留言讨论!

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洋茄子炒鸡蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值