使用VUE自定义组件封装部门选择功能

news2024/11/15 10:38:43

背景

照惯例,先交待下背景,从真实需求出发,讲述实现效果、设计思路和实现方式。
软件系统中,会有一些常见常用的选择功能,如部门选择、人员选择等,用于填报表单,使用频率很高。直接使用一方面会比较繁琐,另一方面造成代码重复,当需要调整时,则需要遍历整个项目源码,容易因漏改引发问题。这种情况下,更好的实现方案,是通过封装组件,来实现简化使用和复用的目的。
前面有一篇,介绍了使用VUE自定义组件封装数据字典,功能比较简单,今天进一步,封装一个比较复杂的部门单选组件,其他业务实体,如人员、角色等,实现非常类似,就不再赘述。

实现效果

首先展示下实现效果。
用户管理列表,左边是部门树,右侧是用户列表,如下图。
在这里插入图片描述
点击“新增”按钮,打开用户编辑页面,自动显示传入的部门。
在这里插入图片描述

第一行就是我们封装的部门单选组件,点击最右侧的图标,弹出部门选择页面来。
在这里插入图片描述

选择新部门后,确定,即可实现部门的变更。

站在使用的角度看,也非常简单,用户编辑页面整体源码如下

<template>
  <el-dialog :title="title" :visible="visible" @close="visible=false">
    <el-form
      ref="form"
      :model="entityData"
      :rules="rules"
      label-width="80px"
      label-position="right"
      style="width:90%;margin:0px auto;"
    >
      <!--表单区域 -->
      <el-form-item label="组织机构" prop="organizationId">
        <organization-single-select
          v-model="entityData.organizationId"
        />
      </el-form-item>
      <el-form-item label="账号" prop="account">
        <el-input v-model="entityData.account" />
      </el-form-item>
      <el-form-item label="姓名" prop="name">
        <el-input v-model="entityData.name" />
      </el-form-item>
      <el-form-item label="职务" prop="position">
        <el-input v-model="entityData.position" />
      </el-form-item>
      <el-form-item label="性别" prop="gender">
        <dictionary-radio-group
          v-model="entityData.gender"
          :code="constant.DICTIONARY_TYPE_CODES.GENDER"
        />
      </el-form-item>
      <el-form-item label="出生日期" prop="birthday">
        <el-date-picker
          v-model="entityData.birthday"
          value-format="yyyy-MM-dd HH:mm:ss"
          format="yyyy年MM月dd日"
          type="date"
          placement="bottom-end"
          placeholder="请选择"
          class="form-item"
        />
      </el-form-item>
      <el-form-item label="手机号" prop="telephone">
        <el-input v-model="entityData.telephone" />
      </el-form-item>
      <el-form-item label="邮箱地址" prop="email">
        <el-input v-model="entityData.email" />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <dictionary-radio-group
          v-model="entityData.status"
          :code="constant.DICTIONARY_TYPE_CODES.STATUS"
        />
      </el-form-item>
      <el-form-item label="排序号" prop="orderNo">
        <el-input v-model="entityData.orderNo" />
      </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button v-show="saveButtonVisible" type="primary" @click="save">保存</el-button>
      <el-button @click="close">关闭</el-button>
    </div>
  </el-dialog>
</template>

,引入部门单选组件后,具体使用的时候,只需要如下代码即可


  <el-form-item label="组织机构" prop="organizationId">
    <organization-single-select
      v-model="entityData.organizationId"
    />
  </el-form-item>

也就是说,封装组件后,使用部门单选功能,跟使用一个文本输入框类似,所有与之相关的页面展现和逻辑处理都在组件内部实现了,是不是对使用方非常友好?

设计与实现

技术栈采用的还是vue2.0,UI组件库使用element ui。

使用简单,是因为将复杂工作封装在了内部,这里同样用组件化的思路,将部门单选组件拆分成了两个vue页面,一个是整体组件,负责文本框的展示,对弹出部门选择页面的调度,另一个是负责具体的部门选择。

先附上整体组件源码

<template>
  <div>
    <el-input v-model="organizationName" disabled>
      <el-button slot="append" icon="el-icon-s-grid" @click="openModal" />
    </el-input>

    <organization-modal ref="organizationModel" @ok="handleOK" />
  </div>
</template>

<script>
import OrganizationModal from './OrganizationModal'
export default {
  name: 'OrganizationSingleSelect',
  components: {
    OrganizationModal
  },
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    width: {
      type: Number,
      default: 500,
      required: false
    },
    value: {
      type: String,
      default: '',
      required: false
    },
    disabled: {
      type: Boolean,
      required: false,
      default: false
    }

  },
  data() {
    return {
      visible: false,
      organizationName: ''
    }
  },
  watch: {
    value: {
      immediate: true,
      handler: 'handleValue'
    }
  },
  methods: {
    openModal() {
      this.$refs.organizationModel.show(this.value, this.organizationName)
    },
    handleValue() {
      if (this.value) {
        this.$api.system.organization.get(this.value)
          .then((res) => {
            this.organizationName = res.data.name
          })
      }
    },
    handleOK(id) {
      // 更新父组件绑定值
      this.$emit('change', id)
    }
  }
}
</script>

<style scoped>
</style>

下面重点说下封装需要注意的点。

1.UI元素比较简单,就一个文本框,默认设置为禁用状态,并且追加了一个按钮,用于触发部门选择页面的弹出。

 <el-input v-model="organizationName" disabled>
      <el-button slot="append" icon="el-icon-s-grid" @click="openModal" />
 </el-input>

在这基础上当然也可以进行功能扩展,如再追加一个清空已选择的按钮,视需求而定。
2.很重要的一点,是设置model选项。因为默认情况下,model使用名为 value 的 prop 和名为 input 的事件,而我们封装的效果是选择控件,将文本框禁用了,事件应该使用chang而不是input,所以需要做如下设置:

 model: {
    prop: 'value',
    event: 'change'
  }

3.为了组件的可配置性,设置了部分prop属性,如宽度、是否禁用等,这样在使用的时候,就能通过属性绑定的方式灵活配置了。

 width: {
    type: Number,
    default: 500,
    required: false
  },
  value: {
    type: String,
    default: '',
    required: false
  },
  disabled: {
    type: Boolean,
    required: false,
    default: false
  }

4.通过vue的watch机制,监视value的变化,该值变化后,调用后端部门服务接口,拿到部门名称后更新显示。

  watch: {
    value: {
      immediate: true,
      handler: 'handleValue'
    }
  },
   ……
  handleValue() {
      if (this.value) {
        this.$api.system.organization.get(this.value)
          .then((res) => {
            this.organizationName = res.data.name
          })
      }
  }

5.选择项变化时,通过change事件,调用emit,把最新的值传递给使用方,这一步很关键。

 change(value) {
      this.$emit('change', value)
 }

接下来看下部门选择页面的实现,完整源码如下:

<template>
  <div>
    <el-dialog title="组织机构——单选" :visible="visible" width="400px" append-to-body @close="close">
      <el-input v-model="searchValue" placeholder="请输入关键字过滤" style="margin-bottom:10px" />
      <el-tag>当前机构:{{ selectedName }}</el-tag>
      <el-tree
        ref="tree"
        :data="treeData"
        node-key="id"
        :default-expanded-keys="defaultExpandedKeys"
        :filter-node-method="filterNode"
        @current-change="handleTreeSelectChange"
      />

      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="confirm">确定</el-button>
        <el-button @click="close">关闭</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>

export default {

  data() {
    return {
      visible: false,
      treeData: [],
      searchValue: '',
      defaultExpandedKeys: [],
      selectedValue: '',
      selectedName: ''
    }
  },
  watch: {
    searchValue(value) {
      this.$refs.tree.filter(value)
    }
  },
  methods: {
    show(id, name) {
      this.searchValue = ''
      this.defaultExpandedKeys = []
      this.selectedValue = id
      this.selectedName = name
      this.loadTree()
      this.visible = true
    },
    loadTree() {
      this.$api.system.organization.tree()
        .then(res => {
          this.treeData = res.data
          // 默认展开根节点
          this.defaultExpandedKeys.push(this.treeData[0].id)
          // 默认展开当前节点
          this.defaultExpandedKeys.push(this.selectedValue)
        })
    },
    close() {
      this.visible = false
    },
    confirm() {
      this.$emit('ok', this.selectedValue)
      this.visible = false
    },
    // 树节点选中改变
    handleTreeSelectChange(data) {
      this.selectedValue = data.id
      this.selectedName = data.label
    },
    filterNode(value, data) {
      if (!value) return true
      return data.label.indexOf(value) !== -1
    }

  }
}

</script>

<style scoped>
</style>

具体功能包括了数据加载、默认展开、显示已选择值、搜索功能,已经可以满足常见的需求了。

多选功能的实现

上面实现了单选功能,其实多选功能实现也类似,这里只放代码,就不再展开介绍了

<template>
  <div>
    <el-input v-model="organizationName" disabled>
      <el-button slot="append" icon="el-icon-s-grid" @click="openModal" />
    </el-input>

    <organization-modal ref="organizationModel" @ok="handleOK" />
  </div>
</template>

<script>
import OrganizationModal from './organizationModal'
export default {
  name: 'OrganizationMultipleSelect',
  components: {
    OrganizationModal
  },
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {
      type: String,
      default: '',
      required: false
    },
    width: {
      type: Number,
      default: 500,
      required: false
    },
    disabled: {
      type: Boolean,
      required: false,
      default: false
    }

  },
  data() {
    return {
      visible: false,
      organizationName: ''
    }
  },
  watch: {
    value: {
      immediate: true,
      handler: 'handleValue'
    }
  },

  methods: {
    openModal() {
      this.$refs.organizationModel.show(this.value)
    },
    handleValue() {
      if (this.value) {
        const idList = this.value.split(',')
        this.$api.system.organization.getOrganization({ idList: idList })
          .then((res) => {
            this.organizationName = res.data.map(x => x.name).join(',')
          })
      }
    },
    handleOK(value) {
      // 处理父组件绑定值
      this.$emit('change', value.join(','))
    }
  }
}
</script>

<style scoped>
</style>

<template>
  <div>
    <el-dialog title="组织机构——多选" :visible="visible" width="400px" append-to-body @close="close">
      <el-input v-model="searchValue" placeholder="请输入关键字过滤" style="margin-bottom:10px" />

      <el-tree
        ref="tree"
        :data="treeData"
        node-key="id"
        show-checkbox
        :default-expanded-keys="defaultExpandedKeys"
        :filter-node-method="filterNode"
        :default-checked-keys="checkedNodesId"
      />

      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="confirm">确定</el-button>
        <el-button @click="close">关闭</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>

export default {

  data() {
    return {
      visible: false,
      treeData: [],
      searchValue: '',
      defaultExpandedKeys: [],
      selectedValue: [],
      checkedNodesId: []

    }
  },
  watch: {
    searchValue(value) {
      this.$refs.tree.filter(value)
    }
  },

  methods: {
    show(idList) {
      this.searchValue = ''
      this.defaultExpandedKeys = []
      this.selectedValue = idList
      this.loadTree()
      this.visible = true
    },
    loadTree() {
      this.$api.system.organization.tree()
        .then(res => {
          this.treeData = res.data
          // 默认展开根节点
          this.defaultExpandedKeys.push(this.treeData[0].id)

          this.checkedNodesId = []
          this.getLeafNodeChecked(this.treeData)
          this.$refs.tree.setCheckedKeys(this.checkedNodesId)
        })
    },
    close() {
      this.visible = false
    },
    confirm() {
      this.$emit('ok', this.$refs.tree.getCheckedKeys())
      this.visible = false
    },

    filterNode(value, data) {
      if (!value) return true
      return data.label.indexOf(value) !== -1
    },
    getLeafNodeChecked(node) {
      // 遍历树节点,设置
      for (const treeNode of node) {
        // 如果节点有子节点,那他的选中状态不被考虑,继续往下找
        if (treeNode.children && treeNode.children.length > 0) {
          this.getLeafNodeChecked(treeNode.children)
        } else {
          // 是叶子节点,如果是check状态就记录
          if (this.selectedValue.includes(treeNode.id)) {
            this.checkedNodesId.push(treeNode.id)
          }
        }
      }
    }

  }
}

</script>

<style scoped>
</style>

最后需要说一下的是,通过组件化的思想,可以将复杂功能拆分成一个个小的功能组件,降低复杂性和提高复用度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈应用安全测试工具

正确的应用程序安全测试工具可以改善企业安全态势和开发工作流程。如今&#xff0c;应用程序安全从一开始就内置在整个软件生命周期中&#xff0c;即使是具有成熟开发实践的组织也需要自动化工具来在复杂、快速变化的环境中成功地保护他们的软件。以下比较了三个广泛使用的应用…

MAVEN打包这一篇就够了

Maven打包说明IDEA目录结构Java代码从编码到最后运行到两个核心步骤为"编译"和"执行"。"编译"会根据"源代码"产出对应的".class"文件&#xff0c;而"执行"就是让程序运行起来&#xff0c;运行的对象就是这些"…

【C语言学习笔记】:动态库

一、动态库 通过之前静态库那篇文章的介绍。发现静态库更容易使用和理解&#xff0c;也达到了代码复用的目的&#xff0c;那为什么还需要动态库呢&#xff1f; 1、为什么还需要动态库&#xff1f; 为什么需要动态库&#xff0c;其实也是静态库的特点导致。 ▶ 空间浪费是静…

怎么打造WhatsApp Team?SaleSmartly(ss客服)告诉你

关键词&#xff1a;WhatsApp Team SaleSmartly&#xff08;ss客服&#xff09; 您是否正在寻找一种让您的团队能够在 WhatsApp协作消息传递的解决方案?拥有了 WhatsApp Team&#xff0c;不仅效率提升&#xff0c;还可以在智能聊天工具中比如SaleSmartly&#xff08;ss客服&…

51单片机——中断系统,小白讲解,相互学习

中断介绍 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的&#xff0c;中断功能的存在&#xff0c;很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一&#xff0c;是我们学些单片机必须要掌握的。 为了更容易的理解中断概念&…

算法思想 - 动态规划算法

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中&#xff0c;可能会有许多可行解。每一个解都对应于一个值&#xff0c;我们希望找到具有最优值的解。动态规划算法与分治法类似&#xff0c;其基本思想也是将待求解问题分解成若干个子问题&#xff0c;先求解子问…

哈希表题目:判断路径是否相交

文章目录题目标题和出处难度题目描述要求示例数据范围解法一思路和算法代码复杂度分析解法二思路和算法代码复杂度分析题目 标题和出处 标题&#xff1a;判断路径是否相交 出处&#xff1a;1496. 判断路径是否相交 难度 3 级 题目描述 要求 给你一个字符串 path\texttt…

【软考系统架构设计师】2022下案例分析历年真题

【软考系统架构设计师】2022下案例分析历年真题 【软考系统架构设计师】2022下案例分析历年真题【软考系统架构设计师】2022下案例分析历年真题2022下案例分析历年真题第一题&#xff08;25分&#xff09;2022下案例分析历年真题第二题&#xff08;25分&#xff09;2022下案例分…

使用纹理(Textures)

当物体表面并非是纯色的时候&#xff0c;比如带波点&#xff0c;斑纹或者表面有刮痕或被裂纹等&#xff0c;这些效果该如何实现呢&#xff1f; 这里我们需要提到一个概念是贴图&#xff08;Maps&#xff09;。Maps是覆盖在游戏物体上的2D图片&#xff0c;用来设置表面的颜色、s…

大数据-学习实践-1相关Linux

大数据-学习实践-1相关Linux (大数据系列) 文章目录大数据-学习实践-1相关Linux1知识点2具体内容2.1安装、使用2.2高级命令2.2.1文件2.2.2日期2.2.3进程2.2.4三剑客 (grep、sed、awk)2.3高级配置2.3.1分配IP&#xff1a;静态IP设置2.3.2起名&#xff08;hostname&#xff09;&…

工程经验:残差连接对网络训练的巨大影响

文章目录1、没有使用残差连接的网络难以训练2、loss 不下降的原因3、使用了残差连接的网络可以高效训练1、没有使用残差连接的网络难以训练 经典的 SegNet 网络结构如下&#xff1a; 在使用上图所示的 SegNet 作为噪声预测网络训练扩散模型&#xff08;DDPM&#xff09;时&…

Elasticsearch汉字补全和智能纠错使用详解

1 使用ES实现的效果 汉字补全 拼写纠错

Python自动化测试【软件测试最全教程(附笔记、学习路线)】,看完即就业

最近看到很多粉丝在后台私信我&#xff0c;叫我做一期Python自动化测试的教程&#xff0c;其实关于这个问题&#xff0c;我也早就在着手准备了&#xff0c;我录制了一整套完整的Python自动化测试的教程&#xff0c;上传到网盘里了&#xff0c;大家有兴趣的可以去文末交流群免费…

[架构之路-107]-《软考-系统架构设计师》-0-系统分析师与系统架构设计师简介与官网介绍

官网链接&#xff1a;https://www.ruankao.org.cn/index/ind计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试简介计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;以下简称计算机软件资格考试&#xff09;是原中国计算机软件专业技术…

化学试剂Glutaric Acid-PEG-Glutaric Acid,GA-PEG-GA,戊二酸-聚乙二醇-戊二酸

一&#xff1a;产品描述 1、名称 英文&#xff1a;Glutaric Acid-PEG-Glutaric Acid&#xff0c;GA-PEG-GA 中文&#xff1a;戊二酸-聚乙二醇-戊二酸 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Carboxylic acid PEG 4、分子量&#xff1a;可定制&#xff0c; 戊…

如果网站的 Cookie 特别多特别大,会发生什么(一)

有没有想过&#xff0c;如果网站的 Cookie 特别多特别大&#xff0c;会发生什么情况&#xff1f; 不多说&#xff0c;马上来试验一下&#xff1a; for (i 0; i < 20; i) document.cookie i X.repeat(2000) 什么&#xff0c;网站居然报错了&#xff1f; 众所周知&am…

【Unity VR开发】结合VRTK4.0:自身移动(滑动)

语录&#xff1a; 依山傍水房树间&#xff0c;行也安然&#xff0c;住也安然&#xff1b; 一条耕牛半顷田&#xff0c;收也凭天&#xff0c;荒也凭天&#xff1b; 雨过天晴驾小船&#xff0c;鱼在一边&#xff0c;酒在一边&#xff1b; 夜晚妻子话灯前&#xff0c;今也谈谈…

考研复试机试 | C++

目录1.盛水最多的容器<11>题目代码&#xff1a;2.整数转罗马数字题目&#xff1a;代码&#xff1a;3. 清华大学机试题 abc题目题解4.清华大学机试题 反序数题目描述代码对称平方数题目代码&#xff1a;5. 杭电上机题 叠筐题目&#xff1a;代码pass&#xff1a;关于清华大…

Windows server——部署DNS服务(3)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.管理DNS服务 1.子域 案例 2. 委派 案例 1&#xff09;添加主机记录 …

替代AG9300|替代NCS8823|CS5260 Type-C转VGA视频转换方案

替代AG9300|替代NCS8823|CS5260 Type-C转VGA视频转换方案 CS5260是一款是一款实现USB TYPE-C到VGA视频转换的单片机解决方案转换器。CS5260支持USB Type-C显示端口交替模式&#xff0c;CS5260可以将视频和音频流从USB Type-C接口传输到VGA端口。在CS5260芯片中&#xff0c;显示…