Browse Source

1、调整页面布局,增加PageTitle显示

2、优化2级页面高亮菜单
main
dark 3 months ago
parent
commit
2486225f88
  1. 11
      src/components/breadcrumb/index.tsx
  2. 21
      src/components/popconfirm/index.tsx
  3. 6
      src/components/table/style.ts
  4. 1
      src/global.d.ts
  5. 25
      src/layout/ListPageLayout.tsx
  6. 36
      src/layout/RootLayout.tsx
  7. 24
      src/layout/TwoColPageLayout.tsx
  8. 18
      src/layout/style.ts
  9. 11
      src/pages/system/logs/login/index.tsx
  10. 8
      src/pages/system/roles/index.tsx
  11. 13
      src/pages/system/users/index.tsx
  12. 105
      src/pages/websites/account/index.tsx
  13. 35
      src/pages/websites/domain/index.tsx
  14. 35
      src/pages/websites/record/index.tsx
  15. 2
      src/routes.tsx
  16. 8
      src/store/system.ts
  17. 1
      src/types/system/menus.d.ts

11
src/components/breadcrumb/index.tsx

@ -4,6 +4,8 @@ import { DownOutlined } from '@ant-design/icons'
import { getIcon } from '@/components/icon' import { getIcon } from '@/components/icon'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
import { useStyle } from './style.ts' import { useStyle } from './style.ts'
import { useSetAtom } from 'jotai'
import { currentMenuAtom } from '@/store/system.ts'
export const PageBreadcrumb = memo((props: BreadcrumbProps & { export const PageBreadcrumb = memo((props: BreadcrumbProps & {
showIcon?: boolean; showIcon?: boolean;
@ -12,6 +14,7 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
const { styles } = useStyle() const { styles } = useStyle()
const nav = useNavigate() const nav = useNavigate()
const setCurrent = useSetAtom(currentMenuAtom)
const { items = [], showIcon = true, ...other } = props const { items = [], showIcon = true, ...other } = props
const renderIcon = useCallback((icon: any) => { const renderIcon = useCallback((icon: any) => {
@ -26,7 +29,7 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
const isLast = route?.path === items[items.length - 1]?.path const isLast = route?.path === items[items.length - 1]?.path
if (route.children) { if (route.children) {
const items = route.children.map((item) => {
const items = route.children.filter(item => !(item.hidden || item.hidden_breadcrumb)).map((item) => {
return { return {
...item, ...item,
key: item.path || item.name, key: item.path || item.name,
@ -41,9 +44,12 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
onClick: (menu) => { onClick: (menu) => {
const info = props.menusFlatten.current?.find(item => item.path === menu.key) const info = props.menusFlatten.current?.find(item => item.path === menu.key)
if (info) { if (info) {
setCurrent(info)
// setOpenKeys([ info.path as string ]) // setOpenKeys([ info.path as string ])
nav({ nav({
to: info.path, to: info.path,
}).then(() => {
}) })
} }
} }
@ -75,10 +81,9 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
) )
} }
return ( return (
<div style={{ userSelect: 'none' }}> <div style={{ userSelect: 'none' }}>
<Breadcrumb {...other}
<Breadcrumb className={other.className}
items={items} items={items}
itemRender={itemRender} itemRender={itemRender}
/> />

21
src/components/popconfirm/index.tsx

@ -0,0 +1,21 @@
import { Popconfirm as AntPopconfirm, PopconfirmProps } from 'antd'
const Popconfirm = (props: PopconfirmProps) => {
const disabled = props.disabled
let attr = {}
if (typeof disabled !== 'undefined') {
attr = {
disabled: disabled
}
}
return (
<AntPopconfirm {...props}>
<a {...attr}>{props.children}</a>
</AntPopconfirm>
)
}
export default Popconfirm

6
src/components/table/style.ts

@ -34,6 +34,12 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
margin: 0; margin: 0;
align-content: center; align-content: center;
} }
.ant-table-cell{
.ant-divider-vertical{
margin-inline: 0;
}
}
` `
return { return {

1
src/global.d.ts

@ -77,6 +77,7 @@ export interface IRootContext {
interface MenuItem extends IMenu { interface MenuItem extends IMenu {
routes?: MenuItem[]; routes?: MenuItem[];
parent?: IMenu
} }
interface IAuth { interface IAuth {

25
src/layout/ListPageLayout.tsx

@ -1,10 +1,13 @@
import React from 'react' import React from 'react'
import { useStyle } from '@/layout/style.ts' import { useStyle } from '@/layout/style.ts'
import { PageContainer, PageContainerProps } from '@ant-design/pro-components' import { PageContainer, PageContainerProps } from '@ant-design/pro-components'
import { useAtomValue } from 'jotai'
import { useAtom } from 'jotai'
import { currentMenuAtom } from '@/store/system.ts' import { currentMenuAtom } from '@/store/system.ts'
import useResizeObserver from '@/hooks/useResizeObserver.ts' import useResizeObserver from '@/hooks/useResizeObserver.ts'
import useInlineStyle from '@/hooks/useInlineStyle.ts' import useInlineStyle from '@/hooks/useInlineStyle.ts'
import { Space } from 'antd'
import { ArrowLeftOutlined } from '@ant-design/icons'
import { useNavigate } from '@tanstack/react-router'
interface IListPageLayoutProps extends PageContainerProps { interface IListPageLayoutProps extends PageContainerProps {
children: React.ReactNode children: React.ReactNode
@ -15,8 +18,10 @@ const ListPageLayout: React.FC<IListPageLayoutProps> = (
{ {
className, children, authHeight = true, ...props className, children, authHeight = true, ...props
}) => { }) => {
const navigate = useNavigate()
const { styles, cx } = useStyle({ className: 'one-col' }) const { styles, cx } = useStyle({ className: 'one-col' })
const currentMenu = useAtomValue(currentMenuAtom)
const [ currentMenu, setCurrentMenu ] = useAtom(currentMenuAtom)
const [ , headerSize ] = useResizeObserver({ const [ , headerSize ] = useResizeObserver({
selector: '.ant-page-header', selector: '.ant-page-header',
}) })
@ -33,7 +38,21 @@ const ListPageLayout: React.FC<IListPageLayoutProps> = (
<PageContainer <PageContainer
ghost={false} ghost={false}
// breadcrumbRender={false} // breadcrumbRender={false}
title={ currentMenu?.title }
title={
<Space>
{
currentMenu?.parent && <span className={'black'}
onClick={() => {
navigate({
to: currentMenu?.parent?.path,
}).then(() => {
setCurrentMenu(currentMenu.parent!)
})
}}> <ArrowLeftOutlined/></span>
}
<span>{currentMenu?.title}</span>
</Space>
}
className={cx(styles.container, styles.pageCard, styles.layoutTable, className)} className={cx(styles.container, styles.pageCard, styles.layoutTable, className)}
{...props} {...props}

36
src/layout/RootLayout.tsx

@ -6,7 +6,7 @@ import { appAtom, currentMenuAtom } from '@/store/system.ts'
import { currentStaticUserAtom, userMenuDataAtom } from '@/store/system/user.ts' import { currentStaticUserAtom, userMenuDataAtom } from '@/store/system/user.ts'
import { MenuItem } from '@/global' import { MenuItem } from '@/global'
import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components' import { ProConfigProvider, ProLayout, } from '@ant-design/pro-components'
import { zhCNIntl, enUSIntl } from '@ant-design/pro-provider/es/intl'
import { enUSIntl, zhCNIntl } from '@ant-design/pro-provider/es/intl'
import { CatchBoundary, Link, Outlet, useNavigate } from '@tanstack/react-router' import { CatchBoundary, Link, Outlet, useNavigate } from '@tanstack/react-router'
import { ConfigProvider } from '@/components/config-provider' import { ConfigProvider } from '@/components/config-provider'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
@ -55,18 +55,24 @@ export default () => {
const [ pathname, setPathname ] = useState(location.pathname) const [ pathname, setPathname ] = useState(location.pathname)
const [ openMenuKeys, setOpenKeys ] = useState<string[]>([]) const [ openMenuKeys, setOpenKeys ] = useState<string[]>([])
const [ collapsed, setCollapsed ] = useState(false) const [ collapsed, setCollapsed ] = useState(false)
const [ childPath, setChildPath ] = useState<string>('')
const menusFlatten = useRef<MenuItem[]>() const menusFlatten = useRef<MenuItem[]>()
if (!menusFlatten.current) { if (!menusFlatten.current) {
menusFlatten.current = flattenTree<MenuItem>(menuData, { key: 'id', title: 'name' }).filter(item => !item.hidden)
menusFlatten.current = flattenTree<MenuItem>(menuData, { key: 'id', title: 'name' })
} }
const [ rootMenuKeys, setRootMenuKeys ] = useState<string[]>(() => { const [ rootMenuKeys, setRootMenuKeys ] = useState<string[]>(() => {
const item = menusFlatten.current?.find(item => item.path === location.pathname)
let item = menusFlatten.current?.find(item => item.path === location.pathname)
if (item && item.hidden && item.active) {
setCurrentMenu(item)
item = menusFlatten.current?.find(i => i.path === item?.active)
}
if (item?.meta.affix) { if (item?.meta.affix) {
return [ item.meta.name ] return [ item.meta.name ]
} }
return item ? item.parentName : [] return item ? item.parentName : []
}) })
const childMenuRef = useRef<MenuItem[]>([]) const childMenuRef = useRef<MenuItem[]>([])
const currentMenu = menuData.find(item => { const currentMenu = menuData.find(item => {
return item.key === rootMenuKeys?.[0] return item.key === rootMenuKeys?.[0]
@ -74,16 +80,20 @@ export default () => {
childMenuRef.current = currentMenu?.children || [] childMenuRef.current = currentMenu?.children || []
useEffect(() => { useEffect(() => {
const item = menusFlatten.current?.find(item => item.path === location.pathname) const item = menusFlatten.current?.find(item => item.path === location.pathname)
if (item && item.meta.name !== rootMenuKeys?.[0]) { if (item && item.meta.name !== rootMenuKeys?.[0]) {
setRootMenuKeys(item.parentName) setRootMenuKeys(item.parentName)
} }
}, [ location.pathname ])
useEffect(() => {
setCurrentMenu(item!)
if (item && item.hidden && item.active) {
item.parent = menusFlatten.current?.find(i => i.path === item?.active)
setChildPath(item.active)
} else {
setChildPath('')
}
}, [ location.pathname ]) }, [ location.pathname ])
return ( return (
@ -110,10 +120,12 @@ export default () => {
...{ ...{
rotate: -31, rotate: -31,
content: currentUser?.nickname, content: currentUser?.nickname,
fontColor: 'rgba(0,0,0,.15)',
fontSize: 17,
font: {
color: '#00000012',
size: 17,
},
zIndex: 1009, zIndex: 1009,
}
} as any
} style={{ width: '100vw', height: '100vh' }}> } style={{ width: '100vw', height: '100vh' }}>
<ProLayout <ProLayout
token={{ token={{
@ -216,7 +228,7 @@ export default () => {
<Menu <Menu
mode={'inline'} mode={'inline'}
inlineCollapsed={collapsed} inlineCollapsed={collapsed}
selectedKeys={[ location.pathname ]}
selectedKeys={[ childPath || location.pathname ]}
openKeys={openMenuKeys} openKeys={openMenuKeys}
onOpenChange={(keys) => { onOpenChange={(keys) => {
setOpenKeys(keys) setOpenKeys(keys)
@ -234,7 +246,7 @@ export default () => {
}} }}
items={convertToMenu((childMenuRef.current || []), (item => { items={convertToMenu((childMenuRef.current || []), (item => {
return { return {
...item,
data: item,
key: item.path || item.meta.name, key: item.path || item.meta.name,
label: item.title, label: item.title,
} }

24
src/layout/TwoColPageLayout.tsx

@ -3,10 +3,13 @@ import { PageContainer, PageContainerProps } from '@ant-design/pro-components'
import { Flexbox } from 'react-layout-kit' import { Flexbox } from 'react-layout-kit'
import { DraggablePanel, DraggablePanelProps } from '@/components/draggable-panel' import { DraggablePanel, DraggablePanelProps } from '@/components/draggable-panel'
import { useStyle } from './style' import { useStyle } from './style'
import { useAtomValue } from 'jotai/index'
import { useAtom } from 'jotai/index'
import { currentMenuAtom } from '@/store/system.ts' import { currentMenuAtom } from '@/store/system.ts'
import useResizeObserver from '@/hooks/useResizeObserver.ts' import useResizeObserver from '@/hooks/useResizeObserver.ts'
import useInlineStyle from '@/hooks/useInlineStyle.ts' import useInlineStyle from '@/hooks/useInlineStyle.ts'
import { useNavigate } from '@tanstack/react-router'
import { Space } from 'antd'
import { ArrowLeftOutlined } from '@ant-design/icons'
interface ITreePageLayoutProps { interface ITreePageLayoutProps {
children?: React.ReactNode children?: React.ReactNode
@ -18,7 +21,8 @@ interface ITreePageLayoutProps {
export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ...props }) => { export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ...props }) => {
const { styles, cx } = useStyle({ className: 'two-col' }) const { styles, cx } = useStyle({ className: 'two-col' })
const currentMenu = useAtomValue(currentMenuAtom)
const navigate = useNavigate()
const [ currentMenu, setCurrentMenu ] = useAtom(currentMenuAtom)
const [ , headerSize ] = useResizeObserver({ const [ , headerSize ] = useResizeObserver({
selector: '.ant-page-header', selector: '.ant-page-header',
}) })
@ -33,7 +37,21 @@ export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ..
return ( return (
<PageContainer <PageContainer
breadcrumbRender={false} breadcrumbRender={false}
title={currentMenu?.title}
title={
<Space>
{
currentMenu?.parent && <span className={'black'}
onClick={() => {
navigate({
to: currentMenu?.parent?.path,
}).then(() => {
setCurrentMenu(currentMenu.parent!)
})
}}> <ArrowLeftOutlined/></span>
}
<span>{currentMenu?.title}</span>
</Space>
}
className={cx(styles.container, styles.pageCard, styles.layoutTable, className)} className={cx(styles.container, styles.pageCard, styles.layoutTable, className)}
{...props.pageProps} {...props.pageProps}
> >

18
src/layout/style.ts

@ -121,6 +121,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
padding-block-end: 0; padding-block-end: 0;
max-height: 50px; max-height: 50px;
overflow: hidden; overflow: hidden;
justify-content: center;
} }
.ant-pro-layout-content, .ant-pro-layout-content,
.ant-pro-layout-container{ .ant-pro-layout-container{
@ -129,6 +130,23 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
height: 100vh; height: 100vh;
} }
.ant-pro-page-container .ant-pro-page-container-warp-page-header{
padding-inline: 30px;
.black{
cursor: pointer;
background-color: transparent;
outline: none;
transition: color 0.3s;
.anticon {
transition: all 0.2s;
&:hover {
transform: scale(1.3);
}
}
}
}
` `
const childMenus = css` const childMenus = css`

11
src/pages/system/logs/login/index.tsx

@ -9,9 +9,10 @@ import { useAtom, useAtomValue } from 'jotai'
import { Table as ProTable } from '@/components/table' import { Table as ProTable } from '@/components/table'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Space, Table, Popconfirm } from 'antd'
import { Space, Table } from 'antd'
import { deleteLoginLogAtom, loginLogPageAtom, loginLogsAtom, loginLogSearchAtom } from '@/store/system/logs.ts' import { deleteLoginLogAtom, loginLogPageAtom, loginLogsAtom, loginLogSearchAtom } from '@/store/system/logs.ts'
import ListPageLayout from '@/layout/ListPageLayout.tsx' import ListPageLayout from '@/layout/ListPageLayout.tsx'
import Popconfirm from '@/components/popconfirm'
const LoginLog = memo(() => { const LoginLog = memo(() => {
@ -97,9 +98,7 @@ const LoginLog = memo(() => {
selectedRowKeys: ids, selectedRowKeys: ids,
selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT ], selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT ],
}} }}
scroll={{
}}
scroll={{}}
tableAlertOptionRender={() => { tableAlertOptionRender={() => {
return ( return (
<Space size={16}> <Space size={16}>
@ -107,9 +106,9 @@ const LoginLog = memo(() => {
onConfirm={() => { onConfirm={() => {
deleteLog(ids) deleteLog(ids)
}} }}
disabled={isDeleting}
title={t('message.batchDelete')}> title={t('message.batchDelete')}>
<Button type={'link'}
loading={isDeleting}>{t('actions.batchDel')}</Button>
<>{t('actions.batchDel')}</>
</Popconfirm> </Popconfirm>
</Space> </Space>
) )

8
src/pages/system/roles/index.tsx

@ -20,11 +20,12 @@ import {
searchAtom searchAtom
} from '@/store/system/role.ts' } from '@/store/system/role.ts'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Form, Space, Spin, Table, Tree, Popconfirm } from 'antd'
import { Button, Divider, Form, Space, Spin, Table, Tree } from 'antd'
import { PlusOutlined } from '@ant-design/icons' import { PlusOutlined } from '@ant-design/icons'
import { menuDataAtom } from '@/store/system/menu.ts' import { menuDataAtom } from '@/store/system/menu.ts'
import { getTreeCheckedStatus } from '@/utils/tree.ts' import { getTreeCheckedStatus } from '@/utils/tree.ts'
import ListPageLayout from '@/layout/ListPageLayout.tsx' import ListPageLayout from '@/layout/ListPageLayout.tsx'
import Popconfirm from '@/components/popconfirm'
const MenuTree = (props: any) => { const MenuTree = (props: any) => {
const { data: menuList, isLoading: menuLoading } = useAtomValue(menuDataAtom) const { data: menuList, isLoading: menuLoading } = useAtomValue(menuDataAtom)
@ -122,15 +123,16 @@ const Roles = memo(() => {
> >
{t('actions.edit', '编辑')} {t('actions.edit', '编辑')}
</a>, </a>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
onConfirm={() => { onConfirm={() => {
deleteRole([ record.id ]) deleteRole([ record.id ])
}} }}
title={t('message.deleteConfirm')}> title={t('message.deleteConfirm')}>
<a key="del">
<>
{t('actions.delete', '删除')} {t('actions.delete', '删除')}
</a>
</>
</Popconfirm> </Popconfirm>
, ,
], ],

13
src/pages/system/users/index.tsx

@ -7,7 +7,7 @@ import {
ProFormColumnsType, ProFormColumnsType,
ProTable ProTable
} from '@ant-design/pro-components' } from '@ant-design/pro-components'
import { Button, Form, Popconfirm } from 'antd'
import { Button, Divider, Form } from 'antd'
import { PlusOutlined } from '@ant-design/icons' import { PlusOutlined } from '@ant-design/icons'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import DepartmentTree from '@/components/department-tree/DepartmentTree.tsx' import DepartmentTree from '@/components/department-tree/DepartmentTree.tsx'
@ -24,6 +24,7 @@ import { useMemo, useRef, useState } from 'react'
import Switch from '@/components/switch' import Switch from '@/components/switch'
import { DepartmentCascader } from '@/components/department-tree' import { DepartmentCascader } from '@/components/department-tree'
import RolePicker from '@/components/role-picker/RolePicker.tsx' import RolePicker from '@/components/role-picker/RolePicker.tsx'
import Popconfirm from '@/components/popconfirm'
const Users = () => { const Users = () => {
@ -113,6 +114,7 @@ const Users = () => {
> >
{t('actions.edit', '编辑')} {t('actions.edit', '编辑')}
</a>, </a>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
disabled={isResetting} disabled={isResetting}
key={'reset_password_confirm'} key={'reset_password_confirm'}
@ -126,10 +128,11 @@ const Users = () => {
}) })
}}></span> }}></span>
}> }>
<a key="del">
<>
{t('actions.resetPass', '重置密码')} {t('actions.resetPass', '重置密码')}
</a>
</>
</Popconfirm>, </Popconfirm>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isPending} disabled={isPending}
@ -137,9 +140,9 @@ const Users = () => {
deleteUser([ record.id ]) deleteUser([ record.id ])
}} }}
title={t('message.deleteConfirm')}> title={t('message.deleteConfirm')}>
<a key="del">
<>
{t('actions.delete', '删除')} {t('actions.delete', '删除')}
</a>
</>
</Popconfirm> </Popconfirm>
, ,
], ],

105
src/pages/websites/account/index.tsx

@ -1,5 +1,5 @@
import { useTranslation } from '../../../i18n.ts' import { useTranslation } from '../../../i18n.ts'
import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd'
import { Button, Form, Space, Tooltip, Badge, Divider } from 'antd'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { import {
deleteWebsiteDnsAccountAtom, deleteWebsiteDnsAccountAtom,
@ -17,8 +17,10 @@ import { useStyle } from './style.ts'
import { FilterOutlined } from '@ant-design/icons' import { FilterOutlined } from '@ant-design/icons'
import { getValueCount } from '@/utils' import { getValueCount } from '@/utils'
import { Table as ProTable } from '@/components/table' import { Table as ProTable } from '@/components/table'
import {DNSTypeEnum, DNSTypes, syncDNSAtom} from "@/store/websites/dns.ts";
import {WebSite} from "@/types";
import { DNSTypeEnum, DNSTypes, syncDNSAtom } from '@/store/websites/dns.ts'
import { WebSite } from '@/types'
import Switch from '@/components/switch'
import Popconfirm from '@/components/popconfirm'
const i18nPrefix = 'websiteDnsAccounts.list' const i18nPrefix = 'websiteDnsAccounts.list'
const getKeyColumn = (type: string, t) => { const getKeyColumn = (type: string, t) => {
@ -28,14 +30,14 @@ const getKeyColumn = (type: string, t) => {
columns.push(...[ columns.push(...[
{ {
title: t('website.ssl.dns.columns.accessKey', 'Access Key'), title: t('website.ssl.dns.columns.accessKey', 'Access Key'),
dataIndex: 'accessKey',
dataIndex: [ 'authorization', 'accessKey' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
}, },
{ {
title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), title: t('website.ssl.dns.columns.secretKey', 'Secret Key'),
dataIndex: 'secretKey',
dataIndex: [ 'authorization', 'secretKey' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -47,13 +49,13 @@ const getKeyColumn = (type: string, t) => {
columns.push(...[ columns.push(...[
{ {
title: t('website.ssl.dns.columns.secretID', 'Secret ID'), title: t('website.ssl.dns.columns.secretID', 'Secret ID'),
dataIndex: 'secretID',
dataIndex: [ 'authorization', 'secretID' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
}, { }, {
title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), title: t('website.ssl.dns.columns.secretKey', 'Secret Key'),
dataIndex: 'secretKey',
dataIndex: [ 'authorization', 'secretKey' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -65,13 +67,13 @@ const getKeyColumn = (type: string, t) => {
columns.push(...[ columns.push(...[
{ {
title: t('website.ssl.dns.columns.apiId', 'ID'), title: t('website.ssl.dns.columns.apiId', 'ID'),
dataIndex: 'apiId',
dataIndex: [ 'authorization', 'apiId' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
}, { }, {
title: t('website.ssl.dns.columns.token', 'Token'), title: t('website.ssl.dns.columns.token', 'Token'),
dataIndex: 'token',
dataIndex: [ 'authorization', 'token' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -83,13 +85,13 @@ const getKeyColumn = (type: string, t) => {
columns.push(...[ columns.push(...[
{ {
title: t('website.ssl.dns.columns.email', 'Email'), title: t('website.ssl.dns.columns.email', 'Email'),
dataIndex: 'email',
dataIndex: [ 'authorization', 'email' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
}, { }, {
title: t('website.ssl.dns.columns.apiKey', 'API ToKen'), title: t('website.ssl.dns.columns.apiKey', 'API ToKen'),
dataIndex: 'apiKey',
dataIndex: [ 'authorization', 'apiKey' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -103,7 +105,7 @@ const getKeyColumn = (type: string, t) => {
case DNSTypeEnum.NameSilo: case DNSTypeEnum.NameSilo:
columns.push({ columns.push({
title: t('website.ssl.dns.columns.apiKey', 'API Key'), title: t('website.ssl.dns.columns.apiKey', 'API Key'),
dataIndex: 'apiKey',
dataIndex: [ 'authorization', 'apiKey' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -112,7 +114,7 @@ const getKeyColumn = (type: string, t) => {
if (type === DNSTypeEnum.NameCheap) { if (type === DNSTypeEnum.NameCheap) {
columns.push({ columns.push({
title: t('website.ssl.dns.columns.apiUser', 'API User'), title: t('website.ssl.dns.columns.apiUser', 'API User'),
dataIndex: 'apiUser',
dataIndex: [ 'authorization', 'apiUser' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -120,7 +122,7 @@ const getKeyColumn = (type: string, t) => {
} else if (type === DNSTypeEnum.Godaddy) { } else if (type === DNSTypeEnum.Godaddy) {
columns.push({ columns.push({
title: t('website.ssl.dns.columns.apiSecret', 'API Secret'), title: t('website.ssl.dns.columns.apiSecret', 'API Secret'),
dataIndex: 'apiSecret',
dataIndex: [ 'authorization', 'apiSecret' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -131,14 +133,14 @@ const getKeyColumn = (type: string, t) => {
columns.push( columns.push(
{ {
title: t('website.ssl.dns.columns.apiUser', 'UserName'), title: t('website.ssl.dns.columns.apiUser', 'UserName'),
dataIndex: 'apiUser',
dataIndex: [ 'authorization', 'apiUser' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
}, },
{ {
title: t('website.ssl.dns.columns.token', 'Token'), title: t('website.ssl.dns.columns.token', 'Token'),
dataIndex: 'token',
dataIndex: [ 'authorization', 'token' ],
formItemProps: { formItemProps: {
rules: [ { required: true, message: t('message.required') } ] rules: [ { required: true, message: t('message.required') } ]
} }
@ -181,6 +183,12 @@ const WebsiteDnsAccount = () => {
title: t(`${i18nPrefix}.columns.name`, '名称'), title: t(`${i18nPrefix}.columns.name`, '名称'),
dataIndex: 'name', dataIndex: 'name',
valueType: 'text', valueType: 'text',
width: 250,
fieldProps: {
style: {
width: '100%'
}
},
formItemProps: { formItemProps: {
rules: [ rules: [
{ required: true, message: t('message.required', '请输入') } { required: true, message: t('message.required', '请输入') }
@ -188,10 +196,26 @@ const WebsiteDnsAccount = () => {
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.name`, '标签'),
dataIndex: 'tag',
valueType: 'text',
hideInForm: true,
width: 200,
fieldProps: {
style: {
width: '100%'
}
}
},
{
title: t(`${i18nPrefix}.columns.type`, '类型'), title: t(`${i18nPrefix}.columns.type`, '类型'),
dataIndex: 'type', dataIndex: 'type',
valueType: 'select', valueType: 'select',
width: 200,
fieldProps: { fieldProps: {
style: {
width: '100%'
},
options: DNSTypes options: DNSTypes
}, },
formItemProps: { formItemProps: {
@ -212,10 +236,16 @@ const WebsiteDnsAccount = () => {
{ {
title: t(`${i18nPrefix}.columns.status`, '状态'), title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status', dataIndex: 'status',
valueType: 'switch',
width: 200,
render(_dom, record) {
return <Switch size={'small'} value={record.status}/>
}
}, },
{ {
title: t(`${i18nPrefix}.columns.option`, '操作'), title: t(`${i18nPrefix}.columns.option`, '操作'),
key: 'option', key: 'option',
width: 300,
valueType: 'option', valueType: 'option',
fixed: 'right', fixed: 'right',
render: (_, record) => [ render: (_, record) => [
@ -223,20 +253,24 @@ const WebsiteDnsAccount = () => {
as={'a'} as={'a'}
disabled={record.status === 2} disabled={record.status === 2}
onClick={() => { onClick={() => {
record.status = record.status > 0
if (typeof record.authorization === 'string') {
record.authorization = JSON.parse(record.authorization)
}
form.setFieldsValue(record) form.setFieldsValue(record)
setOpen(true) setOpen(true)
}}>{t('actions.edit')}</Action>, }}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'sync_confirm'} key={'sync_confirm'}
disabled={isAsyncing || record.status === 2} disabled={isAsyncing || record.status === 2}
onConfirm={() => { onConfirm={() => {
asyncDNS(record) asyncDNS(record)
}} }}
title={t('message.deleteConfirm')}>
<a key="del">
title={t('message.syncConfirm', '您确定要同步吗?')}>
{t('actions.sync', '同步')} {t('actions.sync', '同步')}
</a>
</Popconfirm>, </Popconfirm>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isDeleting} disabled={isDeleting}
@ -244,9 +278,7 @@ const WebsiteDnsAccount = () => {
deleteWebsiteDnsAccount([ record.id ]) deleteWebsiteDnsAccount([ record.id ])
}} }}
title={t('message.deleteConfirm')}> title={t('message.deleteConfirm')}>
<a key="del">
{t('actions.delete', '删除')} {t('actions.delete', '删除')}
</a>
</Popconfirm> </Popconfirm>
] ]
} }
@ -270,7 +302,19 @@ const WebsiteDnsAccount = () => {
<ListPageLayout className={styles.container}> <ListPageLayout className={styles.container}>
<ProTable <ProTable
rowKey="id" rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '账号管理管理')}
headerTitle={
<Space>
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加帐号')}</Button>
</Space>
}
toolbar={{ toolbar={{
search: { search: {
loading: isFetching && !!search?.title, loading: isFetching && !!search?.title,
@ -298,25 +342,17 @@ const WebsiteDnsAccount = () => {
</Badge> </Badge>
</Tooltip>, </Tooltip>,
<Divider type={'vertical'} key={'divider'}/>, <Divider type={'vertical'} key={'divider'}/>,
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
] ]
}} }}
scroll={{ scroll={{
x: 2500, y: 'calc(100vh - 290px)'
// x: 2500,
y: 'calc(100vh - 290px)'
}} }}
search={false} search={false}
onRow={(record) => { onRow={(record) => {
return { return {
className: cx({ className: cx({
'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id
// 'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id
}), }),
onClick: () => { onClick: () => {
setWebsiteDnsAccount(record) setWebsiteDnsAccount(record)
@ -375,9 +411,11 @@ const WebsiteDnsAccount = () => {
// //
// }} // }}
onFinish={async (values) => { onFinish={async (values) => {
values.status = values.status ? 1 : 0
saveOrUpdate(values) saveOrUpdate(values)
}} }}
columns={columns as ProFormColumnsType[]}/> columns={columns as ProFormColumnsType[]}/>
<BetaSchemaForm <BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '账号管理高级查询')} title={t(`${i18nPrefix}.filter.title`, '账号管理高级查询')}
grid={true} grid={true}
@ -438,6 +476,7 @@ const WebsiteDnsAccount = () => {
}} }}
columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/>
</ListPageLayout> </ListPageLayout>
) )
} }

35
src/pages/websites/domain/index.tsx

@ -1,5 +1,5 @@
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd'
import { Button, Form, Divider, Space, Tooltip, Badge } from 'antd'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { import {
deleteWebsiteDomainAtom, deleteWebsiteDomainAtom,
@ -18,6 +18,7 @@ import { FilterOutlined } from '@ant-design/icons'
import { getValueCount } from '@/utils' import { getValueCount } from '@/utils'
import { Table as ProTable } from '@/components/table' import { Table as ProTable } from '@/components/table'
import { Link } from '@tanstack/react-router' import { Link } from '@tanstack/react-router'
import Popconfirm from '@/components/popconfirm'
const i18nPrefix = 'websiteDomains.list' const i18nPrefix = 'websiteDomains.list'
@ -85,6 +86,7 @@ const WebsiteDomain = () => {
form.setFieldsValue(record) form.setFieldsValue(record)
setOpen(true) setOpen(true)
}}>{t('actions.edit')}</Action>, }}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isDeleting} disabled={isDeleting}
@ -92,9 +94,7 @@ const WebsiteDomain = () => {
deleteWebsiteDomain([ record.id ]) deleteWebsiteDomain([ record.id ])
}} }}
title={t('message.deleteConfirm')}> title={t('message.deleteConfirm')}>
<a key="del">
{t('actions.delete', '删除')} {t('actions.delete', '删除')}
</a>
</Popconfirm> </Popconfirm>
] ]
} }
@ -118,7 +118,19 @@ const WebsiteDomain = () => {
<ListPageLayout className={styles.container}> <ListPageLayout className={styles.container}>
<ProTable <ProTable
rowKey="id" rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '域名管理管理')}
headerTitle={
<Space>
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加域名')}</Button>
</Space>
}
toolbar={{ toolbar={{
search: { search: {
loading: isFetching && !!search?.title, loading: isFetching && !!search?.title,
@ -146,25 +158,18 @@ const WebsiteDomain = () => {
</Badge> </Badge>
</Tooltip>, </Tooltip>,
<Divider type={'vertical'} key={'divider'}/>, <Divider type={'vertical'} key={'divider'}/>,
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
] ]
}} }}
scroll={{ scroll={{
x: 2500, y: 'calc(100vh - 290px)'
// x: 2500,
y: 'calc(100vh - 290px)'
}} }}
search={false} search={false}
onRow={(record) => { onRow={(record) => {
return { return {
className: cx({ className: cx({
'ant-table-row-selected': currentWebsiteDomain?.id === record.id
// 'ant-table-row-selected': currentWebsiteDomain?.id === record.id
}), }),
onClick: () => { onClick: () => {
setWebsiteDomain(record) setWebsiteDomain(record)

35
src/pages/websites/record/index.tsx

@ -1,5 +1,5 @@
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd'
import { Button, Form, Divider, Space, Tooltip, Badge } from 'antd'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { import {
deleteWebsiteDnsRecordsAtom, deleteWebsiteDnsRecordsAtom,
@ -17,6 +17,7 @@ import { useStyle } from './style'
import { FilterOutlined } from '@ant-design/icons' import { FilterOutlined } from '@ant-design/icons'
import { getValueCount } from '@/utils' import { getValueCount } from '@/utils'
import { Table as ProTable } from '@/components/table' import { Table as ProTable } from '@/components/table'
import Popconfirm from '@/components/popconfirm'
const i18nPrefix = 'websiteDnsRecordss.list' const i18nPrefix = 'websiteDnsRecordss.list'
@ -117,6 +118,7 @@ const WebsiteDnsRecords = () => {
form.setFieldsValue(record) form.setFieldsValue(record)
setOpen(true) setOpen(true)
}}>{t('actions.edit')}</Action>, }}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isDeleting} disabled={isDeleting}
@ -124,9 +126,7 @@ const WebsiteDnsRecords = () => {
deleteWebsiteDnsRecords([ record.id ]) deleteWebsiteDnsRecords([ record.id ])
}} }}
title={t('message.deleteConfirm')}> title={t('message.deleteConfirm')}>
<a key="del">
{t('actions.delete', '删除')} {t('actions.delete', '删除')}
</a>
</Popconfirm> </Popconfirm>
] ]
} }
@ -150,7 +150,19 @@ const WebsiteDnsRecords = () => {
<ListPageLayout className={styles.container}> <ListPageLayout className={styles.container}>
<ProTable <ProTable
rowKey="id" rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '记录管理管理')}
headerTitle={
<Space>
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加记录')}</Button>
</Space>
}
toolbar={{ toolbar={{
search: { search: {
loading: isFetching && !!search?.title, loading: isFetching && !!search?.title,
@ -178,25 +190,18 @@ const WebsiteDnsRecords = () => {
</Badge> </Badge>
</Tooltip>, </Tooltip>,
<Divider type={'vertical'} key={'divider'}/>, <Divider type={'vertical'} key={'divider'}/>,
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
] ]
}} }}
scroll={{ scroll={{
x: 2500, y: 'calc(100vh - 290px)'
x: 2000,
y: 'calc(100vh - 290px)'
}} }}
search={false} search={false}
onRow={(record) => { onRow={(record) => {
return { return {
className: cx({ className: cx({
'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id
// 'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id
}), }),
onClick: () => { onClick: () => {
setWebsiteDnsRecords(record) setWebsiteDnsRecords(record)

2
src/routes.tsx

@ -334,7 +334,7 @@ export const RootProvider = memo((props: { context: Partial<IRootContext> }) =>
// history: hashHistory, // history: hashHistory,
context: { queryClient, menuData: [] }, context: { queryClient, menuData: [] },
defaultPreload: 'intent', defaultPreload: 'intent',
defaultPendingComponent: () => <Loading loading={true} delay={300}/>
defaultPendingComponent: () => <Loading loading={true} delay={100}/>
}) })

8
src/store/system.ts

@ -1,9 +1,7 @@
import { IAppData } from '@/global'
import { createStore } from 'jotai'
import { IAppData, MenuItem } from '@/global'
import { createStore, atom } from 'jotai'
import { atomWithStorage } from 'jotai/utils' import { atomWithStorage } from 'jotai/utils'
import { changeLanguage as setLang } from 'i18next' import { changeLanguage as setLang } from 'i18next'
import { atom } from 'jotai/index'
import { System } from '@/types'
/** /**
* app全局状态 * app全局状态
@ -26,7 +24,7 @@ export const getAppData = () => {
return appStore.get(appAtom) return appStore.get(appAtom)
} }
export const currentMenuAtom = atom<System.IMenu>(null)
export const currentMenuAtom = atom<MenuItem | null>(null)
export const changeLanguage = (lang: string, reload?: boolean) => { export const changeLanguage = (lang: string, reload?: boolean) => {

1
src/types/system/menus.d.ts

@ -28,6 +28,7 @@ export interface IMenu {
status: string, status: string,
parent_path: string, parent_path: string,
affix: boolean, affix: boolean,
active: string,
hidden: boolean, hidden: boolean,
hidden_breadcrumb: boolean, hidden_breadcrumb: boolean,
redirect: string, redirect: string,

Loading…
Cancel
Save