UI 提供的 progress-step 要怎么实现?
前言
这天突然收到了 UI
修改设计稿的消息通知:“xxx 已修改 xxx 项目并 @ 了你,请及时查看变更内容
”,一条、两条、三条 …,修改消息铺天盖地而来,然后就什么都看不到了()因为我选择开启消息免打扰
,但没多久产品就非常贴心的询问是否已经收到了对应的消息,自然免不了还要介绍一下本次修改的内容和原因(
)。消息免打扰失效
于是,我()打开了设计稿正打算好好欣赏欣赏,不曾想一道光芒一闪而过(心甘情愿的
),看到了这样的内容:是谁拉开了窗帘
而这个显示方式之前使用的是 Steps 步骤条 的方式展示的,类似于:
意味着要从一个 Steps 步骤条 改变成一个 progress-step 流程节点 的形式,这很难实现吗?不难!很容易实现吗?倒也未必!
【扩展】假设现在有个面试官就用这个 progress-step
组件作为场景题,想想你该如何描述对这个组件的设计思路!!!
组件设计基本原则
撸起袖子一把梭?
千万先别急着撸起袖子开干,咱们先来聊聊组件设计的基本原则,往往一把梭哈的代码容易形成一个糅杂着各种逻辑的组件,因为这样的设计是脆弱的,很容易带来副作用和难以预知的结果,需求不变更还好,需求要是频繁变更,那么主要问题就会出现,可能因为一个原因去改变组件,就会破坏其他的职责(行为),于是你不得不在多处都进行修改。
怎么知道一个组件复不复杂,这就要看组件内部到底维护了多少和自身状态相关的数据,即非 props
传入的数据,对于现在的 Vue / React
而言都是可以通过 数据来驱动视图 的,用一个函数式表示即 UI = render(data)
或 UI = f(data)
,这意味着一个组件至少需要做到 数据(data) 和 视图(UI) 的解耦。
单一职责
单一职责原则(SRP - single responsibility principle) 中的 职责 是什么,你可以理解为是组件的 行为,这个行为可以是渲染一个列表、展示一张图片、发起一个请求等等,当组件的这个行为发生修改,意味着这个组件本身发生了变化,此时该组件就是 单一职责 的。
为什么不能多职责?
大部分人经常会选择忽视一个组件的多个职责带来的缺陷,这里举个常见查询页面的例子:
- 一部分是和查询条件相关的表单部分
- 负责和用户进行各种交互,比如表单联动、表单重置、表单折叠等等,同时它需要收集、提供用户已经填入的数据
- 一部分是展示查询结果的表格部分
- 将接口响应的数据展示在表格中,同时也要支持与用户的各种交互,比如切换页码、页大小、上一页、下一页、跳转页等
- 一部分是负责将表单数据发送给服务器进行数据交互,即发起请求
假设此时按照 撸起袖子一把梭 的原则去编写这个组件(页面),一定会将表单交互、表格交互、发起请求、处理响应等逻辑混在一个组件(页面)当中:
- 表单的交互逻辑需要维护(行为 1)
- 表格的交互逻辑需要维护(行为 2)
- 请求的发起和响应的处理也需要维护(行为 3)
假设现在接口响应的数据结构发生了变化,在当前的组件(页面)中,你可能需要重新修改处理判断不同响应状态码下页面的表现,同时还得修改表格的展示方式,又或者需要根据响应的数据内容自动填充表单,但由于数据结构的变化还得去调整表单相关的部分,此时仅仅一个数据结构改变的原因就可能影响了三个部分的内容,因为它们耦合了。
多职责的缺陷:
- 组件内容相互耦合,当需求变更时存在相互破坏的可能
- 组件内容各个部分的逻辑杂糅,不易于阅读和维护
- 经过不断的迭代会使得单个组件代码量不断增大
让组件具有单一职责
关于这一点,你完全可以从你平时在项目中使用到的 UI
组件库中去看看,比如 Element UI
中表单相关的组件就分为:
- Form 组件负责整体表单的展示形式和对整体数据的处理
- FormItem 组件单个具体的表单组件的展示形式和单个表单的数据处理
- Input、Select、Option 等等具体的表单组件
基于此还是将上述的例子改变成单一职责:
- 表单部分需要单独封装成组件,主要提供表单展示形式、交互能力、数据处理能力
- 表格部分需要单独封装成组件,主要负责提供对数据展示、交互的能力
- 查询页面(组件)负责提供发起请求数据和处理数据响应的能力,并将表单组件和表格组件组合在一起
这样一来,即便后续响应的数据结构发生改变,也不需要再去修改表单和表格部分的内容,而是在当前的查询页面(组件)中处理好对应的数据格式即可,比如将接收到的数据处理成符合表格或表格组件需要的格式即可。
单一职责的优点:
- 单个组件的内容独立分离,需求变更时只需要修改对应组件
- 单个组件逻辑只存在于该组件中,保证组件代码的简洁性、易读性和维护性
- 在不断迭代中,只需要修改对应组件的内容,不会导致页面中的其他组件代码量增大
通用性
组件的核心是需要体现在业务中的,在项目中的大多数组件都属于业务组件,不具备通用性,因为具有通用性的组件不应该只满足于某个业务,因此组件的设计要考虑从业务中抽离。
设计通用性组件应该考虑什么
- 数据(data) 和 视图(UI) 的解耦
- 见过不少人封装组件就真的只是将原页面内容单纯的剪切出去,视图部分大多是硬编码,除此之外组件维护了七七八八的状态,巴不得这个组件在外部使用的时候什么都不用传递,这样的组件显然不具备通用性,因此一定要将数据和视图进行解耦
- 保证组件的 单一职责(行为)
- 复用一个组件时,是为了重复使用其职责(行为),而只有单一职责的组件才能够被更好的复用,多个单一职责的组件就能更好的实现 组合性,如果一个组件错误地拥有多个职责时,就会增加复用时的开销
- 组件只提供 最基础的 DOM 和 交互逻辑
- 不要让一个组件将视图相关的 DOM 完全掌控在内部,应该将容易变化的部分交给使用者自己去定义(如利用插槽),比如
Element UI
中的Table
组件就提供了render-header(Table-column Attributes)、header(Table-column Scoped Slot)、append(Table Slot)
用于自定义渲染
- 不要让一个组件将视图相关的 DOM 完全掌控在内部,应该将容易变化的部分交给使用者自己去定义(如利用插槽),比如
- 组件封装应该 隐藏内部细节和实现意义,通过
props
控制具体行为和输出- 保证组件的纯度,减少组件的副作用,比如不要在组件中直接使用一些全局变量等,因为这不符合封装的特性,还容易带来不可预测的行为
- 组件封装保证 可测试性
- 组件的可测试性也决定着组件封装的通用性,如果在为一个组件编写测试用例时需要非常复杂,那么大致是组件设计存在问题
设计 progress-step 组件
接下来,就该看看到底该如何分析和设计这个 progress-step 组件了,上面我们提到了最基本的要做到 数据(data) 和 视图(UI) 的解耦,那实际上也就意味着要从这两个大的方向去考虑。
视图(UI)
确定视图展示方式
拿到视图肯定要进行分析,UI 图展示的不一定全面,因此必须要确定所有可能的展示方式,在和产品沟通的过程中确认了这个视图的三种展示形态:
-
串行展示,类似于:
-
并行展示,类似于:
-
混合展示,类似于:
但你仔细查看 UI 稿的设计,其实完全可以将它归类为第二种展示方式,否则你还得为这个 UI 图单独实现另一种展示方式,并且在和产品的沟通中也得到了同意。
确定实现方式
首先,肯定得去看看社区中是否有类似的方案可以直接使用,或者经过小的改动可以被使用的,奈何业务就是业务,果然没找到合适的,但在查找方案的时候也了解到了几种实现方案:
- 基于普通 DOM 元素实现
- 基于 SVG 实现
- 基于 Canvas 实现
最后两种方案,我看到的大多是需要支持各种可比较复杂的拖拽、复制、连接的方式,并且其具体实现也是比较复杂,如果其其不是很熟悉的话,无论在实现还是在后续的各种调整上会花费大量时间,而且如上的一个需求无非是一些展示和简单的交互,并不需要涉及如此复杂的各种自定义操作。
再举个例子,比如前面提到的 Steps 步骤条 也是基于普通 DOM 元素实现的,并且也确实没有太多需要用户自定义的操作。因此,可以将 progress-step 组件当做 Steps 步骤条 的升级版,另外考虑开发时间的限制,选择方案一是最合适的。
确定实现细节
上述展示方式虽然有三种,但实现时可以先实现最简单的 串行展示 方式, 而 并行展示 其实相当于三条串行展示的合并,分别是上边、中间、下边的串行方式,最后的 混合展示 其实只要你实现了前面两种方式,这种方式无非是相当于组件的递归渲染,只不过位置上需要做一些处理:
- 串行展示
- 单节点的 虚线 和 箭头 可以借助元素的伪元素
::before、::after
并通过定位实现
- 单节点的 虚线 和 箭头 可以借助元素的伪元素
- 并行展示
- 上下边的部分仍然可以看做是串行节点的展示
- 中间的部分可以通过一个元素来实现边框效果,并且可以认为是前后节点与这个边框的串行版本
- 混合展示
- 实现了前面两种模式,混合展示方式其实就只是根据数据来进行组件递归渲染
数据(data)
作为前端肯定要具备看到 UI 就能大致设想出其对应 data 的基本结构,而且上述经过视图分析之后,已经得到其对应的三种具体展示形式,在真正开始写代码前,请先把需要封装的组件涉及的核心数据结构给设计好,毕竟这个数据最终是需要从外部传入的:
-
串行展示
- 多个节点,意味着整体应该设计为一个数组
- 单个节点即对应数组的每一项元素信息
data = [{status: 'completed' , name: '完成' }, {status: 'processing' , name: '当前处理节点' }, {status: 'pending' , name: '待处理' } ]
-
并行展示
- 可以看做是上下两个串行展示的合并,可将数据带有并行的节点也使用数组来表示
data = [{status: 'completed' , name: '开始' }, [ // 1. 这个数组表示是并行节点[ // 1.1 这个数组表示是并行节点中,上边 串行节点的数据{status: 'completed' , name: '完成' }, {status: 'processing' , name: '当前处理节点' }, {status: 'pending' , name: '待处理' }],[ // 1.2 这个数组表示是并行节点中,下边 串行节点的数据{status: 'completed' , name: '完成' }, {status: 'processing' , name: '当前处理节点' }, {status: 'pending' , name: '待处理' }]],{status: 'pending' , name: '结束' }, ]
- 混合展示
- 相当于并行节点中又包含并行节点,类似于:
data = [{status: 'completed' , name: '开始' }, [ // 1. 这个数组表示是并行节点[ // 1.1 这个数组表示是并行节点中,上边 串行节点的数据{status: 'completed' , name: '完成' }, [// 1.1 这个数组表示是并行节点中的并行节点[// 1.1.1 这个数组表示是并行节点中,上边 串行节点的数据{status: 'processing' , name: '当前处理节点' }], [// 1.1.2 这个数组表示是并行节点中,下边 串行节点的数据{status: 'processing' , name: '当前处理节点' }], ],{status: 'pending' , name: '待处理' }],[ // 1.2 这个数组表示是并行节点中,下边 串行节点的数据{status: 'completed' , name: '完成' }, {status: 'processing' , name: '当前处理节点' }, {status: 'pending' , name: '待处理' }]],{status: 'pending' , name: '结束' }, ]
实现 progress-step 组件
其大部分的实现思路已经在上面介绍过了,这里就不再额外介绍一些样式计算相关的内容,下面直接展示效果和源码。
props 简介
- data:要展示的节点数据
- colors:不同节点状态的颜色
- status:不同节点状态的值
- size:节点图标的大小
- stepWidth:节点占据的宽度
- space:节点之间的间距
const props = withDefaults(defineProps<PropsType>(), {data: () => [],colors: () => ["#d2d2d2", "#3a84fb", "#67d36f"],status: () => ["pending", "processing", "completed"],size: 25,stepWidth: 80,space: 20,
});
效果展示
串行展示
const data = [{ status: "completed", title: "开始", description: "这是描述" },{ status: "processing", title: "处理中", description: "这是描述" },{ status: "pending", title: "待处理", description: "这是描述" },{ status: "pending", title: "结束", description: "这是描述" },
]
并行展示
const data = [{ status: "completed", title: "开始", description: "这是描述" },[[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "completed", title: "已完成", description: "这是描述" },{ status: "pending", title: "待处理", description: "这是描述" },],[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "pending", title: "待处理", description: "这是描述" },],],{ status: "pending", title: "结束", description: "这是描述" },
]
混合展示
const data = [{ status: "completed", title: "开始", description: "这是描述" },[[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "completed", title: "已完成", description: "这是描述" },[[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "pending", title: "待处理", description: "这是描述" },],[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "processing", title: "处理中", description: "这是描述" },]],{ status: "pending", title: "待处理", description: "这是描述" },],[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "completed", title: "已完成", description: "这是描述" },[[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "pending", title: "待处理", description: "这是描述" },],[{ status: "completed", title: "已完成", description: "这是描述" },{ status: "processing", title: "处理中", description: "这是描述" },]],{ status: "pending", title: "待处理", description: "这是描述" },],],{ status: "pending", title: "结束", description: "这是描述" },
]
不足
现存缺点比较明显:
- 当流程节点超过视图容器,很难动态调整好各个节点间的距离,现在暂时使用滚动条代替
- 不能无限制渲染节点层数(最大层数为 3 ,如混合模式所示),动态计算方式仍存在缺陷
- 节点内容变化导致高度、位置的计算问题
- …
源代码
需要查看代码的可通过此处查阅:源代码
代码目录
最后
在实际实现过程中涉及到动态计算的部分很容易理不清,包括动态计算矩形框高度、宽度以及节点位置等,以上仅仅算是一个实现思路(不要害怕写出不完美的代码
),期望各位小伙伴能够在评论区给出更优质的方案!!!
相关文章:

UI 提供的 progress-step 要怎么实现?
前言 这天突然收到了 UI 修改设计稿的消息通知:“xxx 已修改 xxx 项目并 了你,请及时查看变更内容”,一条、两条、三条 …,修改消息铺天盖地而来,然后就什么都看不到了(因为我选择开启消息免打扰…...

DBSwitch和Seatunel
一、DBSwitch 什么是DBSwitch?它主要用在什么场景? 通过步骤分析可以看到这个是通过配置数据源,采用一次性或定时方案,同步到数据仓库的指定表,并且指定映射关系的工具。有点类似于flinkcdc的增量同步。 参考: dbs…...

【日志】力扣刷题 -- 轮转数组
2024.10.06 【力扣刷题】 经典面试150—转轮数组—中等 189. 轮转数组 - 力扣(LeetCode) 第一次做,暴力循环 // 超出时间限制 void rotate(int* nums, int numsSize, int k) {for(int i 0; i < k; i){int right numsSize - 1;int temp…...

Java 项目 Dockerfile 示例:从基础镜像选择到环境变量配置的详细指南
Java 项目 Dockerfile 示例:从基础镜像选择到环境变量配置的详细指南 本文提供了一个 Java 项目的 Dockerfile 示例,展示了如何为 Java 应用创建高效的 Docker 镜像。Dockerfile 从 OpenJDK 8 的 Java 运行环境开始,配置了工作目录和 JVM 启…...

WebGL编程指南 - 高级变换与动画基础
学习使用一个矩阵变换库,该库封装了矩阵运算的数学细节。快速上手使用该矩阵库,对图形进行复合变换。在该矩阵库的帮助下,实现简单的动画效果。 矩阵变换库:cuon-matrix.js OpenGL中的函数: 书中 cuon-matrix.js 函数…...

银行客户贷款行为数据挖掘与分析
#1024程序员节 | 征文# 在新时代下,消费者的需求结构、内容与方式发生巨大改变,企业要想获取更多竞争优势,需要借助大数据技术持续创新。本文分析了传统商业银行面临的挑战,并基于knn、逻辑回归、人工神经网络三种算法࿰…...

制程质量管理方案设计
质量管理系统框架——QMS 涵盖产生产制造体系的全生命周期的质量管理过程 与SAP、WMS、MES、OA等业务系统进行集成,整合各业务系统中的质量信息 利用整合的全价值链质量信息,寻找质量改进点和质量创新点 系统功能模块管理 系统管理:用户管理…...

uniapp移动端优惠券! 附源码!!!!
本文为常见的移动端uniapp优惠券,共有6种优惠券样式(参考了常见的优惠券),文本内容仅为示例,您可在此基础上调整为你想要的文本 预览效果 通过模拟数据,实现点击使用优惠券让其变为灰色的效果(模…...

【分布式技术】中间件-zookeeper安装配置
文章目录 安装部署1. 安装ZooKeeper2. 配置ZooKeeper3. 启动ZooKeeper服务器4. 使用ZooKeeper命令行客户端5. 使用ZooKeeper的四个基本操作6. ZooKeeper集群模式7. 安全和权限8. 监控和日志 相关文献 安装部署 在Linux环境中操作ZooKeeper通常涉及以下几个方面: 1…...

高等数学 7.6高阶线性微分方程
文章目录 一、线性微分方程的解的结构*二、常数变易法 方程 d 2 y d x 2 P ( x ) d y d x Q ( x ) f ( x ) (1) \cfrac{\mathrm{d}^2 y}{\mathrm{d}x^2} P(x) \cfrac{\mathrm{d}y}{\mathrm{d}x} Q(x) f(x) \tag{1} dx2d2yP(x)dxdyQ(x)f(x)(1) 叫做二阶线性微分方程。…...

LSP的建立
MPLS需要为报文事先分配好标签,建立一条LSP,才能进行报文转发。LSP分为静态LSP和动态LSP两种。 静态LSP的建立 静态LSP是用户通过手工为各个转发等价类分配标签而建立的。由于静态LSP各节点上不能相互感知到整个LSP的情况,因此静态LSP是一个…...

huggingface的数据集下载(linux下clone)
1. 安装lfs sudo apt-get install git-lfs 或者 apt-get install git-lfs 2. git lfs install git lfs install 3. git clone dataset包 第2,3步骤的截图如下:...

Java使用dom4j生成kml(xml)文件遇到No such namespace prefix: xxx is in scope on:问题解决
介绍addAttribute和addNamepsace: addAttribute 方法 addAttribute 方法用于给XML元素添加属性。属性(Attributes)是元素的修饰符,提供了关于元素的额外信息,并且位于元素的开始标签中。属性通常用于指定元素的行为或样式&#…...

深入探讨Java中的LongAdder:使用技巧与避坑指南
文章目录 一、什么是LongAdder?二、LongAdder的简单使用示例代码: 三、LongAdder的工作原理四、LongAdder的常见使用场景五、使用LongAdder时的注意事项(避坑指南)1. 不要滥用LongAdder2. sum()方法与精度问题3. 避免过度使用rese…...

【本科毕业设计】基于单片机的智能家居防火防盗报警系统
基于单片机的智能家居防火防盗报警系统 相关资料链接下载摘要Abstract第1章 绪论1.1课题的背景1.2 研究的目的和意义 第2章 系统总体方案设计2.1 设计要求2.2 方案选择和论证2.2.1 单片机的选择2.2.2 显示方案的选择 第3章 系统硬件设计3.1 整体方案设计3.1.1 系统概述3.1.2 系…...

C语言 动态数据结构的C语言实现单向链表-2
建立一个单向链表 在单向链表中查找节点---查找尾节点 在单向链表中查找节点 --- 查找第 n 个节点 向单向链表中插入一个节点 向单向链表的尾部插入一个节点 向单向链表中某节点后插入一个节点 向单向链表中插入一个节点 删除单向链表中的某一节点 链表 vs 数组 动态数据结构...

Github 2024-10-23C开源项目日报 Top10
根据Github Trendings的统计,今日(2024-10-23统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量C项目10PLpgSQL项目1Redis - 内存数据库和数据结构服务器 创建周期:5411 天开发语言:C协议类型:BSD 3-Clause “New” or “Revised” Licen…...

ubuntu20.04 opencv4.0 /usr/local/lib/libgflags.a(gflags.cc.o): relocation报错解决
在一个只有ubuntu20.04的docker环境中配置opencv4.0.0, 什么库都没有,都要重新安装, 其他的问题在网上都找到了解决方案,唯独这个问题比较棘手: [ 86%] Linking CXX executable …/…/bin/opencv_annotation /usr/bin/ld: /usr/lo…...

android openGL ES详解——混合
一、混合概念 混合是一种常用的技巧,通常可以用来实现半透明。但其实它也是十分灵活的,你可以通过不同的设置得到不同的混合结果,产生一些有趣或者奇怪的图象。混合是什么呢?混合就是把两种颜色混在一起。具体一点,就…...

计网--物理层
目录 物理层的任务 1、常见概念 2、信道极限容量 3、传输介质 (1)导引型传输介质 (2)非导引型传输介质 4、信道复用技术 (1)频分 / 时分 复用 (2)波分复用WDM (…...

算法的学习笔记—数组中的逆序对(牛客JZ51)
😀前言 在算法和数据结构领域,"逆序对"是一个经典问题。它在数组中两个数字之间定义,若前面的数字大于后面的数字,则这两个数字组成一个逆序对。我们要做的就是,给定一个数组,找出数组中所有的逆…...

Golang | Leetcode Golang题解之第498题对角线遍历
题目: 题解: func findDiagonalOrder(mat [][]int) []int {m, n : len(mat), len(mat[0])ans : make([]int, 0, m*n)for i : 0; i < mn-1; i {if i%2 1 {x : max(i-n1, 0)y : min(i, n-1)for x < m && y > 0 {ans append(ans, mat[x…...

什么是全局污染?怎么避免全局污染?
全局污染(Global Pollution)是指在编程过程中,过度使用全局变量或对象导致命名冲突、代码可维护性下降及潜在错误增加的问题。在 JavaScript 等动态语言中,尤其需要关注全局污染的风险。 全局污染的影响 1. 命名冲突 3. 意外修改…...

C# 串口通信教程
串口通信(Serial Communication)是一种用于设备之间数据传输的常见方法,通常用于与外部硬件设备(如传感器、机器人、微控制器)进行通信。在 C# 中,System.IO.Ports 命名空间提供了与串口设备交互的功能&…...

PHP编程基础
PHP(Hypertext Preprocessor,超文本预处理器)是一种广泛使用的开源服务器端脚本语言,主要用于网页开发,同时也可以进行命令行脚本编写。以下是PHP编程的基础知识: 1. PHP文件结构 PHP文件通常以 .php 为扩…...

TwinCAT3下位机配置EAP通讯传递与接收变量
添加EAP设备 DEVICE中右键选择添加新项,添加EAP(EtherCAT Automation Protocal)选择Network Variables类型,如下图。 设置网络适配器来激活EAP,在Adapter中选择search,选择网络适配器后确定,…...

近似推断 - 期望最大化(EM)篇
前言 近似推断是统计学和机器学习中一个至关重要的领域,尤其在处理复杂模型和不完全数据时显得尤为重要。期望最大化( Expectation Maximization \text{Expectation Maximization} Expectation Maximization,简称 EM \text{EM} EM࿰…...

arp欺骗及其实验
ARP欺骗(ARP Spoofing)是一种网络攻击技术,攻击者通过伪造ARP(地址解析协议)消息,将其MAC地址与目标IP地址关联,从而实现对网络流量的截获、篡改或重定向。以下是ARP欺骗的详细信息:…...
HDU The Boss on Mars(容斥原理)
题目大意: ACM 有 n 名员工,现在是他们从老板那里拿薪水的时候了。所有员工都从 1 到 n 编号。原因不明,如果员工的工作编号是 k,他今年可以获得 k^4 Mars 美元。所以为 ACM 工作的员工非常富有。 因为员工人数太多,…...

nnUnet 大模型学习笔记(续):训练网络(3d_fullres)以及数据集标签的处理
目录 1. 数据集处理 1.1 实现脚本 1.2 json文件 2. 设置读取路径 2.1 设置路径 2.2 数据集转换 2.3 数据集预处理 2.4 训练(3d_fullres) 3. 训练结果展示 关于nnUnet 数据集的处理和环境搭建,参考上文:第四章:nnUnet大模…...