一、简介
有一件事不得不承认,飞书在应用桌面端组件开发方面,还是做得比较优秀的,特别是在用户体验方面适用不同人群。如果做得很一般,那么也就不会有这么多产品设计者,争先相仿飞书的产品了,哈哈哈😄,其中也包括我身边的产品同事。既然说到这了,那么咋们先看下模仿的效果图吧,观摩一二,如下:
那么有JYM好奇,会问,飞书的地址选择器又是咋样的呢?继续往下看:
二、飞书APP地址选址组件
从图片中可以看出,飞书的地址选择组件,包括了三部分:(国家、省市区、详细地址)
而我们产品的设计,则是将国家省市区嵌套在一起,进行组件联动,这个待会后面再说。
我们先继续看飞书APP,
国家的弹层选择是怎样子的:
其中,分为推荐(即热门) 和 字母分类搜索 两大部分,对全球国家进行分组和定位。当选择国家后,则会依据选择的国家重新请求省市区中的数据。大致就这么一个逻辑
省市区的弹层是怎样子的
先选省份,然后拉城市的数据,选完城市,再拉区县的数据,如果所得到的下级数据数组为空则不展示该分层及后面的后面的分层。当然,这里还提供了暂不选择的方式,就是不一定需要选择到最后一层才能结束地址选择并关闭弹层。也可以直接点击 暂不选择按钮 关闭弹层,并以上一层的选择目标为结果集,回显到输入框中。
在这里,之所以先通过体验飞书选址组件的,也是为了后面的逻辑分析和模仿。
三、仿飞书选址组件
设计思路
- “热门”里面的数据为系统可配默认值为:中国、中国澳门、中国香港、中国台湾、越南、新加坡、美国
- 初始值根据用户网络环境自动定位,定位失败则默认空
- 下拉框带有搜索功能,可搜索国家/地区名称,模糊实时搜索
- 交互:
- (1)选择了上一层,才出现下一层的tab,直到最后一层选中后关闭下拉框
- (2)“暂不选择”则关闭下拉框,只填入这层之前选择的值,此按钮需要根据场景需求来,可隐藏掉
注意:当前页面上“组织地址”下拉中隐藏掉“暂不选择”按钮,需要用户选到最后一个层级的值
接口定义
从设计图中的信息,我们无法需要这些信息:字段名称、唯一值代号、以及所依附的父级代号,形成一个完整的国家省市区的关系链树状结构。但是,因考虑到性能的问题,我们不可能要求后端接口一次性返回所有国家省市区所组合而成的的庞大数状结构数据,那么我们或许可以将其拆分成一个单元结构即树叶,再通过按需,将整个单元结构串联起来形成躯干。
如此做法,一来可以按照用户所需加载数据,二来提高性能和响应速度。也方便后续地址数据的维护。
所以定义了如下单元结构:
export interface Region {
/**
* 区域简称
*/
area?: string;
/**
* 区域代码,身份证头两位
*/
code?: string;
/**
* 导入时间
*/
create_at?: number;
/**
* 名称
*/
name?: string;
/**
* 所属区域代码
*/
parent: string;
}
然后通过行政区域代号去请求,得到区域列表
请求参数:body
export interface Request {
/**
* 所属区域代码,不知道就传空
*/
code: string;
}
请求结果:response
export interface Response {
/**
* 行政区域列表
*/
regions: Region[];
}
示例结果:
开发组件(基于TDesign框架)
我们先对需求进行分析,理清开发思路即可
1.设置四个分类常量
const topBar: Array<TabItem> = [
{
value: "country",
label: "国家",
},
{
value: "province",
label: "省份/地区",
},
{
value: "city",
label: "城市",
},
{
value: "area",
label: "区/县",
},
];
2.入参定义
const props = defineProps({
// data: {
// type: Object,
// default: () => {},
// },
value: {
type: Object,
default: () => undefined,
},
disabled: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: "请选择地址",
},
isShowControl: {
// 是否显示‘暂时不选得控件’
type: Boolean,
default: true,
},
});
其中isShowControl,用于控制暂不选择的功能节点
3.数据回显
这是关键的一步,需要通过向数据库保存选择的代号,以数组(按分类的顺序)保存,
等获取详情后再通过相应的code去请求默认数据
const onSetDefaultAddress = async (cur) => {
// address: [], // 以这个的数量为参考
// 默认选中的项目
// select: [], // 这里的数量只会比address少于或等于
if (cur) {
const arr = [];
cur.address.forEach((item, itemIndex) => {
if (itemIndex === 0) {
// countryDatas.value = item;
arr.push(onGetRegion(undefined, "country"));
} else if (itemIndex === 1) {
// provinceDatas.value = item;
arr.push(onGetRegion(cur.address[itemIndex - 1], "province"));
} else if (itemIndex === 2) {
// cityDatas.value = item;
arr.push(onGetRegion(cur.address[itemIndex - 1], "city"));
} else if (itemIndex === 3) {
// areaDatas.value = item;
arr.push(onGetRegion(cur.address[itemIndex - 1], "area"));
}
});
await Promise.all(arr);
cur.select.forEach((item, itemIndex) => {
if (itemIndex === 0) {
countryActiveItem.value = item;
} else if (itemIndex === 1) {
provinceActiveItem.value = item;
} else if (itemIndex === 2) {
cityActiveItem.value = item;
} else if (itemIndex === 3) {
areaActiveItem.value = item;
}
});
currentTopActive.value = "country"; // 默认跳回国家
autoAddressText(); // 显示文本信息
} else {
initData();
onGetRegion(undefined, "country");
}
};
代码中的onGetRegion
就是接口定义中的接口调用方法
autoAddressText
用于自动拼凑回显
大致的思路就如上面所述,供大家参考!也对自己开发过程的一次总结