antd#Pagination TypeScript Examples
The following examples show how to use
antd#Pagination.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Paging.tsx From jmix-frontend with Apache License 2.0 | 7 votes |
render() {
const {paginationConfig, total} = this.props;
return (
<Pagination
{...paginationConfig}
total={total}
pageSizeOptions={toJS(paginationConfig.pageSizeOptions)}
onChange={this.props.onPagingChange}
onShowSizeChange={this.props.onPagingChange}
/>
);
}
Example #2
Source File: default.tsx From useTable with MIT License | 6 votes |
Component = () => {
const { formProps, tableProps, paginationProps } = useAntdFormTable(list);
return (
<Fragment>
<SchemaForm {...formProps} components={{ Input }} style={{ marginBottom: 20 }} inline>
<Field name="name" title="name" x-component={'Input'} />
<FormButtonGroup>
<Submit>查询</Submit>
<Reset>重置</Reset>
</FormButtonGroup>
</SchemaForm>
<Table scroll={{ y: 300 }} {...tableProps}>
<Table.Column title="email" dataIndex="email" />
<Table.Column title="phone" dataIndex="phone" />
<Table.Column title="gender" dataIndex="gender" />
</Table>
<Pagination style={{ marginTop: 16 }} {...paginationProps} />
</Fragment>
);
}
Example #3
Source File: all.tsx From useTable with MIT License | 6 votes |
Component = () => {
const selectionPlugin = useSelectionPlugin({ primaryKey: 'phone' });
const sortablePlugin = useSortablePlugin();
const filterPlugin = useFilterPlugin();
const { tableProps, paginationProps, getSelectedRowKeys } = useAntdTable(list, {
plugins: [filterPlugin, selectionPlugin, sortablePlugin],
});
return (
<Fragment>
<p>
<Button
onClick={() => {
const selectedRowKeys = getSelectedRowKeys() || [];
message.success(selectedRowKeys.join(','));
}}
>
点击查看勾选值
</Button>
</p>
<Table scroll={{ y: 300 }} {...tableProps}>
<Table.Column title="email" dataIndex="email" filters={filters} />
<Table.Column title="phone" dataIndex="phone" sorter />
<Table.Column title="gender" dataIndex="gender" />
</Table>
<Pagination style={{ marginTop: 16 }} {...paginationProps} />
</Fragment>
);
}
Example #4
Source File: default.tsx From useTable with MIT License | 6 votes |
Component = () => {
const { tableProps, paginationProps } = useAntdTable(list);
return (
<Fragment>
<Table scroll={{ y: 300 }} {...tableProps}>
<Table.Column title="email" dataIndex="email" />
<Table.Column title="phone" dataIndex="phone" />
<Table.Column title="gender" dataIndex="gender" />
</Table>
<Pagination style={{ marginTop: 16 }} {...paginationProps} />
</Fragment>
);
}
Example #5
Source File: GeneralPagination.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
export function GeneralPagination(
props: GeneralPaginationProps
): React.ReactElement {
const { t } = useTranslation(NS_PRESENTATIONAL_BRICKS);
const showTotal = (totals: number): React.ReactElement => {
return (
<span className={style.totalText}>
{t(K.PAGINATION_TOTAL_TEXT)}{" "}
<strong className={style.total}>{totals}</strong>{" "}
{t(K.PAGINATION_TOTAL_UNIT)}
</span>
);
};
// 默认分页配置
const defaultPagination: PaginationProps = {
showTotal,
current: props.page,
pageSize: props.pageSize,
total: props.total,
showSizeChanger: true,
pageSizeOptions: ["10", "20", "50"],
onChange: props.handleOnChange,
};
const configProps = { ...defaultPagination, ...(props.configProps || {}) };
return (
<Pagination
{...configProps}
className={classNames({
[style.onlyShowTotal]: props.onlyShowTotal,
})}
/>
);
}
Example #6
Source File: SimpleTable.tsx From office-hours with GNU General Public License v3.0 | 6 votes |
SimpleTable = ({
output,
currentPage,
setPage,
}: SimpleTableTypes): ReactElement => {
return (
<div>
<Table
dataSource={output.dataSource}
columns={output.columns}
pagination={false}
/>
<Pagination
current={currentPage}
pageSize={6}
total={output.totalStudents}
onChange={(page) => setPage(page)}
/>
</div>
);
}
Example #7
Source File: antd-default-props.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
setAntdDefault = () => {
Pagination.defaultProps = {
showSizeChanger: false,
...Pagination.defaultProps,
pageSize: PAGINATION.pageSize,
pageSizeOptions: PAGINATION.pageSizeOptions,
showTotal: (total) => (isZh() ? `共计 ${total} 条` : `total ${total} items`),
};
Modal.defaultProps = {
...Modal.defaultProps,
centered: true,
};
Spin.defaultProps = {
...Spin.defaultProps,
// @ts-ignore this property is exist
delay: 300,
};
Spin.setDefaultIndicator(<Loading />);
}
Example #8
Source File: Home.tsx From react-ts-antd with MIT License | 5 votes |
render () {
const {
total,
pageNo,
pageSize,
loading,
dataSource,
columns,
visible,
title,
textBtn,
currentRowData
} = this.state;
const { Option } = Select;
return (
<DocumentTitle title={'首页'}>
<div className="home-container">
<Header curActive={'active'} />
<div className="content clearfix">
<div className="list">
<h2>任务列表</h2>
<div className="list-right">
<Space size="middle">
<Select size="large" onChange={ this.handleChange } style={{ width: 160 }} allowClear placeholder="请筛选任务状态">
<Option value=''>全部</Option>
<Option value={ 0 }>待办</Option>
<Option value={ 1 }>完成</Option>
<Option value={ 2 }>删除</Option>
</Select>
<Button type="primary" size="large" onClick={ this.addTask }><PlusOutlined /> 添加任务</Button>
</Space>
</div>
</div>
<Table
bordered
rowKey={ record => record.id }
dataSource={ dataSource }
columns={ columns }
loading={ loading }
pagination={ false }
/>
<Pagination
className="pagination"
total={ total }
style={{ display: loading && total === 0 ? 'none' : '' }}
showTotal={total => `共 ${total} 条数据`}
onChange={ this.changePage }
current={ pageNo }
showSizeChanger={ false }
defaultPageSize={ pageSize }
hideOnSinglePage={ false }
/>
</div>
<Footer />
<AddEditTaskForm
title={ title }
textBtn={ textBtn }
visible={ visible }
currentRowData={ currentRowData }
onSubmitDrawer={ this.onSubmit }
onCloseDrawer={ this.onClose }
/>
</div>
</DocumentTitle>
)
}
Example #9
Source File: index.tsx From gant-design with MIT License | 4 votes |
GantTableList = function GantTableList<T extends Record>(props: GantTableListProps<T>, ref: React.Ref<HTMLDivElement>) {
const {
pagination,
rowSelection,
rowKey,
dataSource,
isZebra,
editable,
onRow: originOnRow,
bordered: customBorderd,
scroll,
wrap,
footerDirection,
flex,
orderList,
light,
spacing,
cellPadding,
onSave,
expandedRowKeys,
onExpand,
onExpandedRowsChange,
headerRight,
editActions,
tableWraper,
withIndex,
virtualScroll: virtualScrollConfig,
resizable: resizeCell,
onScroll,
defaultExpandAllRows
} = props
/* =======================warning======================= */
if (process.env.NODE_ENV !== "production") {
warning(
!("flex" in props),
`GantTableList \`flex\` is deprecated, please do not use`
)
warning(
!("resizeCell" in props),
`GantTableList \`resizeCell\` is deprecated, please use \`resizable\` instead`
)
}
const [tableKey] = useState(() => { // 用于缓存列宽度
if (props.tableKey) {
return props.tableKey
}
if (props.columns && props.columns.length) {
const str = props.columns.map(item => item.dataIndex + item.title).join('')
return window.escape(str).replace(/%u/g, '')
}
return Math.random().toString(32).slice(2)
})
const computedRowKey: RowKey<T> = useCallback(
(record, index) => {
if (!record) return index
const recordKey = typeof rowKey === 'function' ? rowKey(record, index) : record[rowKey || defaultKey];
return recordKey === undefined ? index : recordKey
}, [rowKey]
)
const scrollY = useMemo<string | number>(() => _.get(scroll, 'y') as string | number, [scroll])
// 有子节点禁用排序功能
//#region
// 是否是树形结构
const isTree: boolean = useMemo(() => dataSource.some(item => _.get(item, 'children.length')), [dataSource])
// 树状结构或者编辑状态下 禁止拖拽
const sortable = useMemo(() => isTree ? false : !!props.onDragEnd, [isTree, props.onDragEnd])
const [lock, setLock] = useState(true) // 控制onSave回调
const isEdit = useMemo(() => editable === EditStatus.EDIT, [editable])
// level-1层数据复制dataSource,防止数据污染
const dataList = useMemo(() => _.cloneDeep(dataSource), [dataSource])
// level-1层数据,编辑时数据
const [cacheDataList, setCacheDataList] = useState([])
useEffect(() => {
// edit状态下打开锁,其他状态不变
if (isEdit) {
setLock(false)
}
setCacheDataList(list => {
if (isEdit) {
return cloneDatasource(dataList)
}
return []
})
}, [dataList, isEdit])
//#endregion
// 处理表格columns,可能有嵌套头部
//#region
const [columns, setColumns] = useState(props.columns);
useEffect(() => {
setColumns(props.columns)
}, [props.columns])
// 展开的expandedRowKeys
const [expandRowKeys, setexpandRowKeys] = useState([])
useEffect(() => {
if (Array.isArray(expandedRowKeys)) {
setexpandRowKeys(expandedRowKeys)
}
}, [expandedRowKeys])
// 是否触发虚拟滚动
const virtualScroll = useMemo(() => !!(scrollY && virtualScrollConfig), [scrollY, virtualScrollConfig])
const virtualScrollConfigInner = useMemo<VirtualScroll<T>>(() => (virtualScroll ? virtualScrollConfig === true ? defaultVirtualScrollConfig : { ...defaultVirtualScrollConfig, ...virtualScrollConfig } : {} as VirtualScroll<T>), [virtualScroll, virtualScrollConfig])
// 虚拟滚动的条数
const thresholdInner = useMemo(() => _.get(virtualScrollConfigInner, 'threshold', defaultVirtualScrollConfig.threshold), [virtualScrollConfigInner])
/**
* level-2层数据
* 根据是否编辑获取数据列表
* 同时添加g-index序号,获取所有可展开rowKey
* 编辑状态下会有__origin属性
*/
const [dataListWithIndex, expandableRowKeys] = useMemo(() => {
const list = isEdit ? cacheDataList : dataList;
return computeIndexAndRowKey<T>(list, computedRowKey)
}, [isEdit, cacheDataList, dataList, computedRowKey])
useEffect(() => {
// 默认打开所有子菜单
if (defaultExpandAllRows && _.isUndefined(expandedRowKeys)) {
setexpandRowKeys(expandableRowKeys)
}
}, [])
/**
* 虚拟滚动的相关数据
*/
const [outlineNum, setOutLineNum] = useState(0)
/**
* level-2层数据
* 总数据、实际要渲染的rowkeys,用这个数据计算实际高度
* 通过复制dataListWithIndex数据计算,因为在虚拟滚动下要刨除掉children,但是不能影响源数据
*/
const [renderListAll, renderRowKeys, tilingListAll] = useMemo(() => {
if (dataListWithIndex.length === 0) return [[], [], []]
return computeIndex<T>(dataListWithIndex, expandRowKeys, virtualScroll)
}, [dataListWithIndex, expandRowKeys])
// 单元格padding和border高度
const padddingBorder = useMemo(() => 2 * parseInt(cellPadding as string) + 1, [cellPadding])
// dom高度
const originRowHeight = useMemo(() => {
let height = parseInt(virtualScrollConfigInner.rowHeight as string)
if (rowSelection) {
height = Math.max(height, 20 + padddingBorder)
}
if (isTree) {
return Math.max(height, 18 + padddingBorder)
}
return height || 0
}, [virtualScrollConfigInner, isTree, padddingBorder, rowSelection])
// 行高
// 对单元格中的选择框和树状结构的展开按钮有影响
const originLineHeight = useMemo(() => {
if (virtualScroll && virtualScrollConfigInner.center) {
return (originRowHeight - padddingBorder) + 'px'
}
}, [virtualScrollConfigInner, padddingBorder, virtualScroll])
// 计算滚动比例
const rate = 1
// const rate = useMemo(() => Math.ceil(Math.chain(renderRowKeys.length).multiply(originRowHeight).divide(3e+7).done()), [renderRowKeys, originRowHeight])
// 逻辑上的行高,包括border
const rowHeight = useMemo(() => originRowHeight / rate + 1, [originRowHeight, rate])
const mainHeight = useMemo(() => renderRowKeys.length * rowHeight, [renderRowKeys, rowHeight])
// 最终渲染的数据
const renderList = useMemo(() => {
let list = renderListAll
if (virtualScroll) {
list = getVirtualList(outlineNum, thresholdInner, renderRowKeys, tilingListAll)
}
return list
}, [virtualScroll, outlineNum, renderRowKeys, tilingListAll])
//#endredion
const minHeight = useMemo(() => renderList.length > 0 ? scrollY : undefined, [scrollY, renderList])
const storageWidth = useMemo(() => getStorageWidth(tableKey)['table'] || _.get(scroll, 'x') || '100%', [tableKey])
const headerFixed = useMemo(() => !_.isUndefined(scrollY), [scrollY])
const bordered = useMemo(() => light ? false : customBorderd, [light, customBorderd])
// 当展开项发生变化的时候主动触发table的更新,去重新计算滚动条
const [emitReCompute, setEmitReCompute] = useState(0)
// 业务层修改editable状态的时候,计算修改前后数据的
useEffect(() => {
if (editable === EditStatus.SAVE && !lock) {
// 保存之后,锁上保证下次更新不会再次进入
setLock(true)
console.time('计算diff')
const diffData = diffList(dataList, cacheDataList, isTree)
console.timeEnd('计算diff')
onSave(_.cloneDeep(cacheDataList), diffData)
}
}, [editable, dataList, cacheDataList, isTree, lock])
//行选择
//#region
const [computedRowSelection, setselectedRowKeys, footerselection] = useRowSelection(rowSelection, dataListWithIndex, bordered)
const computedPagination = usePagination(pagination, computedRowSelection, dataSource.length)
const footerCallback = useCallback((currentPageData) => {
return (
<>
{currentPageData.length ? footerselection : null}
<div className='gant-table-footer-inner' style={{ flexDirection: footerDirection }}>
<div className='gant-table-footer-tail' style={{ flexDirection: footerDirection }}>
{typeof props.tail === 'function' && props.tail(currentPageData)}
</div>
{computedPagination && <Pagination size='small' {...computedPagination} />}
</div>
</>
)
}, [props.tail, computedPagination, footerselection, footerDirection])
const footer = useMemo(() => {
if (!(dataSource.length && footerselection) && !props.tail && !computedPagination) return null
return footerCallback
}, [props.tail, dataSource, computedPagination, footerselection])
//#endregion
// 滚动加载
//#region
const onscroll = useCallback(_.debounce<any>((e) => {
// 编辑状态下不触发whell事件
if (!onScroll || isEdit) return
if (e.type === 'wheel') {
// 向下滚动,视图上移
if (e.deltaY > 0) {
onScroll()
}
// 向上滚动,视图下移
else { }
} else {
const bodyTable: ScrollElement = e.target
if (bodyTable.scrollTop > bodyTable.scrollTopBackUp) { // 向下滚动
const scrollAvailable = bodyTable.scrollHeight - bodyTable.clientHeight
const lef = scrollAvailable - bodyTable.scrollTop
if (lef <= bodyTable.scrollHeight * 0.01) { // 滚动到临界点
if (!bodyTable.scrollloaded) {
bodyTable.scrollloaded = true
onScroll()
bodyTable.scrollloaded = false
}
} else {
// scrollloaded控制在一定距离内不会重复调用
bodyTable.scrollloaded = false
}
}
bodyTable.scrollTopBackUp = bodyTable.scrollTop
}
e.preventDefault()
}, 50), [onScroll, isEdit])
const [tableGroup] = useState(new Map<string, HTMLTableElement>())
/**
* 计算虚拟滚动误差
* 平均每滚动多少条要修正误差
*/
const scrollError = useMemo(() => {
if (scrollY) {
// 最后一屏之前渲染的条数
const leave = renderRowKeys.length - thresholdInner
// 最大滚动高度
const maxScroll = mainHeight - parseInt(scrollY as string)
// 最多滚动多少条
const maxScrollLength = Math.floor(maxScroll / rowHeight)
// 偏差条数
const error = leave - maxScrollLength
if (error > 0) {
return Math.floor(maxScrollLength / error)
}
}
return 0
}, [mainHeight, scrollY, renderRowKeys, thresholdInner, rowHeight])
/**
* 虚拟滚动
*/
const onVScroll = useCallback((e) => {
const { scrollTop } = e.currentTarget
const outTopLinevir = Math.floor(scrollTop / rowHeight); // 用于计算table的位置
// 校正移动的条数,修正设置的值,防止在rate不为1的情况下,出现数据遗漏的问题
const outTopLine = outTopLinevir + (scrollError > 0 ? Math.floor(outTopLinevir / scrollError) : 0)
// 设置数据
setOutLineNum(outTopLine)
// 实际渲染的高度
// td有个border-bottom,要加1
const domHeight = thresholdInner * rowHeight * rate
const outTopHeight = outTopLinevir * rowHeight
let top = Math.max(0, Math.min(mainHeight - domHeight, outTopHeight))
tableGroup.forEach(table => setStyle(table, `transform: translate(0, ${top}px)`))
// const table = tableGroup.get('bodyTable')
// setStyle(table, `transform: translate(0, ${top}px)`)
// e.preventDefault()
}, [thresholdInner, rowHeight, renderRowKeys, rate, mainHeight, tableGroup, scrollError])
// 绑定滚动事件
const bindScroll = useCallback(
() => {
// if (tableWraper && _.isEmpty(computedPagination)) {
if (tableWraper) {
const bodyTable: ScrollElement = tableWraper.querySelector('.ant-table-body');
bodyTable.scrollTopBackUp = bodyTable.scrollTop || 0
bodyTable.addEventListener('wheel', onscroll, false)
bodyTable.addEventListener('scroll', onscroll, false)
if (virtualScroll) {
bodyTable.addEventListener('wheel', onVScroll, false)
bodyTable.addEventListener('scroll', onVScroll, false)
}
}
},
[tableWraper, onscroll, computedPagination, virtualScroll, onVScroll],
)
// 移除滚动事件
const removeScroll = useCallback(
() => {
if (tableWraper) {
const bodyTable = tableWraper.querySelector('.ant-table-body');
bodyTable.removeEventListener('wheel', onscroll, false)
bodyTable.removeEventListener('scroll', onscroll, false)
if (virtualScroll) {
bodyTable.removeEventListener('wheel', onVScroll, false)
bodyTable.removeEventListener('scroll', onVScroll, false)
}
}
},
[tableWraper, onscroll, virtualScroll, onVScroll],
)
useEffect(() => {
bindScroll()
return () => {
removeScroll()
}
}, [tableWraper, bindScroll, removeScroll])
// table header
const onHeaderCell = useCallback(
(col, { hasFixed, hasChildren, index, originOnHeaderCell }) => {
const { key, dataIndex, fixed, align, children, } = col
// 明亮模式下或者是固定列以及有嵌套表头不允许resize
const resizable = !(light || fixed || hasChildren)
type resizeCellProps = Partial<{
orderType: string,
key: string,
flex: boolean,
fixed: boolean,
hasFixed: boolean,
tableKey: string,
dataIndex: string,
headerFixed: boolean,
resizable: boolean,
}>
interface cellProps extends resizeCellProps {
style: React.CSSProperties
}
let headerCellProps: cellProps = {
style: { width: col.width, maxWidth: col.width } // 防止折行模式下,被内容撑出
}
const ordered = orderList.find(order => dataIndex === order.fieldName)
if (ordered) {
headerCellProps.orderType = ordered.orderType
}
if (resizeCell) headerCellProps = ({
...headerCellProps,
key,
flex,
fixed,
hasFixed, // 有固定的列
tableKey,
dataIndex,
headerFixed, // 表格头固定
// 当前列是否可以被缩放,弹性缩放下,最后一列不允许缩放
resizable: flex ? resizable && index !== length - 1 : resizable,
})
if (typeof originOnHeaderCell === 'function') return { ...headerCellProps, ...originOnHeaderCell(col) }
return headerCellProps
},
[light, cellPadding, tableKey, orderList, resizeCell, flex, headerFixed],
)
const onCell = useCallback(
(col, record, rowIndex, { hasFixed, originOnCell }) => {
const { dataIndex, editConfig, fixed } = col
const cellEditable = _.isPlainObject(editConfig) && !_.isEmpty(editConfig)
// 修正rowIndex值
const computedRowIndex = rowIndex + outlineNum
// 虚拟滚动或者带有固定列的表格不换行
// 根据是否有固定列,以及是否是虚拟滚动来控制文本是否折行
const cWrap = (virtualScroll || hasFixed) ? false : wrap
const style: React.CSSProperties = { width: col.width }
if (!cWrap) {
// 防止折行模式下,被内容撑出
// 如果有maxWidth,会有出现缩小单元格时内容在单元格外的异常
style.maxWidth = col.width
}
let defaultCellProps = {
style,
wrap: cWrap,
light,
record: { ...record },
sortable,
rowIndex: computedRowIndex,
dataIndex,
cellPadding,
editConfig: {},
}
if (cellEditable) defaultCellProps.editConfig = editConfig
if (originOnCell) {
return { ...defaultCellProps, ...originOnCell(record, computedRowIndex) }
}
return defaultCellProps
},
[wrap, light, sortable, tableKey, cellPadding, headerFixed, virtualScroll, outlineNum],
)
/**
* columns API
* @param editConfig object 编辑对象
* showDirt boolean 是否显示脏标记
* render function 编辑时的渲染函数
* editValue string|function 编辑组件的值
*/
const convertColumns = useCallback(
(cols, nest = false): GColumnProps<T>[] => {
const hasFixed = cols.some(col => col.fixed)
const computedCols = cols.map(({ width, onHeaderCell: originOnHeaderCell, onCell: originOnCell, render: originRender, ...col }, index) => {
// 添加自定义render
col.render = (...args) => renderColumnItem({ ...col, render: originRender }, ...args as [string, object, number])
const hasChildren = _.get(col, 'children.length')
if (hasChildren) {
// 嵌套表头不允许resize
col.children = convertColumns(col.children, true)
}
col.width = getStorageWidth(tableKey)[col.dataIndex] || width
col.onHeaderCell = col => onHeaderCell(col, { hasFixed, hasChildren, index, originOnHeaderCell })
col.onCell = (record, rowIndex) => onCell(col, record, rowIndex + outlineNum, { hasFixed, originOnCell })
return col
})
if (withIndex >= 0 && !nest) {
const index = {
dataIndex: 'g-index',
title: '序号',
width: 40,
}
return [...computedCols.slice(0, withIndex), index, ...computedCols.slice(withIndex)]
}
return computedCols
},
[onHeaderCell, onCell, tableKey, withIndex, outlineNum]
)
//#endregion
// 处理表格行删除线
const onRow = useCallback(
(record, index) => {
const pureRecord = getPureRecord(record)
type OptialProps = Partial<{
onClick: (e: React.MouseEvent) => void
}>
interface onRowProps extends OptialProps {
isDeleted: boolean,
rowIndex: number,
sortable: boolean,
originRecord?: T
}
const rowIndex = index + outlineNum
const defaultRowProps: onRowProps = {
isDeleted: record.isDeleted,
rowIndex,
sortable
}
if (isEdit) {
defaultRowProps.originRecord = record[originKey]
}
let originListener: TableEventListeners = {}
if (originOnRow) {
originListener = originOnRow(pureRecord, rowIndex)
}
if (_.get(rowSelection, 'clickable')) {
const getCheckBoxProps = _.get(rowSelection, 'getCheckboxProps')
let checkable = true
if (getCheckBoxProps && typeof getCheckBoxProps === 'function') {
const boxProps = getCheckBoxProps(pureRecord)
checkable = !_.get(boxProps, 'disable')
}
if (checkable) {
defaultRowProps.onClick = e => {
setselectedRowKeys(record)
if (typeof originListener.onClick === 'function') {
originListener.onClick(e)
}
}
}
}
if (originOnRow) {
return {
...originListener,
...defaultRowProps
}
}
return defaultRowProps
},
[sortable, setselectedRowKeys, rowSelection, outlineNum]
)
// 表格中所有使用的组件
// 控制什么时候显示可编辑组件
//#region
const onDragEnd = useCallback(
(result) => {
if (!result.destination) return
// 要减掉第一行,所以需要要减1
let sourceIndex = result.source.index;
let destinationIndex = result.destination.index;
const list = switchIndex(dataSource, sourceIndex, destinationIndex)
sortable && props.onDragEnd(list)
},
[dataSource, sortable]
)
const components = useMemo(
() => (
{
table: TableComponent,
header: {
// wrapper: HeaderWrapper,
row: HeaderRow,
cell: HeaderCell,
},
body: {
wrapper: (sortable ? BodyWrapper : 'tbody') as React.ReactType,
row: BodyRow, // 添加删除线支持
cell: BodyCell,
},
}
),
[resizeCell, BodyWrapper, sortable, HeaderCell]
);
//#endregion
const tableColumns = useMemo(() => convertColumns(columns), [columns, convertColumns, orderList])
// dataIndex的索引
const computedColIndex = useMemo(() => getComputedColIndex(tableColumns), [tableColumns])
const expandIconColumnIndex = useMemo(() => {
let index = 0
index = Math.max(tableColumns.findIndex(item => item.expandColumn), 0)
return computedRowSelection ? index + 1 : index
}, [tableColumns, computedRowSelection])
// 缺省显示
const emptyText = useMemo(() => {
return (
<div className="gant-align-center" style={{ height: scrollY }}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={props.emptyDescription} />
</div>
)
}, [scrollY, props.emptyDescription])
// 展开变化,触发重新计算滚动相关数据
const expandedRowsChange = useCallback(
(expandedRowKeys: string[] | number[]) => {
setEmitReCompute(e => e + 1)
},
[],
)
const expand = useCallback(
(expanded, record) => {
let rowkey = expandRowKeys
const key = record["g-row-key"];
if (expanded) {
rowkey = [...expandRowKeys, key]
} else {
// row was collapse
const expandedRowIndex = expandRowKeys.indexOf(key);
if (expandedRowIndex !== -1) rowkey = [...expandRowKeys.slice(0, expandedRowIndex), ...expandRowKeys.slice(expandedRowIndex + 1)]
}
setexpandRowKeys(rowkey)
if (onExpandedRowsChange) {
onExpandedRowsChange(rowkey)
}
if (onExpand) {
const pureRecord = getPureRecord<T>(record)
onExpand(expanded, pureRecord)
}
},
[onExpandedRowsChange, onExpand, expandRowKeys],
)
// 劫持headerRight
let headerRightElement = useMemo(() => {
let actions = null
if (isEdit && typeof editActions === 'function') {
const keys = computedRowSelection ? (computedRowSelection.selectedRowKeys || []) : []
actions = editActions([cacheDataList, setCacheDataList], keys)
}
return (
<>
{actions}
{headerRight}
</>
)
}, [isEdit, editActions, cacheDataList, headerRight, computedRowSelection])
const onResize = useCallback(
() => {
setEmitReCompute(e => e + 1)
},
[],
)
const dataContextValue = useMemo(() => ({
isTree,
cellPadding,
dataSource: cacheDataList,
setDataSource: setCacheDataList,
computedRowKey,
editable, // 用于控制脏标记的显示,如果是save就会清除掉脏标记
computedColIndex,
computedRowSelection,
originRowHeight,
originLineHeight,
}), [isTree, cellPadding, cacheDataList, editable, computedColIndex, computedRowSelection, originRowHeight, originLineHeight])
const tableContextValue = useMemo(() => ({
light,
spacing,
dataSource, // 滚动加载的时候触发更新header的overflow
emitReCompute, // 展开的时候触发更新header的overflow
headerFixed,
tableColumns,
onResize,
virtualScroll,
mainHeight,
tableGroup,
outlineNum,
thresholdInner,
renderRowKeys,
storageWidth,
scrollY,
}), [light, spacing, dataSource, emitReCompute, tableColumns, headerFixed, onResize, virtualScroll, mainHeight, tableGroup, outlineNum, thresholdInner, renderRowKeys, storageWidth, scrollY])
const bodyWrapperContext = useMemo(() => ({ onDragEnd }), [onDragEnd])
const style = useMemo(() => {
const s = { ...(props.style || {}) }
s['--padding'] = getStyleText(cellPadding)
s['--lineHeight'] = getStyleText(originLineHeight)
return s
}, [props.style, cellPadding, originLineHeight])
const getPrefixCls = (cls) => 'gant-' + cls;
const renderTable = () => {
const {
pagination,
title = '',
className,
headerLeft,
headerMarginBottom,
bodyStyle,
scroll = {},
headerProps,
locale = {},
...tableProps
} = props;
const tablePrefixCls = getPrefixCls('table');
const reizetablePrefixCls = getPrefixCls('table-resizable');
const sortablePrefixCls = getPrefixCls('table-sortable');
const zebraPrefixCls = getPrefixCls('table-zebra');
const lightPrefixCls = getPrefixCls('table-light');
return (
<>
{(title || headerRightElement || headerLeft) && (
<Header
title={title}
{...headerProps}
beforeExtra={headerLeft}
extra={headerRightElement}
/>
)}
<DataContext.Provider value={dataContextValue}>
<TableContext.Provider value={tableContextValue}>
<TableBodyWrapperContext.Provider value={bodyWrapperContext}>
<Table
size='small'
scroll={{ ...scroll, x: storageWidth }}
locale={{ emptyText, ...locale }}
{...tableProps}
expandedRowKeys={expandRowKeys}
onExpandedRowsChange={expandedRowsChange}
onExpand={expand}
bordered={bordered}
dataSource={renderList}
onRow={onRow}
// rowKey={computedRowKey}
rowKey='g-row-key'
components={{ ...components, ...tableProps.components }}
pagination={false}
footer={footer}
bodyStyle={{ minHeight, ...bodyStyle, }}
className={
classnames(
className,
tablePrefixCls,
{
[reizetablePrefixCls]: resizeCell,
// 明亮模式禁用斑马线
[zebraPrefixCls]: !light && isZebra,
[sortablePrefixCls]: sortable,
[lightPrefixCls]: light,
}
)
}
columns={tableColumns}
rowSelection={computedRowSelection as TableRowSelection<T>}
expandIconColumnIndex={expandIconColumnIndex}
style={style}
/>
</TableBodyWrapperContext.Provider>
</TableContext.Provider>
</DataContext.Provider>
</>
)
}
return (
<div ref={ref}>
{renderTable()}
</div >
)
}
Example #10
Source File: WebsiteTree.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
WebsiteTreeViewer: React.FC<WebsiteTreeViewerProp> = (props) => {
const [treeData, setTreeData] = useState<AntDTreeData[]>([])
const [autoRefresh, setAutoRefresh] = useState(!!props.targets)
const [selected, setSelected] = useState(props.targets)
const [limit, setLimit] = useState(20)
const [page, setPage] = useState(1)
const [total, setTotal] = useState(0)
const [searchTarget, setSearchTarget] = useState(props.targets)
const [loading, setLoading] = useState(false)
const [isDelKey, setisDelKey] = useState("")
const refresh = () => {
setLoading(true)
ipcRenderer
.invoke("GenerateWebsiteTree", {
Targets: searchTarget
})
.then((data: { TreeDataJson: Uint8Array }) => {
const treeDataRaw = ConvertWebsiteForestToTreeData(
JSON.parse(Buffer.from(data.TreeDataJson).toString("utf8")) as WebsiteForest
) as AntDTreeData[]
setTreeData([...treeDataRaw])
setLoading(false)
})
}
const delReord = (node: AntDTreeData) => {
function fetchUrl(node: AntDTreeData, url: string): string {
if (!!node.parent) {
const str = `${node.title[0] === "/" ? "" : "/"}${node.title}${url}`
return fetchUrl(node.parent, str)
} else {
return `${node.title}${url}`
}
}
ipcRenderer
.invoke("delete-http-flow-signle", {URLPrefix: fetchUrl(node, "")})
.then((res) => {
refresh()
})
}
const fetchUrl = (data: AntDTreeData | any, arr: string[]) => {
arr.unshift(data.title.indexOf("/") < 0 && !!data?.parent?.title ? `/${data.title}` : data.title)
if(data?.parent?.title) fetchUrl(data?.parent, arr)
}
// 构建 table
const uidToNodeMap = new Map<string, AntDTreeData>()
const viewNode = (node: AntDTreeData) => {
node.children.map(viewNode)
uidToNodeMap.set(node.key, node)
}
treeData.forEach(viewNode)
useEffect(() => {
setSearchTarget(props.targets)
refresh()
}, [props.targets])
useEffect(() => {
if (!autoRefresh) {
return
}
const id = setInterval(() => {
if (!autoRefresh) {
return
}
refresh()
}, 3000)
return () => {
clearInterval(id)
}
}, [autoRefresh])
return (
<>
<Row gutter={8} style={{height: "100%"}}>
<Col span={7} style={{height: "100%", overflow: "auto"}}>
<Spin spinning={loading}>
<Card
title={
<Space>
业务结构
<Button
type={"link"}
size={"small"}
icon={<ReloadOutlined/>}
onClick={() => {
refresh()
}}
/>
</Space>
}
size={"small"}
extra={
!props.targets ? (
<Space>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
refresh()
}}
layout={"inline"}
>
<InputItem
label={"URL关键字"}
value={searchTarget}
setValue={setSearchTarget}
width={100}
/>
<Form.Item style={{marginLeft: 0, marginRight: 0}}>
<Button
size={"small"}
type='link'
htmlType='submit'
icon={<SearchOutlined/>}
style={{marginLeft: 0, marginRight: 0}}
/>
</Form.Item>
</Form>
{/*<Input onBlur={r => setSearchTarget(r.target.value)} size={"small"}/>*/}
</Space>
) : (
<Space>
<Form onSubmitCapture={e => {
e.preventDefault()
}} size={"small"}>
<SwitchItem
label={"自动刷新"} formItemStyle={{marginBottom: 0}}
value={autoRefresh} setValue={setAutoRefresh}
/>
</Form>
</Space>
)
}
>
<div style={{width: "100%", overflowX: "auto", maxHeight: props.maxHeight}}>
<Tree
className='ellipsis-tree'
showLine={true}
treeData={treeData}
titleRender={(nodeData: any) => {
return (
<div style={{display: "flex", width: "100%"}}>
<span
title={`${nodeData.title}`}
className='titleContent'
>
{nodeData.title}
</span>
<Popconfirm
title={"确定要删除该记录吗?本操作不可恢复"}
onConfirm={(e) => {
// 阻止冒泡
e?.stopPropagation()
delReord(nodeData)
}}
onCancel={(e) => {
// 阻止冒泡
e?.stopPropagation()
}}
>
{isDelKey === nodeData.title && (
<DeleteOutlined
style={{
paddingLeft: 5,
paddingTop: 5,
cursor: "pointer",
color: "#707070"
}}
onClick={(e) => {
// 阻止冒泡
e.stopPropagation()
}}
/>
)}
</Popconfirm>
</div>
)
}}
onSelect={(key) => {
if (key.length <= 0) {
setisDelKey("")
return
}
const selectedKey = key[0]
const node = uidToNodeMap.get(selectedKey as string)
if (!node) {
return
}
let path = [node.title]
setisDelKey(path[0])
let parent = node.parent
while (!!parent) {
path.unshift(
!parent.parent ? parent.title + "/" : parent.title
)
parent = parent.parent
}
const pathStr = (path || []).join("")
setSelected(pathStr)
}}
autoExpandParent={true}
defaultExpandAll={true}
onRightClick={({event, node}) => {
showByContextMenu(
{
data: [
{key:'bug-test',title:"发送到漏洞检测"},
{key:'scan-port',title:"发送到端口扫描"},
{key:'brute',title:"发送到爆破"}
],
onClick: ({key}) => {
let str: string[] = []
fetchUrl(node, str)
const param = {
SearchURL: str,
Pagination: {
...genDefaultPagination(20),
Page: 1,
Limit: 101
}}
ipcRenderer.invoke("QueryHTTPFlows", param).then((data: QueryGeneralResponse<HTTPFlow>) => {
if(data.Total > 100){
failed("该节点下的URL数量超过100个,请缩小范围后再重新操作")
return
}
ipcRenderer.invoke("send-to-tab", {
type: key,
data:{URL: JSON.stringify(data.Data.map(item => item.Url))}
})
})
}
}
)}}
/>
</div>
</Card>
</Spin>
</Col>
<Col span={17} style={{height: "100%"}}>
<Card
size={"small"}
className={"flex-card"}
title={"HTTP Flow Record"}
bodyStyle={{padding: 0}}
extra={
<Pagination
simple={true}
defaultCurrent={1}
size={"small"}
pageSize={limit}
current={page}
onChange={(page, size) => {
setPage(page)
setLimit(size || 20)
}}
total={total}
/>
}
>
<HTTPFlowMiniTable
onTotal={setTotal}
filter={{
SearchURL: selected,
Pagination: {
...genDefaultPagination(20),
Page: page,
Limit: limit
}
}}
source={""}
/>
</Card>
</Col>
</Row>
</>
)
}
Example #11
Source File: Participants.tsx From nanolooker with MIT License | 4 votes |
Participants: React.FC = () => {
const { account } = useParams<PageParams>();
const [currentPage, setCurrentPage] = React.useState<number>(1);
const {
participants,
meta: { total, perPage },
isLoading: isParticipantsLoading,
} = useParticipants({
account,
page: currentPage,
});
const history = useHistory();
const isLargeAndLower = !useMediaQuery("(min-width: 992px)");
const onChange = (e: React.ChangeEventHandler<HTMLInputElement>) => {
// @ts-ignore
const { value } = e.currentTarget;
if (value && isValidAccountAddress(value)) {
history.push(`/treasure-hunt/${value}`);
} else if (!value && history.location.pathname !== "/treasure-hunt") {
history.push("/treasure-hunt");
}
};
return (
<>
<Rules />
<Title level={3} id="treasure-hunt-title">
{total} Participant{participants.length === 1 ? "" : "s"}
</Title>
<Input
defaultValue={account}
// @ts-ignore
onChange={onChange}
placeholder="Search for participant address"
style={{ maxWidth: isLargeAndLower ? "100%" : "60%", marginBottom: 12 }}
allowClear
></Input>
<Card size="small" bordered={false} className="detail-layout">
{!isLargeAndLower ? (
<Row gutter={6}>
<Col md={8}>Participant{participants.length === 1 ? "" : "s"}</Col>
<Col md={3} style={{ textAlign: "center" }}>
<TwitterOutlined /> Twitter
</Col>
<Col md={3} style={{ textAlign: "center" }}>
NanoCafe.cc
</Col>
<Col md={3} style={{ textAlign: "center" }}>
Representative
</Col>
<Col md={3} style={{ textAlign: "center" }}>
NanoBrowserQuest
</Col>
<Col md={4} style={{ textAlign: "center" }}>
Payout
</Col>
</Row>
) : null}
{isParticipantsLoading
? Array.from(Array(5).keys()).map(index => (
<Row gutter={6} key={index}>
<Col md={8}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col md={3}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col md={3}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col md={3}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col md={3}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col md={4}>
<Skeleton loading={true} paragraph={false} active />
</Col>
</Row>
))
: null}
{!isParticipantsLoading && !participants.length ? (
<Row>
<Col xs={24} style={{ textAlign: "center" }}>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={
account
? "Participant not found, if you posted your account on the twitter thread, wait a few seconds."
: "No participants found"
}
style={{ padding: "12px" }}
/>
</Col>
</Row>
) : null}
{!isParticipantsLoading && participants.length ? (
<>
{participants.map(
({
account,
twitter,
nanoCafe,
representative,
nanoBrowserQuest,
payout,
}) => (
<Row gutter={6} key={account}>
<Col xs={24} lg={8}>
<Link
to={`/account/${account}`}
style={{ fontSize: "14px" }}
className="break-word"
>
{account}
</Link>
</Col>
<Col xs={12} md={6} lg={3} style={{ textAlign: "center" }}>
<Space size={[6, 12]}>
{isLargeAndLower ? (
<>
<TwitterOutlined /> Twitter
</>
) : null}
<Progress isCompleted={twitter !== "0"} />
</Space>
</Col>
<Col xs={12} md={6} lg={3} style={{ textAlign: "center" }}>
<Space size={6}>
{isLargeAndLower ? <Text>NanoCafe.cc</Text> : null}
<Progress hash={nanoCafe} />
</Space>
</Col>
<Col xs={12} md={6} lg={3} style={{ textAlign: "center" }}>
<Space size={6}>
{isLargeAndLower ? <Text>Representative</Text> : null}
<Progress hash={representative} />
</Space>
</Col>
<Col xs={12} md={6} lg={3} style={{ textAlign: "center" }}>
<Space size={6}>
{isLargeAndLower ? <Text>NanoBrowserQuest</Text> : null}
<Progress hash={nanoBrowserQuest} />
</Space>
</Col>
<Col xs={24} md={6} lg={4} style={{ textAlign: "center" }}>
{payout && payout !== "0" ? (
<Link to={`block/${payout}`} className="truncate">
{payout}
</Link>
) : (
"Waiting for completion"
)}
</Col>
</Row>
),
)}
{!account && perPage ? (
<Row className="row-pagination">
<Col xs={24} style={{ textAlign: "right" }}>
<Pagination
size="small"
{...{
total,
pageSize: perPage,
current: currentPage,
disabled: false,
onChange: (page: number) => {
const element = document.getElementById(
"treasure-hunt-title",
);
element?.scrollIntoView();
setCurrentPage?.(page);
},
showSizeChanger: false,
}}
/>
</Col>
</Row>
) : null}
</>
) : null}
</Card>
</>
);
}
Example #12
Source File: Leaderboard.tsx From nanolooker with MIT License | 4 votes |
Leaderboard: React.FC<Props> = ({ topScores }) => {
const { t } = useTranslation();
const pageSize = 15;
const [currentPage, setCurrentPage] = React.useState(1);
const [paginatedTopScores, setPaginatedTopScores] = React.useState(
[] as PlayerScore[][],
);
React.useEffect(() => {
setPaginatedTopScores(chunk(topScores, pageSize));
}, [topScores]);
return (
<>
<Title level={3}>{t("pages.nanoquakejs.leaderboard")}</Title>
<Card size="small" bordered={false} className="detail-layout">
<Row gutter={12}>
<Col xs={4}>{t("pages.nanoquakejs.rank")}</Col>
<Col xs={14}>{t("pages.nanoquakejs.player")}</Col>
<Col xs={6}>{t("pages.nanoquakejs.frags")}</Col>
</Row>
{!topScores?.length ? (
Array.from(Array(5).keys()).map(index => (
<Row gutter={12} key={index}>
<Col xs={4}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col xs={14}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col xs={6}>
<Skeleton loading={true} paragraph={false} active />
</Col>
</Row>
))
) : (
<>
{paginatedTopScores[currentPage - 1]?.map(
({ rank, player, frags }) => (
<Row gutter={12} key={rank}>
<Col xs={4}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
#{rank} <Trophy rank={rank} />
</Text>
</Col>
<Col xs={14}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
{player}
</Text>
</Col>
<Col xs={6}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
{frags}
</Text>
</Col>
</Row>
),
)}
<Row className="row-pagination">
<Col xs={24} style={{ textAlign: "right" }}>
<Pagination
size="small"
{...{
total: topScores.length,
pageSize,
current: currentPage,
disabled: false,
onChange: (page: number) => {
setCurrentPage?.(page);
},
showSizeChanger: false,
}}
/>
</Col>
</Row>
</>
)}
</Card>
</>
);
}
Example #13
Source File: Leaderboard.tsx From nanolooker with MIT License | 4 votes |
Leaderboard: React.FC = () => {
const { t } = useTranslation();
const { leaderboard, isLoading } = useNanoBrowserQuestLeaderboard();
const pageSize = 20;
const [currentPage, setCurrentPage] = React.useState(1);
const [paginatedTopScores, setPaginatedTopScores] = React.useState(
[] as any[][],
);
React.useEffect(() => {
setPaginatedTopScores(chunk(leaderboard, pageSize));
}, [leaderboard]);
return (
<>
<Title level={3}>{t("pages.nanobrowserquest.leaderboard")}</Title>
<Card size="small" bordered={false} className="detail-layout">
<Row gutter={12}>
<Col xs={3}>{t("pages.nanobrowserquest.rank")}</Col>
<Col xs={12}>{t("pages.nanobrowserquest.player")}</Col>
<Col xs={3}>{t("pages.nanobrowserquest.level")}</Col>
<Col xs={6}>{t("pages.nanobrowserquest.exp")}</Col>
</Row>
{isLoading ? (
Array.from(Array(5).keys()).map(index => (
<Row gutter={12} key={index}>
<Col xs={3}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col xs={12}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col xs={3}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col xs={6}>
<Skeleton loading={true} paragraph={false} active />
</Col>
</Row>
))
) : (
<>
{paginatedTopScores[currentPage - 1]?.map(
({ rank, player, exp, nanoPotions }) => (
<Row gutter={12} key={rank}>
<Col xs={3}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
#{rank} <Trophy rank={rank} />
</Text>
</Col>
<Col xs={12}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
{player}
</Text>
</Col>
<Col xs={3}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
{getLevel(exp)}
</Text>
</Col>
<Col xs={6}>
<Text
style={{ fontSize: fontSizeToRankMap[rank] ?? "auto" }}
>
{exp}
</Text>
</Col>
</Row>
),
)}
<Row className="row-pagination">
<Col xs={24} style={{ textAlign: "right" }}>
<Pagination
size="small"
{...{
total: leaderboard.length,
pageSize,
current: currentPage,
disabled: false,
onChange: (page: number) => {
setCurrentPage?.(page);
},
showSizeChanger: false,
}}
/>
</Col>
</Row>
</>
)}
</Card>
</>
);
}
Example #14
Source File: RichList.tsx From nanolooker with MIT License | 4 votes |
RichList: React.FC = () => {
const { t } = useTranslation();
// @TODO Add search input
// const [search, setSearch] = React.useState("");
const [currentPage, setCurrentPage] = React.useState<number>(1);
const {
data,
meta: { total, perPage },
isLoading: isRichListLoading,
} = useRichList({
// account: search,
page: currentPage,
});
const { fiat } = React.useContext(PreferencesContext);
const {
marketStatistics: {
currentPrice,
priceStats: { bitcoin: { [fiat]: btcCurrentPrice = 0 } } = {
bitcoin: { [fiat]: 0 },
},
},
} = React.useContext(MarketStatisticsContext);
const { availableSupply = 123123123 } = useAvailableSupply();
const isSmallAndLower = !useMediaQuery("(min-width: 576px)");
const startIndex = (currentPage - 1) * perPage + 1;
return (
<>
<Title level={3} id="rich-list-title">
{t("pages.distribution.richList")}
</Title>
<Card size="small" bordered={false} className="detail-layout">
{!isSmallAndLower ? (
<>
<Row gutter={6}>
<Col sm={2} md={2} xl={2}>
#
</Col>
<Col sm={12} md={12} xl={14}>
{t("common.account")}
</Col>
<Col sm={6} md={6} xl={4}>
{t("common.balance")}
</Col>
<Col sm={4} md={4} xl={2}></Col>
</Row>
</>
) : null}
{isRichListLoading
? Array.from(Array(5).keys()).map(index => (
<Row gutter={6} key={index}>
<Col sm={2} md={2} xl={2}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col sm={12} md={12} xl={14}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col sm={6} md={6} xl={4}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col sm={4} md={4} xl={2}>
<Skeleton loading={true} paragraph={false} active />
</Col>
</Row>
))
: null}
{!isRichListLoading && data.length ? (
<>
{data.map(({ account, balance, alias }, index) => (
<Row gutter={6} key={account}>
<Col sm={2} md={2} xl={2}>
<strong>{startIndex + index}</strong>
</Col>
<Col sm={12} md={12} xl={14}>
{alias ? (
<div className="color-important">{alias}</div>
) : null}
<Link to={`/account/${account}`} className="break-word">
{account}
</Link>
</Col>
<Col sm={6} md={6} xl={4}>
Ӿ {new BigNumber(balance).toFormat()}
<span
className="color-muted"
style={{
fontSize: 12,
display: "block",
}}
>
{availableSupply && account !== BURN_ACCOUNT
? `${roundOff(
new BigNumber(balance)
.times(100)
.dividedBy(availableSupply)
.toNumber(),
)}%`
: null}
</span>
</Col>
<Col sm={4} md={4} xl={4}>
{currentPrice && btcCurrentPrice ? (
<>
{`${CurrencySymbol?.[fiat]} ${new BigNumber(balance)
.times(currentPrice)
.toFormat(CurrencyDecimal?.[fiat])}`}
<span
className="color-muted"
style={{
fontSize: 12,
display: "block",
}}
>
{`${new BigNumber(balance)
.times(currentPrice)
.dividedBy(btcCurrentPrice)
.toFormat(12)} BTC`}
</span>
</>
) : null}
</Col>
</Row>
))}
<Row className="row-pagination">
<Col xs={24} style={{ textAlign: "right" }}>
<Pagination
size="small"
{...{
total,
pageSize: perPage,
current: currentPage,
disabled: false,
onChange: (page: number) => {
const element = document.getElementById(
"rich-list-title",
);
element?.scrollIntoView();
setCurrentPage?.(page);
},
showSizeChanger: false,
}}
/>
</Col>
</Row>
</>
) : null}
</Card>
</>
);
}
Example #15
Source File: index.tsx From nanolooker with MIT License | 4 votes |
TransactionsTable = ({
scrollTo,
data,
isLoading,
showPaginate,
isPaginated,
pageSize,
currentPage,
totalPages,
setCurrentPage,
setCurrentHead,
}: TransactionsTableProps) => {
const { t } = useTranslation();
const { theme, natricons } = React.useContext(PreferencesContext);
const { knownAccounts } = React.useContext(KnownAccountsContext);
const isLargeAndHigher = useMediaQuery("(min-width: 992px)");
const smallNatriconSize = !useMediaQuery("(min-width: 768px)");
return (
<Card size="small" className="transaction-card" id={scrollTo}>
{isLoading ? (
<div className="ant-spin-nested-loading">
<div>
<div className="ant-spin ant-spin-spinning">
<span className="ant-spin-dot ant-spin-dot-spin">
<i className="ant-spin-dot-item"></i>
<i className="ant-spin-dot-item"></i>
<i className="ant-spin-dot-item"></i>
<i className="ant-spin-dot-item"></i>
</span>
</div>
</div>
</div>
) : null}
{isLargeAndHigher ? (
<Row
gutter={[{ xs: 6, sm: 12, md: 12, lg: 12 }, 12]}
className="row-header color-muted"
>
<Col xs={0} lg={2}>
{t("transaction.type")}
</Col>
{natricons ? <Col xs={0} lg={2}></Col> : null}
<Col xs={0} lg={natricons ? 12 : 14}>
{t("transaction.accountAndBlock")}
</Col>
<Col xs={0} lg={5}>
{t("transaction.amount")}
</Col>
<Col xs={0} lg={3} style={{ textAlign: "right" }}>
{t("common.date")}
</Col>
</Row>
) : null}
{data?.length ? (
<>
{data.map(
(
{
subtype,
type,
account: historyAccount,
amount,
representative,
hash,
confirmed,
local_timestamp: localTimestamp,
}: History,
index: number,
) => {
const transactionType = subtype || type;
const themeColor = `${transactionType.toUpperCase()}${
theme === Theme.DARK ? "_DARK" : ""
}`;
// When transaction is a representative change, the account is the representative
const account =
transactionType === "change" ? representative : historyAccount;
const knownAccount =
account &&
knownAccounts.find(
({ account: knownAccount }) => account === knownAccount,
);
const modifiedTimestamp = Number(localTimestamp) * 1000;
const modifiedDate = new Date(modifiedTimestamp);
return (
<Row
key={index}
justify="space-between"
align="middle"
gutter={[12, 12]}
>
<Col
xs={natricons ? 12 : 24}
md={4}
lg={2}
className="gutter-row"
span={6}
>
<Tooltip
placement="right"
title={
typeof confirmed !== "undefined"
? t(
`pages.block.${
toBoolean(confirmed) === false
? "pending"
: "confirmed"
}Status`,
)
: null
}
>
<Tag
// @ts-ignore
color={TwoToneColors[themeColor]}
style={{ textTransform: "capitalize" }}
className={`tag-${subtype || type}`}
icon={
typeof confirmed !== "undefined" ? (
toBoolean(confirmed) === false ? (
<SyncOutlined spin />
) : (
<CheckCircleOutlined />
)
) : null
}
>
{t(`transaction.${transactionType}`)}
</Tag>
</Tooltip>
</Col>
{natricons ? (
<Col xs={12} md={2} style={{ textAlign: "right" }}>
<Natricon
account={account}
style={{
margin: "-12px -6px -18px -18px ",
width: `${smallNatriconSize ? 60 : 80}px`,
height: `${smallNatriconSize ? 60 : 80}px`,
}}
/>
</Col>
) : null}
<Col
xs={24}
md={natricons ? 18 : 20}
lg={natricons ? 12 : 14}
>
{knownAccount ? (
<div className="color-important">
{knownAccount.alias}
</div>
) : null}
{account ? (
<Link
to={`/account/${account}`}
className="break-word color-normal"
>
{account}
</Link>
) : (
t("common.notAvailable")
)}
<br />
<Link
to={`/block/${hash}`}
className="color-muted truncate"
>
{hash}
</Link>
</Col>
<Col xs={16} md={12} lg={5}>
<Text
// @ts-ignore
style={{ color: Colors[themeColor] }}
className="break-word"
>
{!amount || amount === "0"
? t("common.notAvailable")
: ""}
{amount && amount !== "0"
? `Ӿ ${new BigNumber(rawToRai(amount)).toFormat()}`
: ""}
</Text>
</Col>
<Col xs={8} md={12} lg={3} style={{ textAlign: "right" }}>
{Number(localTimestamp) ? (
<>
{modifiedDate.getFullYear()}/
{String(modifiedDate.getMonth() + 1).padStart(2, "0")}/
{String(modifiedDate.getDate()).padStart(2, "0")}
<br />
<TimeAgo
locale={i18next.language}
style={{ fontSize: "12px" }}
className="color-muted"
datetime={modifiedTimestamp}
live={false}
/>
</>
) : (
t("common.unknown")
)}
</Col>
</Row>
);
},
)}
{showPaginate ? (
<Row className="row-pagination">
{isPaginated ? (
<Col xs={24} style={{ textAlign: "right" }}>
<Pagination
size="small"
{...{
total: totalPages,
pageSize,
current: currentPage,
disabled: false,
onChange: (page: number) => {
if (scrollTo) {
const element = document.getElementById(scrollTo);
element?.scrollIntoView();
}
setCurrentPage?.(page);
},
showSizeChanger: false,
}}
/>
</Col>
) : null}
{!isPaginated && setCurrentHead ? (
<Col xs={24} style={{ textAlign: "center" }}>
<Button
// @ts-ignore
onClick={setCurrentHead}
type={theme === Theme.DARK ? "primary" : "default"}
>
{t("pages.account.loadMoreTransactions")}
</Button>
</Col>
) : null}
</Row>
) : null}
</>
) : (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
style={{ padding: "12px" }}
/>
)}
</Card>
);
}
Example #16
Source File: index.tsx From nanolooker with MIT License | 4 votes |
Delegators: React.FC = () => {
const { t } = useTranslation();
const [currentPage, setCurrentPage] = React.useState<number>(1);
const { account } = React.useContext(AccountInfoContext);
const { delegators: allDelegators, getDelegators } = React.useContext(
DelegatorsContext,
);
const { knownAccounts } = React.useContext(KnownAccountsContext);
const {
delegators,
meta: { total, perPage },
isLoading: isDelegatorsLoading,
} = useDelegators({
account,
page: currentPage,
});
const isSmallAndLower = !useMediaQuery("(min-width: 576px)");
React.useEffect(() => {
getDelegators();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const count = new BigNumber(allDelegators[account] || 0).toFormat();
return (
<>
<div
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
}}
>
<Title level={3} id="delegator-title">
{count} {t(`common.delegator${count !== "1" ? "s" : ""}`)}
</Title>
<Link to={`/account/${account}`}>
<Button size="small" style={{ marginTop: "6px" }}>
{t("pages.account.viewTransactions")}
</Button>
</Link>
</div>
<Card size="small" bordered={false} className="detail-layout">
{!isDelegatorsLoading && !isSmallAndLower ? (
<>
<Row gutter={6}>
<Col xs={24} sm={12} md={8} lg={6}>
<span className="default-color">
{t("pages.account.votingWeight")}
</span>
</Col>
<Col xs={24} sm={12} md={16} lg={18}>
{t("common.account")}
</Col>
</Row>
</>
) : null}
{isDelegatorsLoading
? Array.from(Array(3).keys()).map(index => (
<Row gutter={6} key={index}>
<Col xs={24} sm={12} md={8} lg={6}>
<Skeleton loading={true} paragraph={false} active />
</Col>
<Col xs={24} sm={12} md={16} lg={18}>
<Skeleton loading={true} paragraph={false} active />
</Col>
</Row>
))
: null}
{!isDelegatorsLoading && Object.keys(delegators).length ? (
<>
{Object.entries(delegators || []).map(([account, weight]) => {
const alias = knownAccounts.find(
({ account: knownAccount }) => account === knownAccount,
)?.alias;
return (
<Row gutter={6} key={account}>
<Col xs={24} sm={12} md={8} lg={6}>
<span
style={{
display: "block",
}}
>
Ӿ {new BigNumber(weight).toFormat()}
</span>
</Col>
<Col xs={24} sm={12} md={16} lg={18}>
{alias ? (
<div className="color-important">{alias}</div>
) : null}
<Link to={`/account/${account}`} className="break-word">
{account}
</Link>
</Col>
</Row>
);
})}
{total > perPage ? (
<Row className="row-pagination">
<Col xs={24} style={{ textAlign: "right" }}>
<Pagination
size="small"
{...{
total,
pageSize: perPage,
current: currentPage,
disabled: false,
onChange: (page: number) => {
const element = document.getElementById(
"delegator-title",
);
element?.scrollIntoView();
setCurrentPage(page);
},
showSizeChanger: false,
}}
/>
</Col>
</Row>
) : null}
</>
) : null}
{!isDelegatorsLoading && !Object.keys(delegators).length ? (
<Row>
<Col xs={24} style={{ textAlign: "center" }}>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description={t("pages.representative.noDelegatorsFound")}
style={{ padding: "12px" }}
/>
</Col>
</Row>
) : null}
</Card>
</>
);
}
Example #17
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ReportRecords = () => {
const [reportTaskRecords, reportTaskRecord, reportTaskRecordPaging] = alarmReportStore.useStore((s) => [
s.reportTaskRecords,
s.reportTaskRecord,
s.reportTaskRecordPaging,
]);
const { getReportTaskRecords, getReportTaskRecord, getReportTask } = alarmReportStore.effects;
const { clearReportTaskRecords, clearReportTaskRecord } = alarmReportStore.reducers;
const [getReportTaskRecordsLoading, getReportTaskRecordLoading] = useLoading(alarmReportStore, [
'getReportTaskRecords',
'getReportTaskRecord',
]);
const { pageNo, pageSize, total } = reportTaskRecordPaging;
const { query } = routeInfoStore.getState((s) => s);
const [{ activedRecord }, updater] = useUpdate({
activedRecord: query.recordId,
});
const layout = useMemo(
() =>
map(get(reportTaskRecord, 'dashboardBlock.viewConfig'), (item: any) => {
const _item = { ...item };
const chartType = get(_item, 'view.chartType');
const staticData = get(_item, 'view.staticData');
if (isEmpty(staticData)) {
set(_item, 'view.staticData', {
time: [],
metricData: [],
});
return _item;
}
if (['chart:line', 'chart:bar', 'chart:area'].includes(chartType)) {
const { time, results } = staticData;
if (results[0].data?.length > 1) {
set(_item, 'view.staticData', {
time,
metricData: map(results[0].data, (itemData) => values(itemData)[0]),
});
} else {
set(_item, 'view.staticData', {
time,
metricData: results[0].data?.[0],
});
}
}
if (chartType === 'chart:pie') {
set(_item, 'view.staticData', {
metricData: [
{
name: staticData.title || '',
data: map(staticData.metricData, ({ title, value }) => ({ name: title, value })),
},
],
});
set(_item, 'view.config.option.series', [
{
radius: ['30%', '50%'],
},
]);
}
return _item;
}),
[reportTaskRecord],
);
useMount(() => {
getReportTask();
getReportTaskRecords({ pageNo, pageSize });
});
useUnmount(() => {
clearReportTaskRecords();
clearReportTaskRecord();
});
useEffect(() => {
if (reportTaskRecords[0] && !activedRecord) {
updater.activedRecord(reportTaskRecords[0].id);
}
}, [activedRecord, reportTaskRecords, updater]);
useEffect(() => {
if (activedRecord) {
getReportTaskRecord(activedRecord);
} else {
clearReportTaskRecord();
}
}, [activedRecord, clearReportTaskRecord, getReportTaskRecord]);
const handleChange = (no: number, dates?: Array<undefined | Moment>) => {
let payload = { pageNo: no, pageSize } as any;
if (dates) {
payload = {
...payload,
start: dates[0] && dates[0].valueOf(),
end: dates[1] && dates[1].valueOf(),
};
}
clearReportTaskRecords();
updater.activedRecord(undefined);
getReportTaskRecords(payload);
};
const handleClick = (id: number) => {
updater.activedRecord(id);
};
return (
<div className="task-report-records flex items-start justify-between">
<div className="search-records pr-4 flex flex-col h-full">
<div className="mb-2">
<DatePicker.RangePicker
borderTime
className="w-full"
onChange={(dates) => handleChange(pageNo, dates)}
ranges={getTimeRanges()}
/>
</div>
<div className="flex-1 h-full overflow-auto">
<Spin spinning={getReportTaskRecordsLoading}>
<Holder when={isEmpty(reportTaskRecords)}>
<ul>
{map(reportTaskRecords, ({ id, start, end }) => (
<li
className={classnames({
'text-base': true,
'py-4': true,
'font-medium': true,
'text-center': true,
'hover-active-bg': true,
active: String(activedRecord) === String(id),
})}
key={id}
onClick={() => handleClick(id)}
>
<CustomIcon className="mr-2" type="rw" />
{end
? `${moment(start).format('YYYY/MM/DD')}-${moment(end).format('YYYY/MM/DD')}`
: moment(start).format('YYYY-MM-DD')}
</li>
))}
</ul>
{total && (
<Pagination
className="text-center mt-3"
simple
defaultCurrent={1}
total={total}
onChange={(no) => handleChange(no)}
/>
)}
</Holder>
</Spin>
</div>
</div>
<div className="flex-1 pl-4 overflow-auto h-full">
<Spin spinning={getReportTaskRecordLoading}>
<Holder when={isEmpty(layout)}>
<BoardGrid.Pure layout={layout} />
</Holder>
</Spin>
</div>
</div>
);
}
Example #18
Source File: use-hooks.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
/**
* Filter功能的包装hook, 可以实现自动管理分页跳转、查询条件贮存url,当页面中仅有一个Filter时使用此hook
* @param getData 必传 用于取列表数据的effect
* @param fieldConvertor 选传 用于对特殊自定义类型的域的值进行转换
* @param pageSize 选传 不传默认为10
* @param extraQuery 选传 查询时需要加入但不存在Filter组件中的参数 注意!!! extraQuery不能直接依赖于路由参数本身,如果必须依赖那就建立一个本地state来替代。否则在前进后退中会额外触发路由变化引起崩溃。另外extraQuery不要和searchQuery混在一起,更新searchQuery用onSubmit, 更新extraQuery直接更新对象, extraQuery的优先级大于searchQuery
* @param excludeQuery 选传 在映射url时,将查询条件以外的query保留
* @param fullRange 选传 date类型是否无视时间(仅日期) 时间从前一天的0点到后一天的23:59:59
* @param dateFormat 选传 日期类型域的toString格式
* @param requiredKeys 选传 当requiredKeys中的任何参数为空时,终止search行为,当下一次参数有值了才查询。用于页面初始化时某key参数需要异步拿到,不能直接查询的场景
* @param loadMore 选传 当列表是使用loadMore组件加载时,不在url更新pageNo, LoadMore组件需要关闭initialLoad
* @param debounceGap 选传 当需要在输入时搜索,加入debounce的时间间隔,不传默认为0
* @param localMode 选传 为true时,与url切断联系,状态只保存在state中
* @param lazy 选传 为true时,首次query必须由onSubmit触发
* @param initQuery 选传 当初次加载页面时filter组件的默认值,如果url上的query不为空,则此值无效
* @return {queryCondition, onSubmit, onReset, onPageChange, pageNo, fetchDataWithQuery }
*/
export function useFilter<T>(props: ISingleFilterProps<T>): IUseFilterProps<T> {
const {
getData,
excludeQuery = [],
fieldConvertor,
extraQuery = {},
fullRange,
dateFormat,
requiredKeys = [],
loadMore = false,
debounceGap = 0,
localMode = false,
lazy = false,
initQuery = {},
} = props;
const [query, currentRoute] = routeInfoStore.useStore((s) => [s.query, s.currentRoute]);
const { pageNo: pNo, ...restQuery } = query;
const [state, update, updater] = useUpdate({
searchQuery: localMode
? {}
: isEmpty(restQuery)
? initQuery
: !isEmpty(excludeQuery)
? omit(restQuery, excludeQuery)
: { ...initQuery, ...restQuery },
pageNo: Number(localMode ? 1 : pNo || 1),
loaded: false,
pageSize: PAGINATION.pageSize,
currentPath: currentRoute.path,
});
const { searchQuery, pageNo, pageSize, loaded, currentPath } = state;
const fetchData = React.useCallback(
debounce((q: any) => {
const { [FilterBarHandle.filterDataKey]: _Q_, ...restQ } = q;
getData({ ...restQ });
}, debounceGap),
[getData],
);
const updateSearchQuery = React.useCallback(() => {
// 这里异步处理一把是为了不出现dva报错。dva移除后可以考虑放开
setTimeout(() => {
setSearch({ ...searchQuery, ...extraQuery, pageNo: loadMore ? undefined : pageNo }, excludeQuery, true);
});
}, [excludeQuery, extraQuery, loadMore, pageNo, searchQuery]);
useDeepCompareEffect(() => {
const payload = { pageSize, ...searchQuery, ...extraQuery, pageNo };
const unableToSearch = some(requiredKeys, (key) => payload[key] === '' || payload[key] === undefined);
if (unableToSearch || (lazy && !loaded)) {
return;
}
pageNo && fetchData(payload);
updateSearchQuery();
}, [pageNo, pageSize, searchQuery, extraQuery]);
// useUpdateEffect 是当mounted之后才会触发的effect
// 这里的目的是当用户点击当前页面的菜单路由时,url的query会被清空。此时需要reset整个filter
useUpdateEffect(() => {
// 当点击菜单时,href会把query覆盖,此时判断query是否为空并且路径没有改变的情况下重新初始化query
if (isEmpty(query) && currentPath === currentRoute.path) {
onReset();
updateSearchQuery();
}
}, [query, currentPath, currentRoute]);
const fetchDataWithQuery = (pageNum?: number) => {
if (pageNum && pageNum !== pageNo && !loadMore) {
update.pageNo(pageNum);
} else {
const { [FilterBarHandle.filterDataKey]: _Q_, ...restSearchQuery } = searchQuery;
return getData({
pageSize,
...extraQuery,
...restSearchQuery,
pageNo: pageNum || pageNo,
});
}
};
const onSubmit = (condition: { [prop: string]: any }) => {
const formatCondition = convertFilterParamsToUrlFormat(fullRange, dateFormat)(condition, fieldConvertor);
if (isEqual(formatCondition, searchQuery)) {
// 如果查询条件没有变化,重复点击查询,还是要强制刷新
fetchDataWithQuery(1);
} else {
update.searchQuery({ ...formatCondition, pageNo: 1 }); // 参数变化时始终重置pageNo
update.pageNo(1);
}
update.loaded(true);
};
const onReset = () => {
if (isEmpty(searchQuery)) {
fetchDataWithQuery(1);
} else {
// reset之后理论上值要变回最开始?
update.searchQuery(
localMode
? {}
: isEmpty(restQuery)
? initQuery
: !isEmpty(excludeQuery)
? omit(restQuery, excludeQuery)
: restQuery,
);
update.pageNo(1);
}
};
const onPageChange = (currentPageNo: number, currentPageSize?: number) => {
updater({
pageNo: currentPageNo,
pageSize: currentPageSize,
});
};
const onTableChange = (pagination: any, _filters: any, sorter: Obj<any>) => {
if (!isEmpty(sorter)) {
const { field, order } = sorter;
if (order) {
update.searchQuery({ ...searchQuery, orderBy: field, asc: order === 'ascend' });
} else {
update.searchQuery({ ...searchQuery, orderBy: undefined, asc: undefined });
}
}
if (!isEmpty(pagination)) {
const { pageSize: pSize, current } = pagination;
update.searchQuery({ ...searchQuery, pageSize: pSize, pageNo: current });
update.pageNo(current || 1);
}
};
const sizeChangePagination = (paging: IPaging) => {
const { pageSize: pSize, total } = paging;
let sizeOptions = PAGINATION.pageSizeOptions;
if (!sizeOptions.includes(`${pageSize}`)) {
// 备选项中不存在默认的页数
sizeOptions.push(`${pageSize}`);
}
sizeOptions = sortBy(sizeOptions, (item) => +item);
return (
<div className="mt-4 flex items-center flex-wrap justify-end">
<Pagination
current={pageNo}
pageSize={+pSize}
total={total}
onChange={onPageChange}
showSizeChanger
pageSizeOptions={sizeOptions}
/>
</div>
);
};
return {
queryCondition: searchQuery,
onSubmit, // 包装原始onSubmit, 当搜索时自动更新url
onReset, // 包装原始onReset, 当重置时自动更新url
onPageChange, // 当Table切换页码时记录PageNo并发起请求
pageNo, // 返回当前pageNo,与paging的PageNo理论上相同
fetchDataWithQuery, // 当页面表格发生操作(删除,启动,编辑)后,进行刷新页面,如不指定pageNum则使用当前页码
autoPagination: (paging: IPaging) => ({
total: paging.total,
current: paging.pageNo,
pageSize: paging.pageSize,
// hideOnSinglePage: true,
onChange: (currentPageNo: number, currentPageSize: number) => onPageChange(currentPageNo, currentPageSize),
}),
onTableChange, // Filter/Sort/Paging变化
sizeChangePagination,
};
}
Example #19
Source File: TemplateList.tsx From yugong with MIT License | 4 votes |
TemplateList: React.FC<Props> = ({ onSelectedTemplate }) => {
const { auth } = useSelector((state: RootState) => state.controller);
const history = useHistory();
const [templateList, setTemplateList] = useState<queryTemplateParams[]>([]);
const [templateParams, setTemplateParams] = useState<queryTemplateParams>({
isPublic: 1,
limit: 8,
offset: 0,
});
// 总条数决定页数
const [total, setTotal] = useState<number>();
const runningTimes = useSelector((state: RootState) => state.runningTimes);
// 当前页
const [current, setCurrent] = useState(1)
const [visibleQrcode, setVisibleQrcode] = useState(0);
const [tags, setTags] = useState<queryTagParams[]>([]);
const getTags = useCallback(async () => {
try {
loading.show();
const tagsResult = await queryTag();
setTags(tagsResult);
loading.hide();
} catch (error) {
loading.hide();
console.error(error);
}
}, []);
useEffect(() => {
getTags();
}, [getTags]);
const renderTags = useCallback(
(tag: string) => {
const tagTsx = tag
.split(",")
.filter((item) => Number(item))
.map((el, ind) => (
<React.Fragment key={ind}>
{tags.map((one, index) =>
Number(el) === one.id ? <Tag key={index}>{one.name}</Tag> : null
)}
</React.Fragment>
));
return tagTsx;
},
[tags]
);
/**
* 获取列表
* @param type
*/
const getTemplateList = useCallback(
async (query?: queryTemplateParams, force?: boolean) => {
let params = {
...templateParams,
...query,
};
params = clearEmptyOfObject(params)
if (force) {
params = { ...query }
}
if (params.isPublic === 0) {
params.userId = auth?.session?.id
}
try {
loading.show();
const { rows = [], limit, offset, count } = await queryTemplate(params);
setTemplateList(rows);
setTotal(Math.ceil(count / limit) * limit);
setCurrent(offset / limit + 1);
loading.hide();
} catch (error) {
loading.hide();
console.error(error);
}
},
[auth?.session?.id, templateParams]
);
useEffect(() => {
getTemplateList({ isPublic: 1 });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const del = useCallback(
(id) => {
loading.show();
deleteTemplate(id).then(() => {
loading.hide();
getTemplateList();
}).catch(error => {
console.error(error)
loading.hide();
});
},
[getTemplateList]
);
const onChangeTab = useCallback(
(key) => {
// 拦截到登录
if (key === "0" && !auth?.isLogin) {
history.push("/login");
return;
}
setTemplateParams({
limit: templateParams.limit,
offset: 0,
isPublic: Number(key) as 1 | 0,
});
getTemplateList({
limit: templateParams.limit,
offset: 0,
isPublic: Number(key) as 1 | 0,
}, true)
},
[auth?.isLogin, getTemplateList, history, templateParams]
);
const onSearch = useCallback(
(data) => {
const optData = { ...templateParams, ...data };
if (!data.tag) {
delete optData.tag;
}
if (!data.title) {
delete optData.title;
}
if (!data.terminal) {
delete optData.terminal;
}
setTemplateParams(optData)
getTemplateList(optData, true);
},
[getTemplateList, templateParams]
);
const onDelete = useCallback(
(id) => () => {
confirm({
content: <div>确定要删除当前模板?</div>,
okText: "确定",
cancelText: "取消",
onCancel: () => { },
onOk: () => del(id),
});
},
[del]
);
const onChangePagination = useCallback(
(page) => {
const currentOffset = (page - 1) * (templateParams.limit || 0);
getTemplateList({
offset: currentOffset
});
},
[getTemplateList, templateParams.limit]
);
const handleShowQrCode = useCallback(
(item) => {
setVisibleQrcode(item.id);
},
[],
)
const pageSearch = stringify({ tpl: visibleQrcode, ...runningTimes.search });
const codeViewUrl = `${process.env.REACT_APP_SITE_PATH || ''}${pageSearch ? `?${pageSearch}` : ''}`;
return (
<>
<Tabs className={s.tab} defaultActiveKey="1" onChange={onChangeTab}>
<TabPane tab="公共模板" key="1"></TabPane>
<TabPane tab="我的项目" key="0"></TabPane>
</Tabs>
<Searchbar key={templateParams.isPublic} onClick={onSearch} tags={tags} />
<div className={s.container}>
{templateList.map((item: any, index) => (
<Card
hoverable
className={s.card}
bodyStyle={{ padding: "10px" }}
key={`${item.id}${index}`}
onDoubleClick={() => onSelectedTemplate(item.id, "create")}
cover={
<div className={classNames(s.projectcove, s.projectcovetpl)}>
{item.cove ? (
<img src={item.cove} alt={item.title} />
) : (
<EmptyIcon />
)}
</div>
}
>
<Meta
title={
<>
<h4 className={s.tpltitle}>{item.title}</h4>
<div className={s.tpldescript}>{item.describe}</div>
</>
}
description={
<>
<div className={s.tag}>{renderTags(item.tag)}</div>
<div className={s.buttonbar}>
<Button
size="small"
type="primary"
onClick={() => onSelectedTemplate(item.id, "create")}
>
从模板创建
</Button>
{auth?.session?.id === item.userId ? (
<>
<Button
size="small"
icon={<EditOutlined />}
onClick={() => onSelectedTemplate(item.id, "edit")}
/>
<Button
size="small"
icon={<DeleteOutlined />}
onClick={onDelete(item.id)}
/>
</>
) : null}
<Button
size="small"
icon={<QrcodeOutlined />}
onClick={() => handleShowQrCode(item)}
/>
</div>
</>
}
/>
</Card>
))}
</div>
{!!total && (
<Pagination
current={current}
pageSize={templateParams?.limit || 0}
onChange={onChangePagination}
total={total}
/>
)}
<QrcodeModal
visible={!!visibleQrcode}
onCancel={() => setVisibleQrcode(0)}
sourceData={codeViewUrl}
title="请扫码访问"
info={<div className={s.viewurl}>访问地址:<a href={codeViewUrl} target={'_blank'} rel="noreferrer">{codeViewUrl}</a></div>}
options={{
width: 122,
margin: 1
}}
/>
</>
);
}
Example #20
Source File: ProductsList.tsx From Shopping-Cart with MIT License | 4 votes |
ProductsList = () => {
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState<number>(1);
const [cartItems, setCartItems] = useState<ProductModel['id'][]>([]);
const { isLoading, items, totalProducts } = useSelector(
(state: RootState) => state.productReducer,
);
const itemList = items && Object.entries(items).map(item => item[1]);
useEffect(() => {
dispatch(fetchProductListAsync.request({ currentPage }));
}, [dispatch, currentPage]);
useEffect(() => {
if (storageService.getItem('cart-class101')) {
setCartItems(
JSON.parse(storageService.getItem('cart-class101') as string),
);
}
}, [setCartItems, storageService.setItem]);
// 제품 카트 클릭 이벤트 핸들러
const handleProductCardClick = useCallback(
(id: ProductModel['id']) => {
if (cartItems.includes(id)) {
storageService.setItem(
'cart-class101',
JSON.stringify([...cartItems.filter(value => value !== id)]),
);
setCartItems([...cartItems.filter(value => value !== id)]);
} else if (cartItems.length >= 3) {
InfoModal('warning', '주의', '장바구니에는 3개 이상 담을 수 없습니다.');
} else {
cartItems.push(id);
storageService.setItem('cart-class101', JSON.stringify([...cartItems]));
}
},
[cartItems, setCartItems, storageService.setItem, storageService.getItem],
);
// 페이지네이션 onChange
const handlePaginationOnChange = useCallback(
(page: number, pageNumber?: number) => {
setCurrentPage(page);
},
[setCurrentPage],
);
if (isLoading) return <LoadingSpin />;
return (
<>
<Row>
<Col span={24}>
<PageTitle title="상품 목록" />
</Col>
</Row>
<Row>
{itemList ? (
itemList.map(product => (
<Col xs={24} sm={12} lg={6} key={product.id}>
{' '}
<ProductCard
onClick={handleProductCardClick}
product={product}
/>{' '}
</Col>
))
) : (
<Empty />
)}
</Row>
<Row style={{ marginTop: '15px' }}>
<Col span={24} style={{ textAlign: 'right' }}>
<Pagination
defaultCurrent={1}
defaultPageSize={4}
total={totalProducts}
onChange={handlePaginationOnChange}
/>
</Col>
</Row>
</>
);
}