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. 29
      src/layout/ListPageLayout.tsx
  6. 312
      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. 163
      src/pages/websites/account/index.tsx
  13. 77
      src/pages/websites/domain/index.tsx
  14. 157
      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 { memo, useCallback } from 'react'
import { useStyle } from './style.ts'
import { useSetAtom } from 'jotai'
import { currentMenuAtom } from '@/store/system.ts'
export const PageBreadcrumb = memo((props: BreadcrumbProps & {
showIcon?: boolean;
@ -12,6 +14,7 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
const { styles } = useStyle()
const nav = useNavigate()
const setCurrent = useSetAtom(currentMenuAtom)
const { items = [], showIcon = true, ...other } = props
const renderIcon = useCallback((icon: any) => {
@ -26,7 +29,7 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
const isLast = route?.path === items[items.length - 1]?.path
if (route.children) {
const items = route.children.map((item) => {
const items = route.children.filter(item => !(item.hidden || item.hidden_breadcrumb)).map((item) => {
return {
...item,
key: item.path || item.name,
@ -41,9 +44,12 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
onClick: (menu) => {
const info = props.menusFlatten.current?.find(item => item.path === menu.key)
if (info) {
setCurrent(info)
// setOpenKeys([ info.path as string ])
nav({
to: info.path,
}).then(() => {
})
}
}
@ -75,10 +81,9 @@ export const PageBreadcrumb = memo((props: BreadcrumbProps & {
)
}
return (
<div style={{ userSelect: 'none' }}>
<Breadcrumb {...other}
<Breadcrumb className={other.className}
items={items}
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;
align-content: center;
}
.ant-table-cell{
.ant-divider-vertical{
margin-inline: 0;
}
}
`
return {

1
src/global.d.ts

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

29
src/layout/ListPageLayout.tsx

@ -1,10 +1,13 @@
import React from 'react'
import { useStyle } from '@/layout/style.ts'
import { PageContainer, PageContainerProps } from '@ant-design/pro-components'
import { useAtomValue } from 'jotai'
import { useAtom } from 'jotai'
import { currentMenuAtom } from '@/store/system.ts'
import useResizeObserver from '@/hooks/useResizeObserver.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 {
children: React.ReactNode
@ -13,15 +16,17 @@ interface IListPageLayoutProps extends PageContainerProps {
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 currentMenu = useAtomValue(currentMenuAtom)
const [ currentMenu, setCurrentMenu ] = useAtom(currentMenuAtom)
const [ , headerSize ] = useResizeObserver({
selector: '.ant-page-header',
})
useInlineStyle({
useInlineStyle({
styles: {
'--pageHeader': `${headerSize.blockSize}px`,
},
@ -33,7 +38,21 @@ const ListPageLayout: React.FC<IListPageLayoutProps> = (
<PageContainer
ghost={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)}
{...props}

312
src/layout/RootLayout.tsx

@ -6,7 +6,7 @@ import { appAtom, currentMenuAtom } from '@/store/system.ts'
import { currentStaticUserAtom, userMenuDataAtom } from '@/store/system/user.ts'
import { MenuItem } from '@/global'
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 { ConfigProvider } from '@/components/config-provider'
import { useEffect, useRef, useState } from 'react'
@ -55,18 +55,24 @@ export default () => {
const [ pathname, setPathname ] = useState(location.pathname)
const [ openMenuKeys, setOpenKeys ] = useState<string[]>([])
const [ collapsed, setCollapsed ] = useState(false)
const [ childPath, setChildPath ] = useState<string>('')
const menusFlatten = useRef<MenuItem[]>()
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 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) {
return [ item.meta.name ]
}
return item ? item.parentName : []
})
const childMenuRef = useRef<MenuItem[]>([])
const currentMenu = menuData.find(item => {
return item.key === rootMenuKeys?.[0]
@ -74,17 +80,21 @@ export default () => {
childMenuRef.current = currentMenu?.children || []
useEffect(() => {
const item = menusFlatten.current?.find(item => item.path === location.pathname)
if (item && item.meta.name !== rootMenuKeys?.[0]) {
setRootMenuKeys(item.parentName)
}
}, [ location.pathname ])
useEffect(() => {
setCurrentMenu(item!)
}, [location.pathname])
if (item && item.hidden && item.active) {
item.parent = menusFlatten.current?.find(i => i.path === item?.active)
setChildPath(item.active)
} else {
setChildPath('')
}
}, [ location.pathname ])
return (
<div
@ -110,167 +120,169 @@ export default () => {
...{
rotate: -31,
content: currentUser?.nickname,
fontColor: 'rgba(0,0,0,.15)',
fontSize: 17,
font: {
color: '#00000012',
size: 17,
},
zIndex: 1009,
}
} style={{ width: '100vw', height: '100vh'}}>
<ProLayout
token={{
header: {
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
},
sider: {
colorMenuBackground: '#222b45',
}
}}
className={styles.myLayout}
// fixedHeader={true}
headerContentRender={false}
headerTitleRender={false}
menuHeaderRender={() => {
return <>
<img height={40}
src={'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'}/>
</>
}}
headerRender={false}
title="Crazy Pro"
// layout={'mix'}
fixSiderbar={true}
siderWidth={80}
collapsedButtonRender={false}
// collapsed={false}
postMenuData={() => {
return menuData.filter(item => !item.hidden).map(item => ({
...item,
children: [],
})) as any
}}
route={
{
path: '/',
routes: menuData.map(item => ({
} as any
} style={{ width: '100vw', height: '100vh' }}>
<ProLayout
token={{
header: {
colorBgMenuItemSelected: 'rgba(0,0,0,0.04)',
},
sider: {
colorMenuBackground: '#222b45',
}
}}
className={styles.myLayout}
// fixedHeader={true}
headerContentRender={false}
headerTitleRender={false}
menuHeaderRender={() => {
return <>
<img height={40}
src={'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg'}/>
</>
}}
headerRender={false}
title="Crazy Pro"
// layout={'mix'}
fixSiderbar={true}
siderWidth={80}
collapsedButtonRender={false}
// collapsed={false}
postMenuData={() => {
return menuData.filter(item => !item.hidden).map(item => ({
...item,
// path: item.path ?? `/${item.key}`,
children: [],
// routes: undefined
}))
})) as any
}}
route={
{
path: '/',
routes: menuData.map(item => ({
...item,
// path: item.path ?? `/${item.key}`,
children: [],
// routes: undefined
}))
}
}
}
location={
{
pathname,
location={
{
pathname,
}
}
}
menu={{
collapsedShowGroupTitle: true,
menu={{
collapsedShowGroupTitle: true,
}}
menuItemRender={(item: MenuDataItem) => {
return (<span style={{ userSelect: 'none' }} onClick={() => {
setRootMenuKeys([ (item as any).key || 'dashboard' ])
setPathname(item.path || '/dashboard')
}}
>
menuItemRender={(item: MenuDataItem) => {
return (<span style={{ userSelect: 'none' }} onClick={() => {
setRootMenuKeys([ (item as any).key || 'dashboard' ])
setPathname(item.path || '/dashboard')
}}
>
<Link to={item.path} className={'menu-link'}
target={item.type === 'url' ? '_blank' : '_self'}>
<span>{item.icon}</span>
<span>{item.name}</span>
</Link>
</span>)
}}
avatarProps={false}
actionsRender={false}
menuProps={{
className: styles.mySiderMenu,
selectedKeys: rootMenuKeys,
theme: 'dark',
}}
loading={isLoading}
contentStyle={{ paddingBlock: 0, paddingInline: 0 }}
{
...{
// "fixSiderbar": true,
// "layout": "side",
'splitMenus': false,
'navTheme': 'realDark',
'contentWidth': 'Fluid',
'colorPrimary': '#1677FF',
// "menuHeaderRender": false
}}
avatarProps={false}
actionsRender={false}
menuProps={{
className: styles.mySiderMenu,
selectedKeys: rootMenuKeys,
theme: 'dark',
}}
loading={isLoading}
contentStyle={{ paddingBlock: 0, paddingInline: 0 }}
{
...{
// "fixSiderbar": true,
// "layout": "side",
'splitMenus': false,
'navTheme': 'realDark',
'contentWidth': 'Fluid',
'colorPrimary': '#1677FF',
// "menuHeaderRender": false
}
}
}
>
>
<If condition={childMenuRef.current?.length > 0}>
<Then>
<Flex className={styles.childMenus}>
{
!collapsed && <div className={styles.childMenuTop}>
<h2>{currentMenu?.title}</h2>
</div>
}
<If condition={childMenuRef.current?.length > 0}>
<Then>
<Flex className={styles.childMenus}>
{
!collapsed && <div className={styles.childMenuTop}>
<h2>{currentMenu?.title}</h2>
</div>
}
<Menu
mode={'inline'}
inlineCollapsed={collapsed}
selectedKeys={[ location.pathname ]}
openKeys={openMenuKeys}
onOpenChange={(keys) => {
setOpenKeys(keys)
}}
onClick={(menu) => {
const info = menusFlatten.current?.find(item => item.path === menu.key)
if (info) {
setCurrentMenu(info)
// setOpenKeys([ info.path as string ])
navigate({
to: info.path,
})
}
<Menu
mode={'inline'}
inlineCollapsed={collapsed}
selectedKeys={[ childPath || location.pathname ]}
openKeys={openMenuKeys}
onOpenChange={(keys) => {
setOpenKeys(keys)
}}
onClick={(menu) => {
const info = menusFlatten.current?.find(item => item.path === menu.key)
if (info) {
setCurrentMenu(info)
// setOpenKeys([ info.path as string ])
navigate({
to: info.path,
})
}
}}
items={convertToMenu((childMenuRef.current || []), (item => {
return {
...item,
key: item.path || item.meta.name,
label: item.title,
}
})) as any}
style={!collapsed ? { width: 210 } : {}}
/>
<div className={styles.childMenuBottom}
onClick={() => {
setCollapsed(!collapsed)
}}>
{
collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>
}
</div>
</Flex>
</Then>
</If>
}}
items={convertToMenu((childMenuRef.current || []), (item => {
return {
data: item,
key: item.path || item.meta.name,
label: item.title,
}
})) as any}
style={!collapsed ? { width: 210 } : {}}
/>
<div className={styles.childMenuBottom}
onClick={() => {
setCollapsed(!collapsed)
}}>
{
collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>
}
</div>
</Flex>
</Then>
</If>
<Flex flex={1} className={styles.body} aria-description={'main-body'} vertical={true}>
<div className={styles.bodyHeader}>
<PageBreadcrumb
menusFlatten={menusFlatten}
className={'top-breadcrumb'}
showIcon={false}
items={items}/>
<Flex flex={1}>
<></>
</Flex>
<Space className={styles.headerRight}>
<SelectLang/>
<Avatar/>
</Space>
</div>
<Outlet/>
</Flex>
{/*<ProLayout
<Flex flex={1} className={styles.body} aria-description={'main-body'} vertical={true}>
<div className={styles.bodyHeader}>
<PageBreadcrumb
menusFlatten={menusFlatten}
className={'top-breadcrumb'}
showIcon={false}
items={items}/>
<Flex flex={1}>
<></>
</Flex>
<Space className={styles.headerRight}>
<SelectLang/>
<Avatar/>
</Space>
</div>
<Outlet/>
</Flex>
{/*<ProLayout
className={styles.mySider}
headerRender={false}
hasSiderMenu={false}
@ -319,7 +331,7 @@ export default () => {
}}
>
</ProLayout>*/}
</ProLayout>
</ProLayout>
</Watermark>
</ConfigProvider>
</ProConfigProvider>

24
src/layout/TwoColPageLayout.tsx

@ -3,10 +3,13 @@ import { PageContainer, PageContainerProps } from '@ant-design/pro-components'
import { Flexbox } from 'react-layout-kit'
import { DraggablePanel, DraggablePanelProps } from '@/components/draggable-panel'
import { useStyle } from './style'
import { useAtomValue } from 'jotai/index'
import { useAtom } from 'jotai/index'
import { currentMenuAtom } from '@/store/system.ts'
import useResizeObserver from '@/hooks/useResizeObserver.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 {
children?: React.ReactNode
@ -18,7 +21,8 @@ interface ITreePageLayoutProps {
export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ...props }) => {
const { styles, cx } = useStyle({ className: 'two-col' })
const currentMenu = useAtomValue(currentMenuAtom)
const navigate = useNavigate()
const [ currentMenu, setCurrentMenu ] = useAtom(currentMenuAtom)
const [ , headerSize ] = useResizeObserver({
selector: '.ant-page-header',
})
@ -33,7 +37,21 @@ export const TwoColPageLayout: React.FC<ITreePageLayoutProps> = ({ className, ..
return (
<PageContainer
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)}
{...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;
max-height: 50px;
overflow: hidden;
justify-content: center;
}
.ant-pro-layout-content,
.ant-pro-layout-container{
@ -128,6 +129,23 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any)
overflow: hidden;
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);
}
}
}
}
`

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 { 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 ListPageLayout from '@/layout/ListPageLayout.tsx'
import Popconfirm from '@/components/popconfirm'
const LoginLog = memo(() => {
@ -97,9 +98,7 @@ const LoginLog = memo(() => {
selectedRowKeys: ids,
selections: [ Table.SELECTION_ALL, Table.SELECTION_INVERT ],
}}
scroll={{
}}
scroll={{}}
tableAlertOptionRender={() => {
return (
<Space size={16}>
@ -107,9 +106,9 @@ const LoginLog = memo(() => {
onConfirm={() => {
deleteLog(ids)
}}
disabled={isDeleting}
title={t('message.batchDelete')}>
<Button type={'link'}
loading={isDeleting}>{t('actions.batchDel')}</Button>
<>{t('actions.batchDel')}</>
</Popconfirm>
</Space>
)

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

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

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

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

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

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

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

@ -1,5 +1,5 @@
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 {
deleteWebsiteDomainAtom,
@ -14,10 +14,11 @@ import {
} from '@ant-design/pro-components'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { useStyle } from './style'
import { FilterOutlined } from '@ant-design/icons'
import { FilterOutlined } from '@ant-design/icons'
import { getValueCount } from '@/utils'
import { Table as ProTable } from '@/components/table'
import { Link } from '@tanstack/react-router'
import Popconfirm from '@/components/popconfirm'
const i18nPrefix = 'websiteDomains.list'
@ -46,25 +47,25 @@ const WebsiteDomain = () => {
hideInSearch: true,
formItemProps: { hidden: true }
},
{
title: t(`${i18nPrefix}.columns.name`, '域名'),
dataIndex: 'name',
render(_text, record){
return <Link to={'/websites/record' }>{record.name}</Link>
}
},
{
title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'),
dataIndex: 'dns_account_id',
},
{
title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status',
},
{
title: t(`${i18nPrefix}.columns.nameservers`, 'nameservers'),
dataIndex: 'nameservers',
},
{
title: t(`${i18nPrefix}.columns.name`, '域名'),
dataIndex: 'name',
render(_text, record) {
return <Link to={'/websites/record'}>{record.name}</Link>
}
},
{
title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'),
dataIndex: 'dns_account_id',
},
{
title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status',
},
{
title: t(`${i18nPrefix}.columns.nameservers`, 'nameservers'),
dataIndex: 'nameservers',
},
{
title: t(`${i18nPrefix}.columns.created`, '创建时间'),
dataIndex: 'created',
@ -85,6 +86,7 @@ const WebsiteDomain = () => {
form.setFieldsValue(record)
setOpen(true)
}}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>,
<Popconfirm
key={'del_confirm'}
disabled={isDeleting}
@ -92,9 +94,7 @@ const WebsiteDomain = () => {
deleteWebsiteDomain([ record.id ])
}}
title={t('message.deleteConfirm')}>
<a key="del">
{t('actions.delete', '删除')}
</a>
{t('actions.delete', '删除')}
</Popconfirm>
]
}
@ -118,7 +118,19 @@ const WebsiteDomain = () => {
<ListPageLayout className={styles.container}>
<ProTable
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={{
search: {
loading: isFetching && !!search?.title,
@ -146,25 +158,18 @@ const WebsiteDomain = () => {
</Badge>
</Tooltip>,
<Divider type={'vertical'} key={'divider'}/>,
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
]
}}
scroll={{
x: 2500, y: 'calc(100vh - 290px)'
// x: 2500,
y: 'calc(100vh - 290px)'
}}
search={false}
onRow={(record) => {
return {
className: cx({
'ant-table-row-selected': currentWebsiteDomain?.id === record.id
// 'ant-table-row-selected': currentWebsiteDomain?.id === record.id
}),
onClick: () => {
setWebsiteDomain(record)

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

@ -1,5 +1,5 @@
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 {
deleteWebsiteDnsRecordsAtom,
@ -17,6 +17,7 @@ import { useStyle } from './style'
import { FilterOutlined } from '@ant-design/icons'
import { getValueCount } from '@/utils'
import { Table as ProTable } from '@/components/table'
import Popconfirm from '@/components/popconfirm'
const i18nPrefix = 'websiteDnsRecordss.list'
@ -45,66 +46,66 @@ const WebsiteDnsRecords = () => {
hideInSearch: true,
formItemProps: { hidden: true }
},
{
title: t(`${i18nPrefix}.columns.record_id`, 'record_id'),
dataIndex: 'record_id',
},
{
title: t(`${i18nPrefix}.columns.domain_id`, 'domain_id'),
dataIndex: 'domain_id',
},
{
title: t(`${i18nPrefix}.columns.name`, 'name'),
dataIndex: 'name',
},
{
title: t(`${i18nPrefix}.columns.content`, 'content'),
dataIndex: 'content',
},
{
title: t(`${i18nPrefix}.columns.poxy`, 'poxy'),
dataIndex: 'poxy',
},
{
title: t(`${i18nPrefix}.columns.ttl`, 'ttl'),
dataIndex: 'ttl',
},
{
title: t(`${i18nPrefix}.columns.type`, 'type'),
dataIndex: 'type',
},
{
title: t(`${i18nPrefix}.columns.status`, 'status'),
dataIndex: 'status',
},
{
title: t(`${i18nPrefix}.columns.tag`, 'tag'),
dataIndex: 'tag',
},
{
title: t(`${i18nPrefix}.columns.remark`, 'remark'),
dataIndex: 'remark',
},
{
title: t(`${i18nPrefix}.columns.created`, 'created'),
dataIndex: 'created',
},
{
title: t(`${i18nPrefix}.columns.modified`, 'modified'),
dataIndex: 'modified',
},
{
title: t(`${i18nPrefix}.columns.record_id`, 'record_id'),
dataIndex: 'record_id',
},
{
title: t(`${i18nPrefix}.columns.domain_id`, 'domain_id'),
dataIndex: 'domain_id',
},
{
title: t(`${i18nPrefix}.columns.name`, 'name'),
dataIndex: 'name',
},
{
title: t(`${i18nPrefix}.columns.content`, 'content'),
dataIndex: 'content',
},
{
title: t(`${i18nPrefix}.columns.poxy`, 'poxy'),
dataIndex: 'poxy',
},
{
title: t(`${i18nPrefix}.columns.ttl`, 'ttl'),
dataIndex: 'ttl',
},
{
title: t(`${i18nPrefix}.columns.type`, 'type'),
dataIndex: 'type',
},
{
title: t(`${i18nPrefix}.columns.status`, 'status'),
dataIndex: 'status',
},
{
title: t(`${i18nPrefix}.columns.tag`, 'tag'),
dataIndex: 'tag',
},
{
title: t(`${i18nPrefix}.columns.remark`, 'remark'),
dataIndex: 'remark',
},
{
title: t(`${i18nPrefix}.columns.created`, 'created'),
dataIndex: 'created',
},
{
title: t(`${i18nPrefix}.columns.modified`, 'modified'),
dataIndex: 'modified',
},
{
title: t(`${i18nPrefix}.columns.option`, '操作'),
key: 'option',
@ -117,6 +118,7 @@ const WebsiteDnsRecords = () => {
form.setFieldsValue(record)
setOpen(true)
}}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>,
<Popconfirm
key={'del_confirm'}
disabled={isDeleting}
@ -124,9 +126,7 @@ const WebsiteDnsRecords = () => {
deleteWebsiteDnsRecords([ record.id ])
}}
title={t('message.deleteConfirm')}>
<a key="del">
{t('actions.delete', '删除')}
</a>
{t('actions.delete', '删除')}
</Popconfirm>
]
}
@ -150,7 +150,19 @@ const WebsiteDnsRecords = () => {
<ListPageLayout className={styles.container}>
<ProTable
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={{
search: {
loading: isFetching && !!search?.title,
@ -178,25 +190,18 @@ const WebsiteDnsRecords = () => {
</Badge>
</Tooltip>,
<Divider type={'vertical'} key={'divider'}/>,
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
]
}}
scroll={{
x: 2500, y: 'calc(100vh - 290px)'
x: 2000,
y: 'calc(100vh - 290px)'
}}
search={false}
onRow={(record) => {
return {
className: cx({
'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id
// 'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id
}),
onClick: () => {
setWebsiteDnsRecords(record)

2
src/routes.tsx

@ -334,7 +334,7 @@ export const RootProvider = memo((props: { context: Partial<IRootContext> }) =>
// history: hashHistory,
context: { queryClient, menuData: [] },
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 { changeLanguage as setLang } from 'i18next'
import { atom } from 'jotai/index'
import { System } from '@/types'
/**
* app全局状态
@ -26,7 +24,7 @@ export const getAppData = () => {
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) => {

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

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

Loading…
Cancel
Save