gdjiami-rc-components
v1.0.0-beta5
Published
mygzb.com React Components
Downloads
5
Readme
React Components
React 组件库, 收集了工作宝中后台应用的常用组件或套件. 致力于减少应用开发的代码重复,提高维护效率
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 为顶层父组件,其子组件分别有
- AdminLayout.Action 位于顶部的右边展示? (当前用户)
- AdminLayout.View 次顶层视图层,全局最外层用一次
- AdminLayout.HeaderBar
- AdminLayout.Footer 底部
- 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 子组件有
- FatTable.Actions 表格项功能按钮组,下为其子组件
- 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.