dark
6 months ago
19 changed files with 815 additions and 569 deletions
-
95src/components/table/Table.tsx
-
1src/components/table/index.ts
-
23src/components/table/style.ts
-
4src/hooks/useScrollStyle.ts
-
3src/layout/ListPageLayout.tsx
-
53src/layout/TwoColPageLayout.tsx
-
36src/layout/style.ts
-
12src/pages/cms/collect/index.tsx
-
12src/pages/cms/video/index.tsx
-
12src/pages/cms/video_cloud/index.tsx
-
12src/pages/cms/video_magnet/index.tsx
-
258src/pages/system/logs/login/index.tsx
-
364src/pages/system/menus/index.tsx
-
15src/pages/system/menus/style.ts
-
7src/pages/system/roles/index.tsx
-
445src/pages/system/users/index.tsx
-
9src/pages/videos/list/index.tsx
-
11src/pages/websites/ssl/index.tsx
-
12src/utils/dom.ts
@ -0,0 +1,95 @@ |
|||
import { ProTable, ProTableProps, ProCard } from '@ant-design/pro-components' |
|||
import React, { useEffect, useRef, useState } from 'react' |
|||
import { useStyle } from './style' |
|||
|
|||
export interface TableProps<T = any, D = any> extends ProTableProps<T, D> { |
|||
|
|||
} |
|||
|
|||
export const Table = <T extends Record<string, any> = any, D = any>(props: TableProps<T, D>) => { |
|||
|
|||
const { styles } = useStyle() |
|||
const toolbarRef = useRef<HTMLDivElement | undefined | null>(undefined) |
|||
const alterRef = useRef<HTMLDivElement | undefined | null>(undefined) |
|||
const [ toolbarHeight, setHeight ] = useState<number>(65) |
|||
const [ alterHeight, setAlterHeight ] = useState<number>(0) |
|||
|
|||
const scroll = props.scroll ? { |
|||
...props.scroll, |
|||
y: props.scroll.y ?? ` calc(100vh - ${toolbarHeight + 200}px)` |
|||
} : undefined |
|||
|
|||
useEffect(() => { |
|||
|
|||
if (!toolbarRef.current) return |
|||
|
|||
setHeight(toolbarRef.current?.offsetHeight ?? 65) |
|||
|
|||
//监听toolbarRef offsetHeight
|
|||
const observer = new ResizeObserver(entries => { |
|||
for (const entry of entries) { |
|||
if (entry.target === toolbarRef.current) { |
|||
setHeight(entry.contentRect.height) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
observer.observe(toolbarRef.current!) |
|||
|
|||
return () => { |
|||
observer.disconnect() |
|||
} |
|||
|
|||
}, [ toolbarRef.current ]) |
|||
|
|||
useEffect(() => { |
|||
|
|||
if (!alterRef.current) return |
|||
|
|||
setHeight(alterRef.current?.offsetHeight ?? 65) |
|||
|
|||
//监听toolbarRef offsetHeight
|
|||
const observer = new ResizeObserver(entries => { |
|||
for (const entry of entries) { |
|||
if (entry.target === alterRef.current && entry.contentRect.height > 0) { |
|||
setAlterHeight(entry.contentRect.height + 16) |
|||
} |
|||
} |
|||
}) |
|||
|
|||
observer.observe(alterRef.current!) |
|||
|
|||
return () => { |
|||
observer.disconnect() |
|||
} |
|||
|
|||
}, [ alterRef.current ]) |
|||
|
|||
const style = { |
|||
'--toolbar-height': `${toolbarHeight}px`, |
|||
'--alter-height': `${alterHeight}px`, |
|||
} as React.CSSProperties |
|||
|
|||
// @ts-ignore fix dataItem
|
|||
return <ProTable<T> |
|||
{...props} |
|||
className={styles.container} |
|||
style={style} |
|||
tableRender={(props, _dom, domList) => { |
|||
return <ProCard |
|||
ghost={props.ghost} |
|||
{...props.cardProps} |
|||
bodyStyle={{ |
|||
paddingBlockStart: 0, |
|||
}} |
|||
> |
|||
<div ref={toolbarRef as any}>{domList.toolbar}</div> |
|||
<div ref={alterRef as any}>{domList.alert}</div> |
|||
<>{domList.table}</> |
|||
</ProCard> |
|||
}} |
|||
scroll={scroll} |
|||
></ProTable> |
|||
} |
|||
|
|||
export default Table |
@ -0,0 +1 @@ |
|||
export * from './Table.tsx' |
@ -0,0 +1,23 @@ |
|||
import { createStyles } from '@/theme' |
|||
|
|||
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { |
|||
const prefix = `${prefixCls}-${token?.proPrefix}-my-table` |
|||
|
|||
const container = css`
|
|||
|
|||
--toolbar-height: 65px; |
|||
--alter-height: 0px; |
|||
--padding: 37px; |
|||
--table-body-height: calc(var(--toolbar-height, 65px) + var(--alter-height, 0px) + var(--header-height, 56px) + var(--padding, 20px) * 4); |
|||
|
|||
.ant-table-body { |
|||
overflow: auto scroll; |
|||
max-height: calc(100vh - var(--table-body-height)) !important; |
|||
height: calc(100vh - var(--table-body-height)) !important; |
|||
} |
|||
`
|
|||
|
|||
return { |
|||
container: cx(prefix, props?.className, container), |
|||
} |
|||
}) |
@ -1,144 +1,156 @@ |
|||
import Switch from '@/components/switch' |
|||
import { |
|||
ActionType, |
|||
PageContainer, |
|||
ProColumns, |
|||
ProTable, |
|||
ActionType, |
|||
ProColumns, |
|||
} from '@ant-design/pro-components' |
|||
import { useStyle } from './style.ts' |
|||
import { memo, useMemo, useRef, useState } from 'react' |
|||
import { useAtom, useAtomValue } from 'jotai' |
|||
import { Table as ProTable } from '@/components/table' |
|||
|
|||
import { useTranslation } from '@/i18n.ts' |
|||
import { Button, Space, Table, Popconfirm } from 'antd' |
|||
import { deleteLoginLogAtom, loginLogPageAtom, loginLogsAtom, loginLogSearchAtom } from '@/store/system/logs.ts' |
|||
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
|||
|
|||
const LoginLog = memo(() => { |
|||
|
|||
const { t } = useTranslation() |
|||
const { styles } = useStyle() |
|||
const actionRef = useRef<ActionType>() |
|||
const [ page, setPage ] = useAtom(loginLogPageAtom) |
|||
const [ search, setSearch ] = useAtom(loginLogSearchAtom) |
|||
const { data, isLoading, isFetching, refetch } = useAtomValue(loginLogsAtom) |
|||
const { mutate: deleteLog, isPending: isDeleting } = useAtomValue(deleteLoginLogAtom) |
|||
const [ ids, setIds ] = useState<number[]>([]) |
|||
const { t } = useTranslation() |
|||
const { styles } = useStyle() |
|||
const actionRef = useRef<ActionType>() |
|||
const [ page, setPage ] = useAtom(loginLogPageAtom) |
|||
const [ search, setSearch ] = useAtom(loginLogSearchAtom) |
|||
const { data, isLoading, isFetching, refetch } = useAtomValue(loginLogsAtom) |
|||
const { mutate: deleteLog, isPending: isDeleting } = useAtomValue(deleteLoginLogAtom) |
|||
const [ ids, setIds ] = useState<number[]>([]) |
|||
|
|||
const columns = useMemo(() => { |
|||
const columns = useMemo(() => { |
|||
|
|||
return [ |
|||
{ |
|||
title: 'id', dataIndex: 'id', |
|||
hideInTable: true, |
|||
hideInSearch: true, |
|||
formItemProps: { |
|||
hidden: true |
|||
} |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.username', '登录帐号'), dataIndex: 'username', valueType: 'text', |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.ip', '登录IP'), dataIndex: 'ip', valueType: 'text', |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.user_agent', '浏览器'), dataIndex: 'user_agent', valueType: 'text', |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.status', '状态'), dataIndex: 'status', valueType: 'switch', |
|||
render: (_, record) => { |
|||
return <Switch value={record.status} size={'small'}/> |
|||
}, |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.created_at', '登录时间'), |
|||
dataIndex: 'created_at', |
|||
valueType: 'dateTime', |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.option','操作'), valueType: 'option', |
|||
key: 'option', |
|||
render: (_, record) => [ |
|||
<Popconfirm |
|||
key={'del_confirm'} |
|||
onConfirm={() => { |
|||
deleteLog([ record.id ]) |
|||
}} |
|||
title={t('message.deleteConfirm')}> |
|||
<a key="del"> |
|||
{t('actions.delete', '删除')} |
|||
</a> |
|||
</Popconfirm> |
|||
, |
|||
], |
|||
}, |
|||
] as ProColumns[] |
|||
}, []) |
|||
return [ |
|||
{ |
|||
title: 'id', dataIndex: 'id', |
|||
hideInTable: true, |
|||
hideInSearch: true, |
|||
formItemProps: { |
|||
hidden: true |
|||
} |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.username', '登录帐号'), dataIndex: 'username', valueType: 'text', |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.ip', '登录IP'), dataIndex: 'ip', valueType: 'text', |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.user_agent', '浏览器'), dataIndex: 'user_agent', valueType: 'text', |
|||
width: 500, |
|||
ellipsis: true, |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.status', '状态'), dataIndex: 'status', valueType: 'switch', |
|||
width: 80, |
|||
render: (_, record) => { |
|||
return <Switch value={record.status} size={'small'}/> |
|||
}, |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.created_at', '登录时间'), |
|||
dataIndex: 'created_at', |
|||
valueType: 'dateTime', |
|||
width: 180, |
|||
}, |
|||
{ |
|||
title: t('system.logs.login.columns.option', '操作'), valueType: 'option', |
|||
key: 'option', |
|||
render: (_, record) => [ |
|||
<Popconfirm |
|||
key={'del_confirm'} |
|||
onConfirm={() => { |
|||
deleteLog([ record.id ]) |
|||
}} |
|||
title={t('message.deleteConfirm')}> |
|||
<a key="del"> |
|||
{t('actions.delete', '删除')} |
|||
</a> |
|||
</Popconfirm> |
|||
, |
|||
], |
|||
}, |
|||
] as ProColumns[] |
|||
}, []) |
|||
|
|||
return ( |
|||
<PageContainer breadcrumbRender={false} title={false} className={styles.container}> |
|||
<div className={styles.authHeight}> |
|||
<ProTable |
|||
rowKey={'id'} |
|||
actionRef={actionRef} |
|||
headerTitle={t('system.logs.login.title', '登录日志')} |
|||
columns={columns} |
|||
loading={isLoading || isFetching} |
|||
dataSource={data?.rows} |
|||
search={false} |
|||
rowSelection={{ |
|||
onChange: (selectedRowKeys) => { |
|||
setIds(selectedRowKeys as number[]) |
|||
}, |
|||
selectedRowKeys: ids, |
|||
selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT ], |
|||
}} |
|||
tableAlertOptionRender={() => { |
|||
return ( |
|||
<Space size={16}> |
|||
<Popconfirm |
|||
onConfirm={() => { |
|||
deleteLog(ids) |
|||
}} |
|||
title={t('message.batchDelete')}> |
|||
<Button type={'link'} |
|||
loading={isDeleting}>{t('actions.batchDel')}</Button> |
|||
</Popconfirm> |
|||
</Space> |
|||
) |
|||
}} |
|||
options={{ |
|||
reload: () => { |
|||
refetch() |
|||
}, |
|||
}} |
|||
toolbar={{ |
|||
search: { |
|||
loading: isFetching && !!search.key, |
|||
onSearch: (value: string) => { |
|||
setSearch({ key: value }) |
|||
}, |
|||
placeholder: t('system.logs.login.search.placeholder','请输入用户名查询') |
|||
}, |
|||
actions: [] |
|||
}} |
|||
pagination={{ |
|||
total: data?.total, |
|||
current: page.page, |
|||
pageSize: page.pageSize, |
|||
onChange: (page) => { |
|||
return ( |
|||
<ListPageLayout className={styles.container}> |
|||
<> |
|||
<ProTable |
|||
rowKey={'id'} |
|||
actionRef={actionRef} |
|||
headerTitle={t('system.logs.login.title', '登录日志')} |
|||
columns={columns} |
|||
loading={isLoading || isFetching} |
|||
dataSource={data?.rows} |
|||
search={false} |
|||
rowSelection={{ |
|||
onChange: (selectedRowKeys) => { |
|||
setIds(selectedRowKeys as number[]) |
|||
}, |
|||
selectedRowKeys: ids, |
|||
selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT ], |
|||
}} |
|||
scroll={{ |
|||
|
|||
setPage((prev) => { |
|||
return { ...prev, page } |
|||
}) |
|||
} |
|||
}} |
|||
/> |
|||
</div> |
|||
}} |
|||
tableAlertOptionRender={() => { |
|||
return ( |
|||
<Space size={16}> |
|||
<Popconfirm |
|||
onConfirm={() => { |
|||
deleteLog(ids) |
|||
}} |
|||
title={t('message.batchDelete')}> |
|||
<Button type={'link'} |
|||
loading={isDeleting}>{t('actions.batchDel')}</Button> |
|||
</Popconfirm> |
|||
</Space> |
|||
) |
|||
}} |
|||
options={{ |
|||
reload: () => { |
|||
refetch() |
|||
}, |
|||
}} |
|||
toolbar={{ |
|||
search: { |
|||
loading: isFetching && !!search.key, |
|||
onSearch: (value: string) => { |
|||
setSearch({ key: value }) |
|||
}, |
|||
placeholder: t('system.logs.login.search.placeholder', '请输入用户名查询') |
|||
}, |
|||
actions: [] |
|||
}} |
|||
pagination={{ |
|||
total: data?.total, |
|||
current: page.page, |
|||
pageSize: page.pageSize, |
|||
onShowSizeChange: (current: number, size: number) => { |
|||
setPage({ |
|||
...page, |
|||
pageSize: size, |
|||
page: current |
|||
}) |
|||
}, |
|||
onChange: (page) => { |
|||
|
|||
|
|||
</PageContainer> |
|||
) |
|||
setPage((prev) => { |
|||
return { ...prev, page } |
|||
}) |
|||
} |
|||
}} |
|||
/> |
|||
</> |
|||
</ListPageLayout> |
|||
) |
|||
}) |
|||
|
|||
export default LoginLog |
@ -0,0 +1,12 @@ |
|||
export function getElementRealHeight(el: Element) { |
|||
const style = window.getComputedStyle(el) |
|||
|
|||
const paddingTop = parseInt(style.paddingTop) |
|||
const paddingBottom = parseInt(style.paddingBottom) |
|||
const marginTop = parseInt(style.marginTop) |
|||
const marginBottom = parseInt(style.marginBottom) |
|||
|
|||
const clientHeight = el.clientHeight |
|||
|
|||
return clientHeight + paddingTop + paddingBottom + marginTop + marginBottom |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue