Browse Source

调整页面

main
dark 5 months ago
parent
commit
124d835edd
  1. 8
      src/App.css
  2. 95
      src/components/dialog/index.tsx
  3. 33
      src/components/dialog/style.ts
  4. 26
      src/components/dialog/useDialog.tsx
  5. 59
      src/components/icon/index.tsx
  6. 5
      src/layout/RootLayout.tsx
  7. 21
      src/locales/lang/pages/websites/zh-CN.ts
  8. 7
      src/locales/lang/zh-CN.ts
  9. 2
      src/pages/websites/dns/DNSList.tsx
  10. 80
      src/pages/websites/domain/index.tsx
  11. 32
      src/store/websites/dns_account.ts

8
src/App.css

@ -22,3 +22,11 @@
.ant-drawer .ant-drawer-footer{ .ant-drawer .ant-drawer-footer{
background-color: #fcfcfc; background-color: #fcfcfc;
} }
.hover{
cursor: pointer;
&:hover{
color: #1c7ed6;
}
}

95
src/components/dialog/index.tsx

@ -0,0 +1,95 @@
import { Modal, ModalProps } from 'antd'
import { useStyle } from './style'
import {
useState,
forwardRef,
ReactNode,
useImperativeHandle,
useCallback, useRef
} from 'react'
export { useDialog } from './useDialog'
export interface DialogRef {
show: (data?: any) => void
close: () => void
get data()
}
export interface DialogProps extends Omit<ModalProps, 'onCancel' | 'onOk'> {
target?: ReactNode | JSX.Element
ref?: DialogRef
onCancel?: () => boolean | void
onOk?: (data?: any) => Promise<boolean | void> | boolean | void
}
const Dialog = forwardRef<DialogRef, DialogProps>(({ open, destroyOnClose, target, ...props }: DialogProps, ref) => {
const { styles, cx } = useStyle()
const [ innerOpen, setOpen ] = useState(() => open ?? false)
const [ submitting, setSubmitting ] = useState(false)
const dataRef = useRef<any>()
useImperativeHandle(ref, () => {
return {
show: (data?: any) => {
dataRef.current = data
setOpen(true)
},
close: () => {
setOpen(false)
},
get data() {
return dataRef.current
}
}
}, [ setOpen ])
const renderTarget = useCallback(() => {
if (target) {
return <span onClick={() => {
setOpen(true)
}}>
{target}
</span>
}
return null
}, [ target ])
return (
<div className={styles.container}>
{renderTarget()}
<Modal
destroyOnClose={destroyOnClose ?? true}
{...props}
wrapClassName={cx(styles.wrap, props.wrapClassName)}
confirmLoading={submitting}
open={innerOpen}
onCancel={() => {
if (props.onCancel?.() === false) {
return
}
setOpen(false)
}}
onOk={async () => {
if (props.onOk) {
setSubmitting(true)
const res = await props.onOk(dataRef.current)
setSubmitting(false)
if (res === false) {
return
}
}
setOpen(false)
}}
>
{props.children}
</Modal>
</div>
)
})
export default Dialog

33
src/components/dialog/style.ts

@ -0,0 +1,33 @@
import { createStyles } from '@/theme'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-dialog-component`
const container = css`
`
const wrap = css`
.ant-modal-content{
padding: 20px 0 15px 0;
.ant-modal-header{
padding: 0 24px 10px;
border-bottom: 1px solid #eee;
}
.ant-modal-body{
padding: 10px 24px;
}
.ant-modal-footer{
background-color: #fcfcfc;
padding: 15px 24px 0;
border-top: 1px solid #eee;
}
}
`
return {
container: cx(prefix, props?.className, container),
wrap,
}
})

26
src/components/dialog/useDialog.tsx

@ -0,0 +1,26 @@
import Dialog, { DialogProps, DialogRef } from './index.tsx'
import { useRef, useCallback, useMemo } from 'react'
export const useDialog = (props: DialogProps) => {
const dialogRef = useRef<DialogRef | undefined>()
const openDialog = useCallback((data?: any) => {
if (dialogRef.current) {
dialogRef.current.show(data)
} else {
console.error('Dialog ref is not defined')
}
}, [])
const closeDialog = useCallback(() => {
if (dialogRef.current) {
dialogRef.current.close()
} else {
console.error('Dialog ref is not defined')
}
}, [])
const dialog = useMemo(() => <Dialog {...props} ref={dialogRef as any}/>, [ props ])
return [ dialogRef, dialog, openDialog, closeDialog ] as const
}

59
src/components/icon/index.tsx

@ -4,13 +4,51 @@ import * as AntIcons from '@ant-design/icons/es/icons'
import IconItem from './picker/IconRender.tsx' import IconItem from './picker/IconRender.tsx'
import { IconUnit } from './types.ts' import { IconUnit } from './types.ts'
import { createStyles } from '@/theme'
type Prefix = 'antd:' | 'park:'; type Prefix = 'antd:' | 'park:';
type IconType = `${Prefix}${string}`; type IconType = `${Prefix}${string}`;
const useStyles = createStyles(({ css, cx }, props: any) => {
const keyframes = css`
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`
const container = css`
display: inline-flex;
align-items: center;
justify-content: center;
`
const size = props.size ? css`
height: ${props.size}px;
width: ${props.size}px;
line-height: ${props.size}px;
` : ''
const isLoading = css`
animation: rotating 2s linear infinite;
`
return {
container: cx( container, size, props.className),
isLoading: cx(keyframes, size, isLoading)
}
})
interface IconProps extends Pick<IconUnit, 'type'> { interface IconProps extends Pick<IconUnit, 'type'> {
type: IconType | IconUnit['type'] type: IconType | IconUnit['type']
isLoading?: boolean
[key: string]: any
} }
function isAntdOrParkIcon(value: string): value is IconType { function isAntdOrParkIcon(value: string): value is IconType {
@ -18,7 +56,10 @@ function isAntdOrParkIcon(value: string): value is IconType {
} }
export function Icon(props: IconProps) { export function Icon(props: IconProps) {
const { type, ...other } = props
const { styles, cx } = useStyles(props)
const { type, isLoading, ...other } = props
if (type && isAntdOrParkIcon(type)) { if (type && isAntdOrParkIcon(type)) {
const [ t, c ] = type.split(':') const [ t, c ] = type.split(':')
return <IconItem {...other as any} type={t} componentName={c}/> return <IconItem {...other as any} type={t} componentName={c}/>
@ -40,11 +81,17 @@ export function Icon(props: IconProps) {
} }
return ( return (
<Fragment>
<IconAll type={type as IconType}
theme="outline" size="20" fill="#868686" strokeWidth={3}
{...other}/>
</Fragment>
<Fragment>
<IconAll type={type as IconType}
className={cx(styles.container, {
[styles.isLoading]: isLoading,
})}
theme="outline"
size={other.size ?? 20}
// fill="#868686"
strokeWidth={3}
{...other}/>
</Fragment>
) )
} }

5
src/layout/RootLayout.tsx

@ -116,7 +116,7 @@ export default () => {
return document.getElementById('crazy-pro-layout') || document.body return document.getElementById('crazy-pro-layout') || document.body
}} }}
> >
<Watermark {
<div {
...{ ...{
rotate: -31, rotate: -31,
content: currentUser?.nickname, content: currentUser?.nickname,
@ -247,6 +247,7 @@ export default () => {
items={convertToMenu((childMenuRef.current || []), (item => { items={convertToMenu((childMenuRef.current || []), (item => {
return { return {
data: item, data: item,
icon: item.icon,
key: item.path || item.meta.name, key: item.path || item.meta.name,
label: item.title, label: item.title,
} }
@ -332,7 +333,7 @@ export default () => {
> >
</ProLayout>*/} </ProLayout>*/}
</ProLayout> </ProLayout>
</Watermark>
</div>
</ConfigProvider> </ConfigProvider>
</ProConfigProvider> </ProConfigProvider>
</CatchBoundary> </CatchBoundary>

21
src/locales/lang/pages/websites/zh-CN.ts

@ -0,0 +1,21 @@
export default {
status: {
/**
* StatusSuccess StatusEnum = "success" // 成功
* StatusFail StatusEnum = "fail" // 失败
* StatusEnable StatusEnum = "enable" // 启用
* StatusPending StatusEnum = "pending" // 等待
* StatusDelete StatusEnum = "delete" // 删除
* StatusDisable StatusEnum = "disable" // 禁用
* StatusSyncing StatusEnum = "syncing" // 同步
*/
success: '成功',
fail: '失败',
enable: '启用',
pending: '等待',
delete: '删除',
disable: '禁用',
syncing: '同步',
}
}

7
src/locales/lang/zh-CN.ts

@ -7,6 +7,7 @@ import video from './pages/cms/video/zh-CN.ts'
import videoCloud from './pages/cms/videoCloud/zh-CN.ts' import videoCloud from './pages/cms/videoCloud/zh-CN.ts'
import videoMagnet from './pages/cms/videoMagnet/zh-CN.ts' import videoMagnet from './pages/cms/videoMagnet/zh-CN.ts'
import list from './pages/videos/list/zh-CN.ts' import list from './pages/videos/list/zh-CN.ts'
import websites from './pages/websites/zh-CN.ts'
export default { export default {
...antdZh, ...antdZh,
@ -46,7 +47,11 @@ export default {
home: { home: {
welcome: '欢迎使用' welcome: '欢迎使用'
}, },
websites:{
common: {
...websites,
},
},
system: { system: {
menus, menus,
roles roles

2
src/pages/websites/dns/DNSList.tsx

@ -220,7 +220,7 @@ const DNSList = () => {
}}>{t('actions.sync', '同步')}</Action>, }}>{t('actions.sync', '同步')}</Action>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isDeleting || record.status === 2}
disabled={isDeleting || record.status === 0 || record.status === 2}
onConfirm={() => { onConfirm={() => {
deleteDNS(record.id) deleteDNS(record.id)
}} }}

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

@ -1,5 +1,5 @@
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Form, Divider, Space, Tooltip, Badge } from 'antd'
import { Button, Form, Divider, Space, Tooltip, Badge, Tag, Input } from 'antd'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { import {
deleteWebsiteDomainAtom, deleteWebsiteDomainAtom,
@ -19,8 +19,12 @@ 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' import Popconfirm from '@/components/popconfirm'
import { accountStatus, accountStatusColor } from '@/store/websites/dns_account.ts'
import Icon from '@/components/icon'
import { useDialog } from '@/components/dialog'
import { dnsListAtom } from '@/store/websites/dns.ts'
const i18nPrefix = 'websiteDomains.list'
const i18nPrefix = 'websites.domain.list'
const WebsiteDomain = () => { const WebsiteDomain = () => {
@ -33,11 +37,27 @@ const WebsiteDomain = () => {
const [ currentWebsiteDomain, setWebsiteDomain ] = useAtom(websiteDomainAtom) const [ currentWebsiteDomain, setWebsiteDomain ] = useAtom(websiteDomainAtom)
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom) const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom)
const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom) const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom)
const { data: dnsData } = useAtomValue(dnsListAtom)
const [ open, setOpen ] = useState(false) const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false) const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title) const [ searchKey, setSearchKey ] = useState(search?.title)
const [ , dialog, openDialog ] = useDialog({
title: '编辑备注',
children: <Form form={form}>
<Form.Item name={'remark'}
label={t(`${i18nPrefix}.domain.columns.remark`, '备注信息')}>
<Input/>
</Form.Item>
</Form>,
onOk: () => {
const id = form.getFieldValue('id')
const remark = form.getFieldValue('remark')
return saveOrUpdate({ remark, id } as any)
}
})
const columns = useMemo(() => { const columns = useMemo(() => {
return [ return [
{ {
@ -51,16 +71,51 @@ const WebsiteDomain = () => {
title: t(`${i18nPrefix}.columns.name`, '域名'), title: t(`${i18nPrefix}.columns.name`, '域名'),
dataIndex: 'name', dataIndex: 'name',
render(_text, record) { render(_text, record) {
return <Link to={'/websites/record'}>{record.name}</Link>
const edit = <Icon className={'hover'}
onClick={() => {
form.setFieldsValue(record)
openDialog(record)
}}
style={{ paddingBlockStart: 0 }} type={'EditTwo'} size={14}/>
return <Space>
<Link to={'/websites/record'}>{record.name}</Link>
{edit}
</Space>
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'), title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'),
dataIndex: 'dns_account_id', dataIndex: 'dns_account_id',
valueType: 'select',
fieldProps: {
options: dnsData?.rows?.map?.(item => {
return {
data: item,
label: item.name,
value: item.id,
}
})
}
}, },
{ {
title: t(`${i18nPrefix}.columns.status`, '状态'), title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status', dataIndex: 'status',
hideInForm: true,
valueType: 'select',
valueEnum: accountStatus,
render(_dom, record) {
const loading = [ 'pending', 'syncing' ].includes(record.status) ?
<Icon type={'LoadingTwo'} size={14} isLoading={true}/> : null
return <Tag
style={{ paddingInline: 5 }}
color={accountStatusColor[record.status]}>
<div style={{ display: 'inline-flex', justifyContent: 'center', alignItems: 'center' }}>
<span style={{ paddingInlineEnd: 3 }}> {t(`websites.common.status.${record.status!}`, record.status + '')}
</span>
{loading}
</div>
</Tag>
}
}, },
{ {
title: t(`${i18nPrefix}.columns.nameservers`, 'nameservers'), title: t(`${i18nPrefix}.columns.nameservers`, 'nameservers'),
@ -69,10 +124,8 @@ const WebsiteDomain = () => {
{ {
title: t(`${i18nPrefix}.columns.created`, '创建时间'), title: t(`${i18nPrefix}.columns.created`, '创建时间'),
dataIndex: 'created', dataIndex: 'created',
},
{
title: t(`${i18nPrefix}.columns.modified`, '修改时间'),
dataIndex: 'modified',
hideInSearch: true,
hideInForm: true,
}, },
{ {
title: t(`${i18nPrefix}.columns.option`, '操作'), title: t(`${i18nPrefix}.columns.option`, '操作'),
@ -87,6 +140,14 @@ const WebsiteDomain = () => {
setOpen(true) setOpen(true)
}}>{t('actions.edit')}</Action>, }}>{t('actions.edit')}</Action>,
<Divider type={'vertical'}/>, <Divider type={'vertical'}/>,
<Action key="sync"
as={'a'}
disabled={record.status === 'syncing'}
onClick={() => {
}}>{t('actions.sync', '同步')}</Action>,
<Divider type={'vertical'}/>,
<Popconfirm <Popconfirm
key={'del_confirm'} key={'del_confirm'}
disabled={isDeleting} disabled={isDeleting}
@ -99,7 +160,7 @@ const WebsiteDomain = () => {
] ]
} }
] as ProColumns[] ] as ProColumns[]
}, [ isDeleting, currentWebsiteDomain, search ])
}, [ isDeleting, currentWebsiteDomain, search, dnsData ])
useEffect(() => { useEffect(() => {
@ -287,6 +348,9 @@ const WebsiteDomain = () => {
}} }}
columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/>
{
dialog
}
</ListPageLayout> </ListPageLayout>
) )
} }

32
src/store/websites/dns_account.ts

@ -13,11 +13,41 @@ type SearchParams = IPage & {
[key: string]: any [key: string]: any
} }
export const accountStatusColor = {
/**
* StatusSuccess StatusEnum = "success" // 成功
* StatusFail StatusEnum = "fail" // 失败
* StatusEnable StatusEnum = "enable" // 启用
* StatusPending StatusEnum = "pending" // 等待
* StatusDelete StatusEnum = "delete" // 删除
* StatusDisable StatusEnum = "disable" // 禁用
* StatusSyncing StatusEnum = "syncing" // 同步
*/
success: 'green',
fail: 'red',
enable: 'blue',
pending: 'blue',
delete: 'red',
disable: 'gray',
syncing: 'blue',
}
export const accountStatus = [
{ label: t(`websites.common.status.success`, 'success'), value: 'success' },
{ label: t(`websites.common.status.fail`, 'fail'), value: 'fail' },
{ label: t(`websites.common.status.enable`, 'enable'), value: 'enable' },
{ label: t(`websites.common.status.pending`, 'pending'), value: 'pending' },
{ label: t(`websites.common.status.delete`, 'delete'), value: 'delete' },
{ label: t(`websites.common.status.disable`, 'disable'), value: 'disable' },
{ label: t(`websites.common.status.syncing`, 'syncing'), value: 'syncing' },
]
export const websiteDnsAccountIdAtom = atom(0) export const websiteDnsAccountIdAtom = atom(0)
export const websiteDnsAccountIdsAtom = atom<number[]>([]) export const websiteDnsAccountIdsAtom = atom<number[]>([])
export const websiteDnsAccountAtom = atom<IWebsiteDnsAccount>(undefined as unknown as IWebsiteDnsAccount )
export const websiteDnsAccountAtom = atom<IWebsiteDnsAccount>(undefined as unknown as IWebsiteDnsAccount)
export const websiteDnsAccountSearchAtom = atom<SearchParams>({ export const websiteDnsAccountSearchAtom = atom<SearchParams>({
key: '', key: '',

Loading…
Cancel
Save