2023-06-08 自己开发业务基础组件
前言
在公司开发业务的时候,遇到公司自己开发UI库里Steps组件不满足需求,那边同学又不愿意更改,扯皮了一大堆,最后UI设计师更换了设计方案,好家伙,直接自己开发,发现开发一个基础组件还是要注意不少细节的,于是趁着这个功夫记录下
第一步,根据UI图确定数据格式
来自有良好感觉的UI设计:
首先,这块分为上方标题和下方步骤图两个部分:
-
上方是一个h5标题不必多说
-
下方是一个超出固定宽度有滚动条的盒子,这里盒子我用的是H5的语义化标签
article
元素,里面每个长方形元素用的是section
元素,之所以用语义化元素,我这里就是为了后续的开发者能够更好的维护和理解
然后,这里数据源肯定是一个数组,数组里的每个元素都是对象,用来定义section里的内容,包括文字-title,状态-status和一个值-value,这个value值用来确定当前是否是选中状态,每个section里定义的数据类型如下:
export type StatusCN = "进行中" | "已完成" | "未开始";
/**
* 每个步骤单元框里的数据
*/
export interface Step {
title: string;
status: StatusCN;
value: number;
}
由于使用React组件开发的,所以这里抽成一个组件,组件接收的props参数如下:
/**
* 工序步骤图参数props
*/
interface LubanStepsProps {
current: number; // 当前选中
onClick: (v: Step) => void; // 点击事件
steps: Step[]; // 数据
disabledClicks: number[]; // 不可点击
}
参数命名是参照公司UI库Steps组件的,其中有一个参数disabledClicks需要说明下,我也不知道人家为啥这么设计,能否点击直接设计到Step接口里不好吗,这样每个section都知道自己能不能点击,而不是单独传进来一个disabledClicks数组,还需要自己遍历判断,真是活见鬼了(这个人想法真奇怪),之前是已经开发好了,然后临时更换的,所以也就按照这个来了
第二步,根据数据确定每个元素,再编写CSS,把样式先搞定,剩下的交给JS
最外边元素article为父元素,包裹整个步骤图的section元素集合
这里给article元素设置了弹性盒子布局flex,其他属性都是默认即可,由于是抽成基础组件,所以这里css采用的react-jss方式,这样避免了样式命名冲突
article: {
display: "flex",
overflowX: "auto",
}
article里面的每个section元素固定宽度,padding不设置高度,因为内部还有文字
section: {
position: "relative",
marginTop: "0 !important",
width: 188,
backgroundColor: "#fff",
border: "1px solid #E1E2E5",
padding: "16px 50px 16px 16px",
flex: "none", // 固定大小,超出滚动
marginRight: 12, // 设置右边距为每个块
marginBottom: 16, // 设置下边距
}
其实这里的属性样式都是根据UI设计来的,不是自己胡编乱造的哈~
确定状态status的布局样式
"& a": {
position: "absolute",
right: 0,
top: 0,
fontSize: "10px",
lineHeight: "16px",
padding: "2px 4px",
cursor: "auto"
},
确定文字title的布局样式
"& p": {
width: 122,
color: "#33394D",
fontSize: 12,
lineHeight: "18px",
fontStyle: "normal",
fontWeight: 400,
marginBottom: 0
},
注意:"&"符号是用在嵌套结构中,指代当前元素
这里再造一些假数据,这个UI样式就出来了
不过这里有几个要注意的点:
- 第1个是不同状态的右上角字体颜色不一样,需要根据状态去设置颜色
- 第2个是选中当前section,里面的文字颜色和外边边框颜色都为选中颜色
- 第3个是props中有disabledClicks数组表示有不可选中的section,这部分颜色要置灰,鼠标变为不可选
根据调研发现H5元素属性上有自定义属性data-*
,根据这个自定义属性给每个section、a和p元素设置不同的值,这样为不同的值,设置元素属性选择器样式即可,这样省事儿很多
<section
onClick={() => {
if (disabledClicks.includes(item.value)) {
return;
}
onClick(item);
}}
className={cls.section}
key={item.value}
data-active={current === item.value}
data-selectable={disabledClicks.includes(item.value) === false}
>
<a data-status={item.status}>{item.status}</a>
<p data-status={item.status}>{item.title}</p>
</section>
其中,data-active
和data-selectable
都是布尔值类型,data-status
这里暂时设置了中文字符串,需增加样式如下:
"& a": {
position: "absolute",
right: 0,
top: 0,
fontSize: "10px",
lineHeight: "16px",
padding: "2px 4px",
cursor: "auto"
},
"& a[data-status='进行中']": {
backgroundColor: "#E9EFFC",
color: "#1F54C5"
},
"& a[data-status='已完成']": {
backgroundColor: "#EBFAED",
color: "#2DA641"
},
"& a[data-status='未开始']": {
backgroundColor: "#E1E2E5",
color: "#717784"
},
"& p": {
width: 122,
color: "#33394D",
fontSize: 12,
lineHeight: "18px",
fontStyle: "normal",
fontWeight: 400,
marginBottom: 0
},
// 可选hover
"&[data-selectable='true']:hover": {
borderColor: "#1F54C5",
cursor: "pointer"
},
"&[data-selectable='false']": {
userSelect: "none",
cursor: "not-allowed"
},
// 禁用颜色
"&[data-selectable='false'] p": {
color: "#8F949E"
},
// 选中边框颜色
"&[data-active='true']": {
borderColor: "#1F54C5"
},
// 选中字体颜色
"&[data-active='true'] p": {
color: "#1F54C5"
}
这里还需考虑一点是滚动条的样式,由于滚动条是浏览器自带的原生伪元素样式,所以不同浏览器会有所区别,这里以webkit内核的浏览器为例:
article: {
display: "flex",
overflowX: "auto",
// 内部滚动条的样式
// 整个滚动条
"&::-webkit-scrollbar": {
height: 6 // 这里height对应水平方向的高度,width对应竖直方向的宽度
},
// 滚动条滑块
"&::-webkit-scrollbar-thumb": {
borderRadius: 13,
visibility: "hidden"
},
// 滚动条滑块-鼠标移入
"&:hover::-webkit-scrollbar-thumb": {
backgroundColor: "#C8CBCF",
visibility: "visible"
},
// 滚动条滑块轨道
"&::-webkit-scrollbar-track": {
backgroundColor: "transparent"
}
},
第三步,也是最后一步,CSS样式已经完成,而且选中和不同状态的也搞定,那么就是交互了
这里把js和css相结合,初始化选中第一个section,每当点击可以选中的section时,选中的title文字会发生变化
代码如下:
<main className="process-step-bar">
<h5>工序</h5>
<section>
{items.length > 0 && (
<LubanSteps
current={current}
onClick={changeStep}
steps={formatStepsData(items)}
disabledClicks={disabledClicks(items)}
/>
)}
{items.length === 0 && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
</section>
</main>
点击事件:
/**
* 点击节点事件-设置procedureId
*/
const changeStep = (v: Step) => {
setCurrent(v.value);
};
接口数据转换steps数据:
/**
* 转换成LubanStep业务组件需要的数据
*/
const formatStepsData: (data: ItemsData[]) => Step[] = (data) => data.map((item, index) => ({
title: item.name,
value: index,
status: transferStatus(item.status)
}));
判断section能否点击:
/**
* 单元工程节点能不能点击
* 之前节点全部完成
*/
const disabledClicks = (data: ItemsData[]) => {
const prevList = data.slice(0, data.length - 1);
const isClick = prevList.every((item) => item.status === "Passed");
if (isClick) {
return [];
}
return [data.length - 1];
};
至此,一个业务基础组件就开发完毕了,主要是CSS涉及到的知识有点儿多,算是恶补了,这里的代码只提供参考,公司代码不便于上传~