Refine 在企业级 BOM 管理系统中的架构应用分析

240 阅读8分钟

1. Refine的基本功能与架构理念

Refine是一个基于React的元框架,核心目标是简化数据密集型应用的开发。它采用 以资源为中心 的架构,开发者通过声明资源(Resource)、提供数据适配器(DataProvider)、注册路由和UI组件,就可以快速搭建符合CRUD模式的页面。

它的设计优势在于:

资源驱动:一切以 resource 为核心组织;

Hook 优先:数据获取、表单处理、权限等逻辑,都有对应的 Hook;

可替换性强:UI、数据源、权限、路由都可替换,保持开放性。

Provider抽象 —— 统一封装数据源、鉴权、通知、国际化等跨应用能力。

这种模式非常适合标准化程度较高的后台管理系统,但在BOM管理系统这种 复杂企业应用 中,会遇到一些新问题。

1.1 Resources概念

Refine中的Resource是指应用程序中的数据实体,如用户、产品、订单等。它是Refine的核心思想。每个Resource都包含了一组标准的CRUD操作和相关的配置信息。通过定义Resource,开发者可以快速生成完整的数据管理界面。

import { Refine } from "@pankod/refine-core";

const App = () => {
    return (
        <Refine
            dataProvider={dataProvider}
            routerProvider={routerProvider}
            resources={[
                {
                    name: "products",
                    list: "/products",
                    create: "/products/new",
                    edit: "/products/:id/edit",
                    show: "/products/:id",
                },
                {
                    name: "categories",
                    list: "/categories",
                    create: "/categories/new",
                    edit: "/categories/:id/edit",
                    show: "/categories/:id",
                    meta: {
                    canDelete: true,
                    },
                },
            ]}
        />
    );
};

这种以资源为中心的设计理念,使得开发者可以专注于业务逻辑而非基础架构代码。每个Resource都包含了列表、创建、编辑、查看等基本操作,这些操作通过约定的接口与数据提供者(DataProvider)进行交互。

1.2 与Router的集成

Refine与路由系统的深度集成是其另一个重要特性。通过将Resource与路由关联,Refine可以自动生成符合RESTful规范的路由结构,简化了导航和页面管理。

import { RouterProvider } from "@pankod/refine-react-router-v6";

const App = () => {
    return (
        <Refine
            routerProvider={RouterProvider}
            // ...其他配置
        />
    );
};

这种集成使得开发者可以通过简单的配置实现复杂的路由结构,同时保持了代码的可维护性和可扩展性。

1.3 Hook系统

Refine提供了一系列强大的Hook,用于简化数据获取、表单处理、权限控制等常见任务。这些Hook基于React的Hooks API,提供了声明式的编程模型。

import { useTable, useForm, useShow } from "@pankod/refine-core";

const ProductList = () => {
    const { tableProps, sorter } = useTable({
        resource: "products",
    });

    return (
        <Table {...tableProps}>
            {/* 表格内容 */}
        </Table>
    );
};

const ProductEdit = () => {
    const { formProps, saveButtonProps } = useForm({
        resource: "products",
        action: "edit",
    });

    return (
        <Form {...formProps}>
            {/* 表单内容 */}
        </Form>
    );
};

这些Hook封装了常见的CRUD操作、表单验证、数据获取等逻辑,使开发者可以更专注于业务实现。

2. 实际开发中遇到的难题

尽管Refine提供了优雅的架构设计和丰富的功能,但在BOM管理系统这样的复杂企业应用中,我们也发现了一些实际应用中的挑战。 它面临的问题通常包括:

  • 多数据源页面:一个页面往往需要同时加载产品节点、特性列表、配置层级等数据。

  • 复杂表单逻辑:字段间强依赖,可能需要动态显示、批量联动。

  • 细粒度权限:不仅是页面级 CRUD,还涉及字段级和数据范围级。

  • 复杂查询:过滤条件多样,涉及到跨资源的组合查询。

这些挑战导致 开发人员常常在业务代码和基础组件之间切换,而 Refine 的默认模式更偏向单一资源的 CRUD,不完全匹配。

2.1 复杂的数据源管理

在BOM管理系统中,一个页面的数据往往来自多个不同的API接口。 例如,在工程配置表中,我们需要同时获取产品节点、配置层级、特性列表、规则定义等多个数据源。Refine的Resource模型假设一个页面主要围绕一个核心资源进行操作,这在BOM系统中往往不够灵活。

// 当前项目中的工程配置表组件
const EngineeringConfig = (props) => {
    const {
        featureTableId,
        config,
        selectAbleVariableVehicles, // 已选的配置层级
        productNodeCodes,
        params,
        preProductNode,
    } = props;
    
    // 需要从多个不同的API获取数据
    const { onRefresh, handleSearch, onSearch, loading } = useConfigTableSearch(
        namespace,
        onFetch,
        setModelState,
        {
            featureType: 'E',
        },
        setSyncData,
    );
    
    // 其他复杂的业务逻辑...
};

2.2 复杂的表单处理

BOM管理系统中的表单往往非常复杂,字段之间可能存在复杂的依赖关系,或者需要根据不同的条件动态显示/隐藏某些字段。例如,在变更内容表单中,根据用户选择的不同操作类型,表单的字段和验证规则会发生变化。

// 当前项目中的变更内容表单
const onCellEditingChange = (_rowData, fieldName, newValue, oldValue) => {
    const rowData = _.cloneDeep(_rowData);
    switch (fieldName) {
        case 'featureVersionDto.groupNum': {
            // 当临时编码族发生变化时,清空父特征相关字段
            if (newValue) {
                [
                    'featureVersionDto.parentFeatureCode',
                    'featureVersionDto.parentId',
                    'featureVersionDto.parentFeature',
                ].forEach((field) => {
                    _.set(rowData, field, void 0);
                });
            }
            
            // 更新同临时编码族其他数据
            if (oldValue && newValue !== oldValue) {
                const children = dataSource.filter(
                    (item) => item.featureVersionDto.groupNum === oldValue && item.id !== rowData.id,
                );
                
                const updatedChildren = children.map((item) => ({
                    ...item,
                    featureVersionDto: {
                        ...item.featureVersionDto,
                        groupNum: newValue,
                    },
                }));
                
                gridManagerRef.current?.modify(updatedChildren);
            }
            break;
        }
        // 其他字段的处理...
    }
    
    return rowData;
};

2.3 复杂的权限控制

BOM管理系统中的权限控制通常非常复杂,不仅包括基本的CRUD权限,还包括字段级别的权限控制、数据范围控制等。例如,在工程配置表中,不同角色的用户可能只能看到或编辑某些特定的字段。

// 当前项目中的权限控制
const { hasVersion, configTabsAuth } = useAuth(_configTabsAuth, searchParams);
// 功能点权限
const { AuthData, setDataId } = useCheckAuth(
    '工程配置表',
    _.get(searchParams, 'productNode.nodeCode') ? `${_.get(searchParams, 'productNode.nodeCode')}:E` : undefined,
);
const { isAuthInitImport = true, isAuthExport, isAuthEdit = false } = AuthData;

2.4 复杂的搜索和过滤

BOM管理系统中的搜索和过滤功能通常非常复杂,可能需要同时查询多个数据源,或者根据复杂的业务规则进行数据过滤。例如,在工程配置表中,用户可能需要根据产品节点、配置层级、特性版本等多个条件进行组合查询。

// 当前项目中的搜索逻辑
const useConfigTableSearch = (
    namespace,
    onFetch,
    setModelState,
    defaultParams,
    setSyncData,
) => {
    const searchRep = usePageSearch(async (filterInfo, pageInfo) => {
        // 复杂的查询逻辑
        const { content, totalCount } = await findContentListApi({
            filterInfo: {
                ...filterInfo,
                // 可能需要添加其他查询条件
            },
            pageInfo,
        });
        setDataSource(content);
        return totalCount;
    }, defaultParams);
    
    return searchRep;
};

3. 针对BOM管理系统的开发范例

Refine 的优势是“开放式的接口”,并不限制我们扩展。针对 BOM 系统的复杂性,可以从 开发体验 出发,做以下改造。

3.1 多数据源的组织方式

Refine 默认 useTable 针对单一资源,但 BOM 页面常常依赖多个资源。 我们可以在 自定义 Hook 中封装多数据源逻辑,让页面开发者只需要关注业务状态。

// 一个自定义 hook,把多数据源整合为业务态
const useEngineeringConfig = () => {
    const products = useList({ resource: "products" });
    const nodes = useList({ resource: "product-nodes" });
    const features = useList({ resource: "features" });

    // 组合后的业务数据
    const engineeringConfig = useMemo(() => {
        return mergeProductsAndNodes(products.data, nodes.data, features.data);
    }, [products.data, nodes.data, features.data]);

    return { engineeringConfig, loading: products.isLoading || nodes.isLoading };
};

// 页面代码
const EngineeringConfigPage = () => {
    const { engineeringConfig, loading } = useEngineeringConfig();

    if (loading) return <Spin />;
    return <EngineeringConfig data={engineeringConfig} />;
};

这样,开发者在页面层只需要关心 业务态数据(engineeringConfig),而不是去管理多个 useList。

3.2 表单逻辑的扩展

Refine 提供了 useForm,但 BOM 的表单往往需要动态依赖。 最佳实践是基于 useForm 封装 业务级表单 Hook,让业务逻辑与 UI 解耦。

const useChangeForm = () => {
    const { formProps, saveButtonProps } = useForm({ resource: "changes" });
    // 增强:动态联动逻辑
    const onFieldChange = (field: string, value: any) => {
        if (field === "groupNum") {
            formProps.form?.setFieldsValue({
                parentFeatureCode: undefined,
                parentId: undefined,
            });
        }
    };

    const schema = {/* 动态表单字段 */}

    return { schema, formProps, saveButtonProps, onFieldChange };
};

// 页面代码
const ChangeForm = () => {
    const { formProps, saveButtonProps, onFieldChange } = useChangeForm();

    return (
        <Form {...formProps} onValuesChange={onFieldChange}>
            <Button {...saveButtonProps}>保存</Button>
        </Form>
    );
};

这种模式下,复杂逻辑被封装在 Hook 内,表单开发更“爽”:

页面代码专注渲染;

逻辑集中在 Hook,方便复用和测试。

3.3 权限控制的细粒度扩展

Refine 内置了 accessControlProvider,适合做资源级别权限。 在 BOM 系统中,可以在其上层再包一层,支持 字段级和条件级权限。

// 封装字段权限 Hook
const useFieldPermission = (resource: string, field: string) => {
    const { can } = useCan({ resource, action: "edit", field });
    return can;
};

// 使用
const EngineeringForm = () => {
    const canEditFeature = useFieldPermission("engineering-config", "featureCode");
    const schema = {
        featureCode: {
            title: '特征编码',
            disabled: !canEditFeature
        }
    }
    return <Form schema={schema} />;
};

这样,开发者在写业务表单时,只需要关心 useFieldPermission 的返回值,而不用关心复杂的权限逻辑实现。

3.4 复杂搜索的抽象

Refine 的 useTable 默认支持简单的 filter,但 BOM 查询往往跨多个条件。 最佳实践是:对搜索逻辑二次封装 Hook,返回统一的搜索 API。

const useBOMSearch = () => {
    const [filters, setFilters] = useState({});
    const { tableProps, search } = useTable({
        resource: "engineering-config",
        initialSorter: [{ field: "createdAt", order: "desc" }],
    });

    // 封装复杂的组合查询
    const onSearch = (params: any) => {
        const merged = transformSearchParams(params);
        setFilters(merged);
        search(merged);
    };

    return { tableProps, onSearch };
};

开发者使用时:

    const EngineeringConfig = () => {
        const { tableProps, onSearch } = useBOMSearch();
        return (
            <>
                <SearchBar onSubmit={onSearch} />
                <Table {...tableProps} />
            </>
        );
    };

页面层就很清晰:只需要调用 onSearch,无需理解复杂的参数拼装逻辑。

4. 总结

Refine 的强项是提供了 资源化的开发模式,让 CRUD 页面快速落地。 在 BOM 管理系统这种复杂场景下,开发者需要:

自定义 Hook,把多数据源、复杂表单、权限、搜索等逻辑封装起来;

页面层只关心“业务状态”和“UI渲染”;

重复逻辑统一下沉到 Hook 或 Provider 中。

这样既保留了 Refine 的优雅,又让开发体验更“爽”——写页面的时候更像是在拼积木,而不是在处理底层复杂度。