我把 PC 表单强行硬塞到移动端的实验失败了,不出意外。没有 one size fit all 的解决方案的,那么编辑 One-to-Many 会有哪 4 种典型场景呢?
第 1 种:string[]
上图中的 Goals,最简单的方法可以是
class Meeting {
public goals: string[];
}
但是为了持久化到数据库方便,可能也会变成 One-to-Many
class Meeting {}
class MeetingGoal {
public meetingId: string;
public goal: string;
}
因为 string 类型的文本输入框是一个非常简单的组件,可以可以直接内联到列表里进行编辑,也就有了上面这样的交互形式。
稍微复杂一点的结构,例如
class Name {
public firstName: string;
public lastName: string;
},
编辑 Name 需要两个文本输入框,可能也可以勉强塞进去,用列表内联编辑的方式来交互。
第 2 种:Location[]
class Promotion {}
class Location {}
class PromotionLocation {
public promotionId: string;
public locationId: string;
}
第二种的特点是 Many 端的类型不是简单的 Value Object,例如 String/Name,而是一个有身份的 Entity Object,例如 Category,Product,Contact 这些。就像 Date Picker 一样,会有一个专门的 xxx Picker 来选择不同类型的 Entity,例如上图的 Location Picker。One-to-Many 的需求就是给 xxx Picker 提供一个多选的版本。
图片上传也是一种典型的情况。
第 3 种:梯度满减
梯度满减中的每个阶梯,是一个标准的 Master/Details 或者 Parent/Children 关联关系。其表结构上的特点是关联表上有多个字段,从而用 xxxPicker 无法满足需求。
class Promotion {}
class Product {}
class DiscountLevel {
public promotionId: string; // Promotion
public threshold: number;
public discountAmount: number;
public giftId?: string; // Product
}
上图的这种交互的特点是了列表是只读的,只满足浏览需求。如果需要编辑每个 DiscountLevel,那么就要跳页去编辑。相当于第一种 string[] 的退化版本,其区别就是 string 的文本输入框很小,可以内联。但是 DiscountLevel 的编辑器很大,内联不进来。
那为什么要跳页,而不是用第 2 种那样,用弹框/浮层的方式来做页内编辑呢? 问题是上面例子里的 DiscountLevel,可能有一个 giftId 字段要填。用弹框的方式来编辑 DiscountLevel,那么 gift 选择是不是又要在弹框上二次弹框呢? 所以如果有多个字段的表单需要填写,而不是像 Location[] 那样只是多选,还是必须要跳页。
第 4 种:Contact => Activity
第 4 种又可以称为无脑跳页模式。也就是大家处理 One-to-Many 的时候第一个想到的方式,就是给每个子对象,提供一个独立的列表页。例如上图中的 Activity >
之所以 Contact => Activity 不能和梯度满减一样处理,其原因在于梯度满减的阶梯数量是有限的,可以肯定在一个屏幕的列表页能可以放下。但是一个 Contact 的 Activity 数量是没有上限的,在 Contact 页面直接列出所有的 Activity 就高度不可控制了。
从“概念”的角度来说,Activity 不是 Contact 的一部分,但是阶梯是梯度满减的一部分。所以在 Contact 上提供 Activity,更多是一种便捷的导航,类似下图中的 RELATED
场景是可枚举的
每个页面都生撸是很费时费力的
每个 One-to-Many 都生成同样的界面是过于死板的
在生撸和死板生成之间,缺少的就是对场景的枚举。把 One-to-Many 看成一样的东西,那就不好生成了。把 One-to-Many 细分成 4 样东西,那就完全可以生成了。
限于移动屏幕的大小,撑死到极限也就是把一级的 One-to-Many 编辑内联到主页面上。如果有多层级的 One-to-Many,还是要用跳页来解决。