记录了工作中的「图库控件」的设计
产品需求
业务需要一个图库控件——可以查看、编辑当前用户的图片。图片分为三类:待处理、已处理、无用图片。图片可以缩放查看,旋转,下载,复制。也可以上传新的图片;图片都可以编辑,填写对应的表单,打标并分类。设计如图:
设计图
组件设计
- UI 图很明显的把组件分成了三个部分:图片列表,图片查看器,编辑图片信息区域。所以,分成三个子组件去设计是很合理的。
- 先说图片查看器组件,这个因为业务需要,其他后续页面可能还要用到(含缩放,旋转功能),所以尽量减少与其他两个组件的耦合。
- 这个图库控件(父组件)是个弹窗,要支持拖动,缩放。
- 根据交互逻辑,组件之间的数据传递还是很频繁的,所以决定使用 VueX 管理数据。
交互设计
前端开发
技术选型
- 父组件的拖动、缩放功能,使用插件vue-draggable-resizable
- 图片查看器功能,使用插件viewerjs
- 以上两个插件还需要封装一下,便于使用
VueX
所有全局的(超过两个组件,或者两个层级的)数据,都使用 VueX 进行管理。
State
1 | const initialState = { |
- 所有变量的用途和使用都在代码注释中,请仔细查阅(第一行是名称,第二行是用途)。
- 我声明了
initialState
变量,会在初始化(初始化、重置)的时候用到,注意不是直接使用,而是_.cloneDeep(initialState)
。
Mutation
1 | const mutations = { |
- 所有
mutation
方法的用途都在代码注释中了,请自行查阅 - 所有
mutation
方法均为同步操作 - 对于「参数被重新赋值是否合理?」感兴趣的话,来这里看看
Action
1 | const actions = { |
- 所有
action
方法的用途都在代码注释中了,请自行查阅 - 所有
action
方法均为异步操作 - 为什么每个 `catch` 中 都要返回`Promise.reject(e)`?
组件设计
组件创建
1 | import Vue from "vue"; |
调用组件
1 | import imgLib from "src/w/img-lib"; |
父组件模版
无可赘述,只列出大致结构。详细点我
1 | ├── vue-draggable-resizable 控制图库移动及缩放 |
父组件中一些值得一说的方法
1 | methods: { |
handleClose
用以销毁子组件和清空Vuex
的数据,然后调用暴露在全局的close
方法。setZIndex
用于设置图库的Index
,因为使用的Element UI
框架,有可能在弹窗中调用图库,而Element UI
的
弹窗ZIndex
是动态变动的,所以要保障比当前已打开的弹窗要高。
编辑模块
无可赘述。详细点我
左侧列表
有一个需求是这样的:图库可能是从一个「图片上传组件」内打开的,要支持从图库把图片拖拽到「图片上传组件」,图片数据加入上传列表。
并且,拖拽图片时,要改变「图片上传组件」的样式,让使用者能快速找到放置点。放置完成后,还原样式。
所以在上面「调用组件」要传递的props
中,你看到了一个奇怪的字段uploadTargetId
。对,他就是那个「图片上传组件」的最外围div
的id
。
而传递这个 id 的用途其实就是为了上一段话提到的「拖拽时改变组件样式」。代码如下:
1 | // 拖拽某个图片,开始时带上当前图片信息,关联的drop样式设置 |
「图片上传组件」的代码
1 | // 制造一个唯一的ID |
图片查看器
无可赘述。详细点我
项目总结
其实,开发一个拓展性好、鲁棒性强的项目,最重要的一开始的设计。而好的设计需要有两个必要的先决条件:对需求的绝对领悟,以及适合的技术选型。
前者需要工作经验的积累,以及对产品 PRD 文档、设计交互稿的深度研究和理解,而后者需要作为一个程序开发者的知识深度和知识广度。
我认为,好的架构设计可以成就一个好的项目。所以,一个项目上最应该花费时间的,恰恰就是项目开始前的设计,就是建筑设计师画的图稿的过程,
而写代码,其实真的只是一个搬砖的过程。