@gdjiami/rc-components
TypeScript icon, indicating that this package has built-in type declarations

2.1.5 • Public • Published

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

Demo

run: yarn parcel -- ./components/AdminLayout/example/index.html

License

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

Readme

Keywords

Package Sidebar

Install

npm i @gdjiami/rc-components

Weekly Downloads

240

Version

2.1.5

License

MIT

Unpacked Size

1.15 MB

Total Files

408

Last publish

Collaborators

  • jiami
  • carney520
  • wenson-jay