likes
comments
collection
share

2023-06-08 自己开发业务基础组件

作者站长头像
站长
· 阅读数 17

前言

在公司开发业务的时候,遇到公司自己开发UI库里Steps组件不满足需求,那边同学又不愿意更改,扯皮了一大堆,最后UI设计师更换了设计方案,好家伙,直接自己开发,发现开发一个基础组件还是要注意不少细节的,于是趁着这个功夫记录下

第一步,根据UI图确定数据格式

来自有良好感觉的UI设计:

2023-06-08 自己开发业务基础组件

首先,这块分为上方标题和下方步骤图两个部分:

  • 上方是一个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样式就出来了

2023-06-08 自己开发业务基础组件

不过这里有几个要注意的点:

  • 第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-activedata-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涉及到的知识有点儿多,算是恶补了,这里的代码只提供参考,公司代码不便于上传~