@gdjiami/rc-components

mygzb.com React Components

Stats

StarsIssuesVersionUpdatedCreatedSize
@gdjiami/rc-components
0.4.22-beta316 days ago3 years agoMinified + gzip package size for @gdjiami/rc-components in KB

Readme

React Components

React 组件库, 收集了工作宝中后台应用的常用组件或套件. 致力于减少应用开发的代码重复,提高维护效率

DEMO


Installation

yarn add @gdjiami/rc-components

# 依赖
yarn add react react-dom tslib react-router react-router-dom

Usage

所有组件都在es目录下, es 使用 ES6 模块系统,另外每目录下面都有 Typescript 声明文件,所以支持类型检查,开发者可以按需导入需要的组件

rc-components 支持类似于antd的按需加载方式,如果你使用 typescript 可以使用ts-import-plugin 插件, 例如:

// webpack.config.js
const tsImportPluginFactory = require("ts-import-plugin");

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(jsx|tsx|js|ts)$/,
        loader: "ts-loader",
        options: {
          transpileOnly: true,
          getCustomTransformers: () => ({
            before: [
              tsImportPluginFactory([
                // 按需导入antd组件
                {
                  libraryName: "antd",
                  libraryDirectory: "es",
                  style: "css",
                },
                // 按需导入rc-components组件
                {
                  libraryName: "@gdjiami/rc-components",
                  libraryDirectory: "es",
                  style: "css",
                },
              ]),
            ],
          }),
        },
        exclude: /node_modules/,
      },
    ],
  },
  // ...
};

对于babel可以使用babel-plugin-import 插件

使用示例

import React from "react";
import { Login } from "@gdjiami/rc-components";
import { message } from "antd";
import { delay } from "./utils";

export default class LoginPage extends React.Component {
  public render() {
    return (
      <Login
        title="登录页面"
        onSubmit={this.handleSubmit}
        onSuccess={this.handleSuccess}
      />
    );
  }

  private handleSubmit = async () => {
    await delay(2000);
  };

  private handleSuccess = () => {
    message.success("登录成功");
  };
}

定位

rc-components 是基于 antd 组件库之上的高层组件库,旨在抽象重复的业务场景, 减少代码重复。其中耦合的东西有:

  • antd
  • react, react-dom
  • tslib
  • react-router v4
  • lodash

这些耦合的技术是 rc-components 的构建基础,而且在团队内的应用是比较稳定的、静态的,近期不会有大的变动。相对的,有些东西是我们 要避免耦合的:

  • 状态管理库,如 mobx,redux.
  • Ajax 请求库
  • 前端路由类型

Components

这里列举各组件的使用方法和注意事项

  • Title

    用于修改浏览器 title
import { Title } from "@gdjiami/rc-components";
import React, { FC } from "react";

export const Page: FC = (props) => {
  return <Title>系统管理</Title>;
};
  • AdminLayout

    后台应用布局组件 AdminLayout 为顶层父组件,其子组件分别有

    1. AdminLayout.Action 位于顶部的右边展示? (当前用户)
    2. AdminLayout.View 次顶层视图层,全局最外层用一次
    3. AdminLayout.HeaderBar
    4. AdminLayout.Footer 底部
    5. AdminLayout.Body 内容层,当业务页面用这个组件,其内容会按 AdminLayout 布局正确展示

    AdminLayout 常用参数(包括但不限于):

    参数 格式 用途
    siteName string 应用名称
    logo string 应用图标
    menus () => Promise<MenuConfig[]>) | MenuConfig[] 菜单列表
    after React.ReactNode 头部右侧内容
    // layout.tsx
    
    <AdminLayout
      siteName="后台管理系统"
      title={<Title.Display breadcrumb inline />}
      menus={[]}
      after={
          <Dropdown
            overlay={
              <Menu>
                <Menu.Item key="resetPassword">修改密码</Menu.Item>
                <Menu.Item key="logout">安全退出</Menu.Item>
              </Menu>
            }
          >
            <AdminLayout.Action>用户名</AdminLayout.Action>
          </Dropdown>
      }
    >
      <AdminLayout.View>
        {props.children}
        <AdminLayout.Footer>Version</AdminLayout.Footer>
      </AdminLayout.View>
    </AdminLayout>
    

    AdminLayout.Body 一般用于业务子页面,里面直接添加页面内容

    <AdminLayout.Body>
      <title>应用管理</title>
      <FatTable
        enableSelect
        enablePersist="{false}"
        columns="{column}"
        header="{renderHeader}"
        headerExtra="{renderHeaderExtra}"
        onRemove="{handleRemove}"
        onFetch="{handleFetch}"
        onAction="{handleAction}"
      />
    </AdminLayout.Body>
    
  • FatTable

    后台应用表格组件,高频组件之一,集成了翻页,搜索,多选,上移下移等基础功能。 FatTable 子组件有

    1. FatTable.Actions 表格项功能按钮组,下为其子组件
    2. FatTable.Action 表格项功能按钮

FatTable 常用参数(仅列举了常用,更多请查看源码): | 参数 | 格式 | 用途| | ---- | ---- | ---- | | enableSelect | boolean | 是否开启可选 | enablePagination | boolean | 是否开启翻页 | onFetch | FetchHandler | 获取表格数据的方法(翻页搜索均调用此方法) | header | HeaderRenderer | 表格头部内容 (一般为搜索功能) | headerExtra | HeaderExtraRenderer | 表格头部额外内容 (一般表格功能按钮,导出、导出、删除、添加等) | columns | ColumnsType | 列表数据展示 | idKey | string | 列表项的 key (如没有唯一的值可手动构造) | className | string | 定义类名 | onShift | ShiftHandler | 顺序发生改变所调用的回调 | onRemove | RemoveHandler | 列表项删除所调用的回调 | onAction | ActionHandler | 操作表格的统一方法

// 直接用就好啦
<FatTable
  enableSelect
  columns="{column}"
  header="{renderHeader}"
  headerExtra="{renderHeaderExtra}"
  onRemove="{handleRemove}"
  onFetch="{handleFetch}"
  onAction="{handleAction}"
/>

onAction 使用方法 和表格交互的重要途径

// 示例内容
import { FatTable } from '@gdjiami/rc-components'
import { ColumnsType} from '@gdjiami/rc-components/es/fat-table'

const AppStore: FC = () => {
  const { getDownloadUrl } = useRootModel()
  const column: ColumnsType<T, P> = [
    {
      title: '示例内容', // 列的标题
      width: 80,
      render: r => ( // 自定义展示内容 没有则展示dataIndex字段
        <span>
          自定义的展示内容{r.logo}
        </span>
      )
    },
    {
      title: '示例内容2',
      dataIndex: 'downloadUrl',
    },
    {
      title: '操作',
      width: 180,
      render: (r, _, t) => {
        // t.triggerAction('toggleOpen', r) 来触发handleAction 可传入 action类型和数据
        // t.remove([r.id]) 来执行删除项的请求等方法
        return (
          <FatTable.Actions className="Container__TagGroup">
            <FatTable.Action onClick={() => t.remove([r.id])}>
              删除
            </FatTable.Action>
            <FatTable.Action onClick={() => t.triggerAction('actionType', r)}>
              启用
            </FatTable.Action>
          </FatTable.Actions>
        )
      }
    }
  ]

 const handleAction = (async (name, data, t) => {
    switch (name) {
      case 'actionType':
        // do something
        break
    }
  }

  return (
      <FatTable
        enableSelect
        enablePersist={false}
        columns={column}
        header={renderHeader}
        headerExtra={renderHeaderExtra}
        onRemove={handleRemove}
        onFetch={handleFetch}
        onAction={handleAction}
      />
  )
}

  • UserSelect

    员工选择的组件 首先在路由定义处使用 UserSelectProvider,为有需要使用的路由提供组件服务。
import { UserSelectProvider } from "@gdjiami/rc-components/es/user-select";

<UserSelectProvider adaptor={adaptor}>
  <Route path="/static" exact component={Comp} />
</UserSelectProvider>;

随后需定义 UserSelectAdaptor.tsx(一般和 Route.tsx 同层)

import {
  UserSelectAdaptor,
  DepartmentDesc,
  UserDesc,
  TenementDesc,
} from "@gdjiami/rc-components/es/user-select";
import { DepartmentSearchResult } from "@gdjiami/rc-components/es/user-select/Provider";
import rpc from "~/rpc";

interface DepartmentTreeItem {
  children?: DepartmentTreeItem[];
  departmentId: string;
  departmentName: string;
  tenementId: string;
  fullPath: string;
  parentIds: string[];
}

const Adaptor: UserSelectAdaptor = {
  /**
   * 获取部门树
   */
  async getDepartmentTree(tenementId: string): Promise<DepartmentDesc> {
    const res = await rpc.request<{ items: DepartmentTreeItem[] }>(
      "org.department.getTree",
      {
        tenementId,
        fetchFullPath: true,
      }
    );
    const items = res.items.map(
      ({ departmentId: id, departmentName: name, ...others }) =>
        ({ id, name, ...others } as DepartmentDesc)
    );
    return items[0];
  },

  async getDepartmentChildren(tenementId: string, departmentId: string) {
    const res = await rpc.request<{ items: DepartmentTreeItem[] }>(
      "org.department.getTree",
      {
        tenementId,
        parentId: departmentId,
        fetchFullPath: true,
      }
    );
    const items = res.items.map(
      ({ departmentId: id, departmentName: name, ...others }) =>
        ({ id, name, ...others } as DepartmentDesc)
    );
    return items;
  },

  /**
   * 获取部门成员
   */
  async getDepartmentUsers(
    tenementId: string,
    departmentId: string,
    page: number,
    pageSize: number
  ): Promise<{ items: UserDesc[]; total: number }> {
    return { items: [], total: 0 };
  },

  /**
   * 用户搜索
   * tenementId不为空时,表示企业内搜索
   */
  async searchUser(
    query: string,
    page: number,
    pageSize: number,
    tenementId?: string
  ): Promise<{ items: UserDesc[]; total: number }> {
    const params = {
      key: query,
      startIndex: (page - 1) * pageSize,
      resultRows: pageSize,
      tenementId,
    };
    const res = await rpc.request<{
      items: Array<UserDesc & { userId: string }>;
      totalItems: number;
    }>("user.search", params);
    return {
      items: res.items.map((i) => ({ ...i, id: i.userId })),
      total: res.totalItems,
    };
  },

  /**
   * 企业搜索
   */
  async searchTenement(
    query: string,
    page: number,
    pageSize: number
  ): Promise<{ items: TenementDesc[]; total: number }> {
    const params = {
      searchKey: query,
      startIndex: (page - 1) * pageSize,
      resultRows: pageSize,
    };
    const res = await rpc.request<{
      items: Array<{ tenementId: string; tenementName: string }>;
      totalItems: number;
    }>("tenement.lists", params);
    return {
      items: res.items.map((item) => {
        const { tenementId, tenementName } = item;
        return { id: tenementId, name: tenementName, extra: item };
      }),
      total: res.totalItems,
    };
  },

  async searchDepartment(
    query: string,
    page: number,
    pageSize: number,
    tenementId?: string
  ) {
    const res = await rpc.request<{
      items: Array<{
        userCount: string;
        parentId: string;
        parentIds: string[];
        leaf: boolean;
        departmentId: string;
        departmentName: string;
      }>;
      totalItems: number;
    }>("org.department.search", {
      tenementId,
      key: query,
      startIndex: (page - 1) * pageSize,
      resultRows: pageSize,
      fetchFullPath: true,
    });

    return {
      items: res.items.map(
        (i) =>
          ({
            ...i,
            id: i.departmentId,
            name: i.departmentName,
          } as DepartmentSearchResult)
      ),
      total: res.totalItems,
    };
  },

  async normalizeDepartmentChecked(
    currentSelected: DepartmentDesc[],
    added: DepartmentDesc[],
    removed: DepartmentDesc[]
  ): Promise<DepartmentSearchResult[]> {
    const map = (i: DepartmentDesc) => ({
      tenementId: i.tenement!.id,
      departmentId: i.id,
    });
    const params = {
      currentItems: currentSelected.map(map),
      addItems: added.map(map),
      delItems: removed.map(map),
    };
    const res = await rpc.request<{
      currentItems: Array<{
        userCount: string;
        parentId: string;
        parentIds: string[];
        leaf: boolean;
        departmentId: string;
        departmentName: string;
      }>;
    }>("org.department.selectedChange", params);
    return res.currentItems.map(
      (i) =>
        ({
          ...i,
          id: i.departmentId,
          name: i.departmentName,
        } as DepartmentSearchResult)
    );
  },

  async getDepartmentDetail(
    ids: string[],
    tenementId?: string
  ): Promise<DepartmentSearchResult[]> {
    const params = {
      items: ids.map((i) => ({ tenementId, departmentId: i })),
    };
    const res = await rpc.request<{
      items: Array<{
        userCount: string;
        parentId: string;
        parentIds: string[];
        leaf: boolean;
        departmentId: string;
        departmentName: string;
      }>;
    }>("org.department.getDepartmentInfo", params);
    return res.items.map(
      (i) =>
        ({
          ...i,
          id: i.departmentId,
          name: i.departmentName,
        } as DepartmentSearchResult)
    );
  },
};

export default Adaptor;

License

This project is licensed under the terms of the MIT license.

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.