jm-rc-components v1.1.1-beta6
React Components
React 组件库, 收集了工作宝中后台应用的常用组件或套件. 致力于减少应用开发的代码重复,提高维护效率
Installation
yarn add @gdjiami/rc-components
# 依赖
yarn add react react-dom tslib react-router react-router-domUsage
所有组件都在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 AdaptorDemo
run: yarn parcel -- ./components/AdminLayout/example/index.html
License
This project is licensed under the terms of the MIT license.
10 months ago
1 year ago
1 year ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago