lk
3 months ago
15 changed files with 1261 additions and 980 deletions
-
2.env.proxy.local
-
2mock/menus.ts
-
8src/pages/dashboard/index.tsx
-
4src/pages/globle/globle_style.css
-
829src/pages/websites/account/index.tsx
-
24src/pages/websites/account/style.ts
-
413src/pages/websites/cert/apply.tsx
-
837src/pages/websites/domain/index.tsx
-
10src/pages/websites/domain/style.ts
-
52src/pages/websites/mytest/index.tsx
-
4src/pages/websites/record/index.tsx
-
2src/service/website/domain_group.ts
-
26src/service/websites.ts
-
25src/store/websites/cert.ts
-
3vite.config.ts
@ -1 +1 @@ |
|||
API_URL=http://47.113.117.106:10000 |
|||
API_URL=http://192.168.31.96:8686 |
@ -0,0 +1,4 @@ |
|||
.disabled_text { |
|||
pointer-events: none; /* 禁用鼠标事件 */ |
|||
color: gray; /* 设置文本颜色为灰色 */ |
|||
} |
@ -1,482 +1,519 @@ |
|||
import { useTranslation } from '../../../i18n.ts' |
|||
import { Button, Form, Space, Tooltip, Badge, Divider } from 'antd' |
|||
import { useAtom, useAtomValue, useSetAtom } from 'jotai' |
|||
import { useTranslation } from "../../../i18n.ts"; |
|||
import { Button, Form, Space, Tooltip, Badge, Divider, Spin } from "antd"; |
|||
import { useAtom, useAtomValue, useSetAtom } from "jotai"; |
|||
import { |
|||
deleteWebsiteDnsAccountAtom, |
|||
saveOrUpdateWebsiteDnsAccountAtom, websiteDnsAccountAtom, websiteDnsAccountsAtom, websiteDnsAccountSearchAtom, |
|||
} from '@/store/websites/dns_account.ts' |
|||
import { useEffect, useMemo, useState } from 'react' |
|||
import Action from '@/components/action/Action.tsx' |
|||
import { |
|||
BetaSchemaForm, |
|||
ProColumns, |
|||
ProFormColumnsType, |
|||
} from '@ant-design/pro-components' |
|||
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
|||
import { useStyle } from './style.ts' |
|||
import { FilterOutlined } from '@ant-design/icons' |
|||
import { getValueCount, unSetColumnRules } from '@/utils' |
|||
import { Table as ProTable } from '@/components/table' |
|||
import { DNSTypeEnum, DNSTypes, syncDNSAtom } from '@/store/websites/dns.ts' |
|||
import { WebSite } from '@/types' |
|||
import Switch from '@/components/switch' |
|||
import Popconfirm from '@/components/popconfirm' |
|||
saveOrUpdateWebsiteDnsAccountAtom, |
|||
websiteDnsAccountAtom, |
|||
websiteDnsAccountsAtom, |
|||
websiteDnsAccountSearchAtom, |
|||
} from "@/store/websites/dns_account.ts"; |
|||
import { useEffect, useMemo, useState } from "react"; |
|||
import Action from "@/components/action/Action.tsx"; |
|||
import { BetaSchemaForm, ProColumns, ProFormColumnsType } from "@ant-design/pro-components"; |
|||
import ListPageLayout from "@/layout/ListPageLayout.tsx"; |
|||
import { useStyle } from "./style.ts"; |
|||
import { FilterOutlined } from "@ant-design/icons"; |
|||
import { getValueCount, unSetColumnRules } from "@/utils"; |
|||
import { Table as ProTable } from "@/components/table"; |
|||
import { DNSTypeEnum, DNSTypes, syncDNSAtom } from "@/store/websites/dns.ts"; |
|||
import { WebSite } from "@/types"; |
|||
import Switch from "@/components/switch"; |
|||
import Popconfirm from "@/components/popconfirm"; |
|||
import { Link } from "@tanstack/react-router"; |
|||
import "@/pages/globle/globle_style.css"; |
|||
|
|||
const i18nPrefix = 'websiteDnsAccounts.list' |
|||
const i18nPrefix = "websiteDnsAccounts.list"; |
|||
const getKeyColumn = (type: string, t) => { |
|||
const columns: ProColumns<WebSite.IDnsAccount>[] = [] |
|||
const columns: ProColumns<WebSite.IDnsAccount>[] = []; |
|||
switch (type) { |
|||
case DNSTypeEnum.AliYun: { |
|||
columns.push(...[ |
|||
{ |
|||
title: t('website.ssl.dns.columns.accessKey', 'Access Key'), |
|||
dataIndex: [ 'authorization', 'accessKey' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
{ |
|||
title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), |
|||
dataIndex: [ 'authorization', 'secretKey' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
]) |
|||
} |
|||
break |
|||
case DNSTypeEnum.TencentCloud: { |
|||
columns.push(...[ |
|||
{ |
|||
title: t('website.ssl.dns.columns.secretID', 'Secret ID'), |
|||
dataIndex: [ 'authorization', 'secretID' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, { |
|||
title: t('website.ssl.dns.columns.secretKey', 'Secret Key'), |
|||
dataIndex: [ 'authorization', 'secretKey' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
]) |
|||
break |
|||
case DNSTypeEnum.AliYun: |
|||
{ |
|||
columns.push( |
|||
...[ |
|||
{ |
|||
title: t("website.ssl.dns.columns.accessKey", "Access Key"), |
|||
dataIndex: ["authorization", "accessKey"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
{ |
|||
title: t("website.ssl.dns.columns.secretKey", "Secret Key"), |
|||
dataIndex: ["authorization", "secretKey"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
], |
|||
); |
|||
} |
|||
break; |
|||
case DNSTypeEnum.TencentCloud: { |
|||
columns.push( |
|||
...[ |
|||
{ |
|||
title: t("website.ssl.dns.columns.secretID", "Secret ID"), |
|||
dataIndex: ["authorization", "secretID"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
{ |
|||
title: t("website.ssl.dns.columns.secretKey", "Secret Key"), |
|||
dataIndex: ["authorization", "secretKey"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
], |
|||
); |
|||
break; |
|||
} |
|||
case DNSTypeEnum.DnsPod: { |
|||
columns.push(...[ |
|||
{ |
|||
title: t('website.ssl.dns.columns.apiId', 'ID'), |
|||
dataIndex: [ 'authorization', 'apiId' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, { |
|||
title: t('website.ssl.dns.columns.token', 'Token'), |
|||
dataIndex: [ 'authorization', 'token' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
]) |
|||
break |
|||
columns.push( |
|||
...[ |
|||
{ |
|||
title: t("website.ssl.dns.columns.apiId", "ID"), |
|||
dataIndex: ["authorization", "apiId"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
{ |
|||
title: t("website.ssl.dns.columns.token", "Token"), |
|||
dataIndex: ["authorization", "token"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
], |
|||
); |
|||
break; |
|||
} |
|||
case DNSTypeEnum.CloudFlare: { |
|||
columns.push(...[ |
|||
{ |
|||
title: t('website.ssl.dns.columns.email', 'Email'), |
|||
dataIndex: [ 'authorization', 'email' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, { |
|||
title: t('website.ssl.dns.columns.apiKey', 'API ToKen'), |
|||
dataIndex: [ 'authorization', 'apiKey' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
] |
|||
) |
|||
break |
|||
columns.push( |
|||
...[ |
|||
{ |
|||
title: t("website.ssl.dns.columns.email", "Email"), |
|||
dataIndex: ["authorization", "email"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
{ |
|||
title: t("website.ssl.dns.columns.apiKey", "API ToKen"), |
|||
dataIndex: ["authorization", "apiKey"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
], |
|||
); |
|||
break; |
|||
} |
|||
case DNSTypeEnum.Godaddy: |
|||
case DNSTypeEnum.NameCheap: |
|||
case DNSTypeEnum.NameSilo: |
|||
columns.push({ |
|||
title: t('website.ssl.dns.columns.apiKey', 'API Key'), |
|||
dataIndex: [ 'authorization', 'apiKey' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
) |
|||
title: t("website.ssl.dns.columns.apiKey", "API Key"), |
|||
dataIndex: ["authorization", "apiKey"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}); |
|||
if (type === DNSTypeEnum.NameCheap) { |
|||
columns.push({ |
|||
title: t('website.ssl.dns.columns.apiUser', 'API User'), |
|||
dataIndex: [ 'authorization', 'apiUser' ], |
|||
title: t("website.ssl.dns.columns.apiUser", "API User"), |
|||
dataIndex: ["authorization", "apiUser"], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}) |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}); |
|||
} else if (type === DNSTypeEnum.Godaddy) { |
|||
columns.push({ |
|||
title: t('website.ssl.dns.columns.apiSecret', 'API Secret'), |
|||
dataIndex: [ 'authorization', 'apiSecret' ], |
|||
title: t("website.ssl.dns.columns.apiSecret", "API Secret"), |
|||
dataIndex: ["authorization", "apiSecret"], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}) |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}); |
|||
} |
|||
break |
|||
break; |
|||
case DNSTypeEnum.NameCom: { |
|||
columns.push( |
|||
{ |
|||
title: t('website.ssl.dns.columns.apiUser', 'UserName'), |
|||
dataIndex: [ 'authorization', 'apiUser' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
}, |
|||
{ |
|||
title: t('website.ssl.dns.columns.token', 'Token'), |
|||
dataIndex: [ 'authorization', 'token' ], |
|||
formItemProps: { |
|||
rules: [ { required: true, message: t('message.required') } ] |
|||
} |
|||
} |
|||
) |
|||
break |
|||
{ |
|||
title: t("website.ssl.dns.columns.apiUser", "UserName"), |
|||
dataIndex: ["authorization", "apiUser"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
{ |
|||
title: t("website.ssl.dns.columns.token", "Token"), |
|||
dataIndex: ["authorization", "token"], |
|||
formItemProps: { |
|||
rules: [{ required: true, message: t("message.required") }], |
|||
}, |
|||
}, |
|||
); |
|||
break; |
|||
} |
|||
default: |
|||
break |
|||
|
|||
break; |
|||
} |
|||
return columns |
|||
} |
|||
return columns; |
|||
}; |
|||
const WebsiteDnsAccount = () => { |
|||
const { styles, cx } = useStyle(); |
|||
const { t } = useTranslation(); |
|||
const [form] = Form.useForm(); |
|||
const [filterForm] = Form.useForm(); |
|||
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDnsAccountAtom); |
|||
const [search, setSearch] = useAtom(websiteDnsAccountSearchAtom); |
|||
const setWebsiteDnsAccount = useSetAtom(websiteDnsAccountAtom); |
|||
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsAccountsAtom); |
|||
const { mutate: deleteWebsiteDnsAccount, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsAccountAtom); |
|||
const { mutate: asyncDNS, isPending: isAsyncing } = useAtomValue(syncDNSAtom); |
|||
const [open, setOpen] = useState(false); |
|||
const [openFilter, setFilterOpen] = useState(false); |
|||
const [searchKey, setSearchKey] = useState(search?.title); |
|||
|
|||
const { styles, cx } = useStyle() |
|||
const { t } = useTranslation() |
|||
const [ form ] = Form.useForm() |
|||
const [ filterForm ] = Form.useForm() |
|||
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDnsAccountAtom) |
|||
const [ search, setSearch ] = useAtom(websiteDnsAccountSearchAtom) |
|||
const setWebsiteDnsAccount = useSetAtom(websiteDnsAccountAtom) |
|||
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsAccountsAtom) |
|||
const { mutate: deleteWebsiteDnsAccount, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsAccountAtom) |
|||
const { mutate: asyncDNS, isPending: isAsyncing } = useAtomValue(syncDNSAtom) |
|||
const [ open, setOpen ] = useState(false) |
|||
const [ openFilter, setFilterOpen ] = useState(false) |
|||
const [ searchKey, setSearchKey ] = useState(search?.title) |
|||
|
|||
const columns = useMemo(() => { |
|||
return [ |
|||
{ |
|||
title: 'ID', |
|||
dataIndex: 'id', |
|||
title: "ID", |
|||
dataIndex: "id", |
|||
hideInTable: true, |
|||
hideInSearch: true, |
|||
formItemProps: { hidden: true } |
|||
formItemProps: { hidden: true }, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.name`, '名称'), |
|||
dataIndex: 'name', |
|||
valueType: 'text', |
|||
title: t(`${i18nPrefix}.columns.name`, "名称"), |
|||
dataIndex: "name", |
|||
valueType: "text", |
|||
width: 250, |
|||
fieldProps: { |
|||
style: { |
|||
width: '100%' |
|||
} |
|||
width: "100%", |
|||
}, |
|||
}, |
|||
formItemProps: { |
|||
rules: [ |
|||
{ required: true, message: t('message.required', '请输入') } |
|||
] |
|||
} |
|||
rules: [{ required: true, message: t("message.required", "请输入") }], |
|||
}, |
|||
render: (text, record) => <Link to={`/cert/domain?id=${record.name}`}>{record.name}</Link>, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.name`, '标签'), |
|||
dataIndex: 'tag', |
|||
valueType: 'text', |
|||
title: t(`${i18nPrefix}.columns.name`, "标签"), |
|||
dataIndex: "tag", |
|||
valueType: "text", |
|||
hideInForm: true, |
|||
width: 200, |
|||
fieldProps: { |
|||
style: { |
|||
width: '100%' |
|||
} |
|||
} |
|||
width: "100%", |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.type`, '类型'), |
|||
dataIndex: 'type', |
|||
valueType: 'select', |
|||
title: t(`${i18nPrefix}.columns.type`, "类型"), |
|||
dataIndex: "type", |
|||
valueType: "select", |
|||
width: 200, |
|||
fieldProps: { |
|||
style: { |
|||
width: '100%' |
|||
width: "100%", |
|||
}, |
|||
options: DNSTypes |
|||
options: DNSTypes, |
|||
}, |
|||
formItemProps: { |
|||
rules: [ |
|||
{ required: true, message: t('message.required', '请选择') } |
|||
] |
|||
rules: [{ required: true, message: t("message.required", "请选择") }], |
|||
}, |
|||
}, |
|||
{ |
|||
name: [ 'type' ], |
|||
valueType: 'dependency', |
|||
name: ["type"], |
|||
valueType: "dependency", |
|||
hideInSetting: true, |
|||
hideInTable: true, |
|||
columns: ({ type }) => { |
|||
return getKeyColumn(type, t) |
|||
} |
|||
return getKeyColumn(type, t); |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.status`, '状态'), |
|||
dataIndex: 'status', |
|||
valueType: 'switch', |
|||
title: t(`${i18nPrefix}.columns.status`, "状态111111"), |
|||
dataIndex: "status", |
|||
valueType: "switch", |
|||
width: 200, |
|||
//disable禁用,enable正常,syncing正在同步中
|
|||
render(_dom, record) { |
|||
return <Switch size={'small'} value={record.status}/> |
|||
} |
|||
// return <Switch size={"small"} checked={false} />;
|
|||
if (record.status == "syncing") { |
|||
return ( |
|||
<div className={cx('loadingStyle')}> |
|||
<Spin tip={t(`${i18nPrefix}.columns.syncing`, "同步中")} size="small"> |
|||
</Spin> |
|||
<div className={cx('loadingStyle_table')} >{t(`${i18nPrefix}.columns.syncing`, "同步中")}</div> |
|||
</div> |
|||
); |
|||
} else { |
|||
return <Switch size={"small"} checked={record.status==='enable'} />; |
|||
} |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.option`, '操作'), |
|||
key: 'option', |
|||
title: t(`${i18nPrefix}.columns.option`, "操作"), |
|||
key: "option", |
|||
width: 300, |
|||
valueType: 'option', |
|||
fixed: 'right', |
|||
valueType: "option", |
|||
fixed: "right", |
|||
render: (_, record) => [ |
|||
<Action key="edit" |
|||
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'}/>, |
|||
<Action |
|||
key="edit" |
|||
as={"a"} |
|||
disabled={record.status === "syncing"} |
|||
onClick={() => { |
|||
form.setFieldsValue(record); |
|||
setOpen(true); |
|||
}} |
|||
> |
|||
{t("actions.edit", "编辑")} |
|||
</Action>, |
|||
<Divider type={"vertical"} />, |
|||
<Popconfirm |
|||
key={'sync_confirm'} |
|||
disabled={isAsyncing || record.status === 2} |
|||
onConfirm={() => { |
|||
asyncDNS(record.id) |
|||
}} |
|||
title={t('message.syncConfirm', '您确定要同步吗?')}> |
|||
{t('actions.sync', '同步')} |
|||
key={"sync_confirm"} |
|||
disabled={record.status == "syncing"} |
|||
onConfirm={() => { |
|||
asyncDNS(record.id); |
|||
}} |
|||
title={t("message.syncConfirm", "您确定要同步吗?")} |
|||
> |
|||
{t("actions.sync", "同步")} |
|||
</Popconfirm>, |
|||
<Divider type={'vertical'}/>, |
|||
<Divider type={"vertical"} />, |
|||
<Popconfirm |
|||
key={'del_confirm'} |
|||
disabled={isDeleting} |
|||
onConfirm={() => { |
|||
deleteWebsiteDnsAccount([ record.id ]) |
|||
}} |
|||
title={t('message.deleteConfirm')}> |
|||
{t('actions.delete', '删除')} |
|||
</Popconfirm> |
|||
] |
|||
} |
|||
] as ProColumns[] |
|||
}, [ isAsyncing, isDeleting, form, asyncDNS, deleteWebsiteDnsAccount ]) |
|||
key={"del_confirm"} |
|||
disabled={isDeleting} |
|||
onConfirm={() => { |
|||
deleteWebsiteDnsAccount([record.id]); |
|||
}} |
|||
title={t("message.deleteConfirm")} |
|||
> |
|||
{t("actions.delete", "删除")} |
|||
</Popconfirm>, |
|||
], |
|||
}, |
|||
] as ProColumns[]; |
|||
}, [isAsyncing, isDeleting, form, asyncDNS, deleteWebsiteDnsAccount]); |
|||
|
|||
useEffect(() => { |
|||
setSearchKey(search?.title) |
|||
filterForm.setFieldsValue(search) |
|||
}, [ search ]) |
|||
setSearchKey(search?.title); |
|||
filterForm.setFieldsValue(search); |
|||
}, [search]); |
|||
|
|||
useEffect(() => { |
|||
if (isSuccess) { |
|||
setOpen(false) |
|||
setOpen(false); |
|||
} |
|||
}, [ isSuccess ]) |
|||
}, [isSuccess]); |
|||
|
|||
return ( |
|||
<ListPageLayout className={styles.container}> |
|||
<ProTable |
|||
rowKey="id" |
|||
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, |
|||
onSearch: (value: string) => { |
|||
setSearch(prev => ({ |
|||
...prev, |
|||
title: value |
|||
})) |
|||
}, |
|||
allowClear: true, |
|||
onChange: (e) => { |
|||
setSearchKey(e.target?.value) |
|||
}, |
|||
value: searchKey, |
|||
placeholder: t(`${i18nPrefix}.placeholder`, '输入账号管理名称') |
|||
}, |
|||
actions: [ |
|||
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}> |
|||
<Badge count={getValueCount(search)}> |
|||
<Button |
|||
onClick={() => { |
|||
setFilterOpen(true) |
|||
}} |
|||
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/> |
|||
</Badge> |
|||
</Tooltip>, |
|||
<Divider type={'vertical'} key={'divider'}/>, |
|||
] |
|||
}} |
|||
scroll={{ |
|||
// x: 2500,
|
|||
y: 'calc(100vh - 290px)' |
|||
}} |
|||
search={false} |
|||
onRow={(record) => { |
|||
return { |
|||
className: cx({ |
|||
// 'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id
|
|||
}), |
|||
onClick: () => { |
|||
setWebsiteDnsAccount(record as any) |
|||
} |
|||
} |
|||
}} |
|||
dateFormatter="string" |
|||
loading={isLoading || isFetching} |
|||
dataSource={data?.rows ?? []} |
|||
columns={columns} |
|||
options={{ |
|||
reload: () => { |
|||
refetch() |
|||
}, |
|||
}} |
|||
pagination={{ |
|||
total: data?.total, |
|||
pageSize: search.pageSize, |
|||
current: search.page, |
|||
onShowSizeChange: (current: number, size: number) => { |
|||
setSearch({ |
|||
...search, |
|||
pageSize: size, |
|||
page: current |
|||
}) |
|||
}, |
|||
onChange: (current, pageSize) => { |
|||
setSearch(prev => { |
|||
return { |
|||
...prev, |
|||
page: current, |
|||
pageSize: pageSize, |
|||
} |
|||
}) |
|||
}, |
|||
}} |
|||
/> |
|||
<BetaSchemaForm |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={1000} |
|||
form={form} |
|||
layout={'vertical'} |
|||
scrollToFirstError={true} |
|||
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '账号管理编辑' : '账号管理添加')} |
|||
layoutType={'DrawerForm'} |
|||
open={open} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
}} |
|||
onOpenChange={(open) => { |
|||
setOpen(open) |
|||
}} |
|||
loading={isSubmitting} |
|||
// onValuesChange={(values) => {
|
|||
//
|
|||
// }}
|
|||
onFinish={async (values) => { |
|||
values.status = values.status ? 1 : 0 |
|||
saveOrUpdate(values) |
|||
}} |
|||
columns={columns as ProFormColumnsType[]}/> |
|||
<ListPageLayout className={styles.container}> |
|||
<ProTable |
|||
rowKey="id" |
|||
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, |
|||
onSearch: (value: string) => { |
|||
setSearch((prev) => ({ |
|||
...prev, |
|||
title: value, |
|||
})); |
|||
}, |
|||
allowClear: true, |
|||
onChange: (e) => { |
|||
setSearchKey(e.target?.value); |
|||
}, |
|||
value: searchKey, |
|||
placeholder: t(`${i18nPrefix}.placeholder`, "输入账号管理名称"), |
|||
}, |
|||
actions: [ |
|||
<Tooltip key={"filter"} title={t(`${i18nPrefix}.filter.tooltip`, "高级查询")}> |
|||
<Badge count={getValueCount(search)}> |
|||
<Button |
|||
onClick={() => { |
|||
setFilterOpen(true); |
|||
}} |
|||
icon={<FilterOutlined />} |
|||
shape={"circle"} |
|||
size={"small"} |
|||
/> |
|||
</Badge> |
|||
</Tooltip>, |
|||
<Divider type={"vertical"} key={"divider"} />, |
|||
], |
|||
}} |
|||
scroll={{ |
|||
// x: 2500,
|
|||
y: "calc(100vh - 290px)", |
|||
}} |
|||
search={false} |
|||
onRow={(record) => { |
|||
return { |
|||
className: cx({ |
|||
// 'ant-table-row-selected': currentWebsiteDnsAccount?.id === record.id
|
|||
}), |
|||
onClick: () => { |
|||
setWebsiteDnsAccount(record as any); |
|||
}, |
|||
}; |
|||
}} |
|||
dateFormatter="string" |
|||
loading={isLoading || isFetching} |
|||
dataSource={data?.rows ?? []} |
|||
columns={columns} |
|||
options={{ |
|||
reload: () => { |
|||
refetch(); |
|||
}, |
|||
}} |
|||
pagination={{ |
|||
total: data?.total, |
|||
pageSize: search.pageSize, |
|||
current: search.page, |
|||
onShowSizeChange: (current: number, size: number) => { |
|||
setSearch({ |
|||
...search, |
|||
pageSize: size, |
|||
page: current, |
|||
}); |
|||
}, |
|||
onChange: (current, pageSize) => { |
|||
setSearch((prev) => { |
|||
return { |
|||
...prev, |
|||
page: current, |
|||
pageSize: pageSize, |
|||
}; |
|||
}); |
|||
}, |
|||
}} |
|||
/> |
|||
<BetaSchemaForm |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={1000} |
|||
form={form} |
|||
layout={"vertical"} |
|||
scrollToFirstError={true} |
|||
title={t( |
|||
`${i18nPrefix}.title_${form.getFieldValue("id") !== 0 ? "edit" : "add"}`, |
|||
form.getFieldValue("id") !== 0 ? "账号管理编辑" : "账号管理添加", |
|||
)} |
|||
layoutType={"DrawerForm"} |
|||
open={open} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
}} |
|||
onOpenChange={(open) => { |
|||
setOpen(open); |
|||
}} |
|||
loading={isSubmitting} |
|||
// onValuesChange={(values) => {
|
|||
//
|
|||
// }}
|
|||
//disable禁用,enable正常,syncing正在同步中
|
|||
onFinish={async (values) => { |
|||
values.status = values.status ?'enable': 'disable'; |
|||
saveOrUpdate(values); |
|||
}} |
|||
columns={columns as ProFormColumnsType[]} |
|||
/> |
|||
|
|||
<BetaSchemaForm |
|||
title={t(`${i18nPrefix}.filter.title`, '账号管理高级查询')} |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={500} |
|||
form={filterForm} |
|||
open={openFilter} |
|||
onOpenChange={open => { |
|||
setFilterOpen(open) |
|||
<BetaSchemaForm |
|||
title={t(`${i18nPrefix}.filter.title`, "账号管理高级查询")} |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={500} |
|||
form={filterForm} |
|||
open={openFilter} |
|||
onOpenChange={(open) => { |
|||
setFilterOpen(open); |
|||
}} |
|||
layout={"vertical"} |
|||
scrollToFirstError={true} |
|||
layoutType={"DrawerForm"} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
mask: false, |
|||
}} |
|||
submitter={{ |
|||
searchConfig: { |
|||
resetText: t(`${i18nPrefix}.filter.reset`, "清空"), |
|||
submitText: t(`${i18nPrefix}.filter.submit`, "查询"), |
|||
}, |
|||
onReset: () => { |
|||
filterForm.resetFields(); |
|||
}, |
|||
render: (props) => { |
|||
return ( |
|||
<div style={{ textAlign: "right" }}> |
|||
<Space> |
|||
<Button |
|||
onClick={() => { |
|||
props.reset(); |
|||
}} |
|||
layout={'vertical'} |
|||
scrollToFirstError={true} |
|||
layoutType={'DrawerForm'} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
mask: false, |
|||
> |
|||
{props.searchConfig?.resetText} |
|||
</Button> |
|||
<Button |
|||
type="primary" |
|||
onClick={() => { |
|||
props.submit(); |
|||
}} |
|||
submitter={{ |
|||
searchConfig: { |
|||
resetText: t(`${i18nPrefix}.filter.reset`, '清空'), |
|||
submitText: t(`${i18nPrefix}.filter.submit`, '查询'), |
|||
}, |
|||
onReset: () => { |
|||
filterForm.resetFields() |
|||
}, |
|||
render: (props,) => { |
|||
return ( |
|||
<div style={{ textAlign: 'right' }}> |
|||
<Space> |
|||
<Button onClick={() => { |
|||
props.reset() |
|||
|
|||
}}>{props.searchConfig?.resetText}</Button> |
|||
<Button type="primary" |
|||
onClick={() => { |
|||
props.submit() |
|||
}} |
|||
>{props.searchConfig?.submitText}</Button> |
|||
</Space> |
|||
</div> |
|||
) |
|||
}, |
|||
|
|||
}} |
|||
// onValuesChange={(values) => {
|
|||
//
|
|||
// }}
|
|||
|
|||
onFinish={async (values) => { |
|||
//处理,变成数组
|
|||
Object.keys(values).forEach(key => { |
|||
if (typeof values[key] === 'string' && values[key].includes(',')) { |
|||
values[key] = values[key].split(',') |
|||
} |
|||
}) |
|||
|
|||
setSearch(values) |
|||
> |
|||
{props.searchConfig?.submitText} |
|||
</Button> |
|||
</Space> |
|||
</div> |
|||
); |
|||
}, |
|||
}} |
|||
// onValuesChange={(values) => {
|
|||
//
|
|||
// }}
|
|||
|
|||
}} |
|||
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch) as ProFormColumnsType[])}/> |
|||
onFinish={async (values) => { |
|||
//处理,变成数组
|
|||
Object.keys(values).forEach((key) => { |
|||
if (typeof values[key] === "string" && values[key].includes(",")) { |
|||
values[key] = values[key].split(","); |
|||
} |
|||
}); |
|||
|
|||
</ListPageLayout> |
|||
) |
|||
} |
|||
setSearch(values); |
|||
}} |
|||
columns={unSetColumnRules(columns.filter((item) => !item.hideInSearch) as ProFormColumnsType[])} |
|||
/> |
|||
</ListPageLayout> |
|||
); |
|||
}; |
|||
|
|||
export default WebsiteDnsAccount |
|||
export default WebsiteDnsAccount; |
@ -1,448 +1,529 @@ |
|||
import { useTranslation } from '@/i18n.ts' |
|||
import { Button, Form, Divider, Space, Tooltip, Badge, Tag, Input, Flex, Select } from 'antd' |
|||
import { useAtom, useAtomValue } from 'jotai' |
|||
import { useTranslation } from "@/i18n.ts"; |
|||
import { Button, Form, Divider, Space, Tooltip, Badge, Tag, Input, Flex, Select, Spin } from "antd"; |
|||
import { useAtom, useAtomValue } from "jotai"; |
|||
import { |
|||
deleteWebsiteDomainAtom, |
|||
saveOrUpdateWebsiteDomainAtom, updateGroupWebsiteDomainAtom, |
|||
updateRemarkWebsiteDomainAtom, updateTagWebsiteDomainAtom, |
|||
saveOrUpdateWebsiteDomainAtom, |
|||
updateGroupWebsiteDomainAtom, |
|||
updateRemarkWebsiteDomainAtom, |
|||
updateTagWebsiteDomainAtom, |
|||
websiteDomainAtom, |
|||
websiteDomainsAtom, |
|||
websiteDomainSearchAtom, |
|||
} from '@/store/websites/domain' |
|||
import { useEffect, useMemo, useState } from 'react' |
|||
import Action from '@/components/action/Action.tsx' |
|||
import { |
|||
BetaSchemaForm, |
|||
ProColumns, |
|||
ProFormColumnsType, |
|||
} from '@ant-design/pro-components' |
|||
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
|||
import { useStyle } from './style' |
|||
import { FilterOutlined } from '@ant-design/icons' |
|||
import { getValueCount, unSetColumnRules } from '@/utils' |
|||
import { Table as ProTable } from '@/components/table' |
|||
import { Link } from '@tanstack/react-router' |
|||
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' |
|||
import ModalPro from '@/components/modal-pro' |
|||
import { domainGroupsAtom } from '@/store/websites/domain_groups.ts' |
|||
import NameServer from '@/pages/websites/domain/components/NameServer.tsx' |
|||
} from "@/store/websites/domain"; |
|||
import { useEffect, useMemo, useState } from "react"; |
|||
import Action from "@/components/action/Action.tsx"; |
|||
import { BetaSchemaForm, ProColumns, ProFormColumnsType } from "@ant-design/pro-components"; |
|||
import ListPageLayout from "@/layout/ListPageLayout.tsx"; |
|||
import { useStyle } from "./style"; |
|||
import { FilterOutlined } from "@ant-design/icons"; |
|||
import { getValueCount, unSetColumnRules } from "@/utils"; |
|||
import { Table as ProTable } from "@/components/table"; |
|||
import { Link, useNavigate } from "@tanstack/react-router"; |
|||
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"; |
|||
import ModalPro from "@/components/modal-pro"; |
|||
import { domainGroupsAtom } from "@/store/websites/domain_groups.ts"; |
|||
import NameServer from "@/pages/websites/domain/components/NameServer.tsx"; |
|||
import Switch from "@/components/switch"; |
|||
|
|||
const i18nPrefix = 'websites.domain.list' |
|||
const i18nPrefix = "websites.domain.list"; |
|||
|
|||
const WebsiteDomain = () => { |
|||
const { styles, cx } = useStyle(); |
|||
const { t } = useTranslation(); |
|||
const [form] = Form.useForm(); |
|||
const [filterForm] = Form.useForm(); |
|||
const navigate = useNavigate(); |
|||
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDomainAtom); |
|||
const { mutate: updateRemark } = useAtomValue(updateRemarkWebsiteDomainAtom); |
|||
const { mutate: updateTags } = useAtomValue(updateTagWebsiteDomainAtom); |
|||
const { mutate: updateGroup } = useAtomValue(updateGroupWebsiteDomainAtom); |
|||
const [search, setSearch] = useAtom(websiteDomainSearchAtom); |
|||
const [currentWebsiteDomain, setWebsiteDomain] = useAtom(websiteDomainAtom); |
|||
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom); |
|||
const { data: groupData, isFetching: isGroupLoading } = useAtomValue(domainGroupsAtom); |
|||
|
|||
const { styles, cx } = useStyle() |
|||
const { t } = useTranslation() |
|||
const [ form ] = Form.useForm() |
|||
const [ filterForm ] = Form.useForm() |
|||
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDomainAtom) |
|||
const { mutate: updateRemark } = useAtomValue(updateRemarkWebsiteDomainAtom) |
|||
const { mutate: updateTags } = useAtomValue(updateTagWebsiteDomainAtom) |
|||
const { mutate: updateGroup } = useAtomValue(updateGroupWebsiteDomainAtom) |
|||
const [ search, setSearch ] = useAtom(websiteDomainSearchAtom) |
|||
const [ currentWebsiteDomain, setWebsiteDomain ] = useAtom(websiteDomainAtom) |
|||
const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDomainsAtom) |
|||
const { data: groupData, isFetching: isGroupLoading } = useAtomValue(domainGroupsAtom) |
|||
|
|||
const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom) |
|||
const { data: dnsData } = useAtomValue(dnsListAtom) |
|||
const { mutate: deleteWebsiteDomain, isPending: isDeleting } = useAtomValue(deleteWebsiteDomainAtom); |
|||
const { data: dnsData } = useAtomValue(dnsListAtom); |
|||
|
|||
const [ open, setOpen ] = useState(false) |
|||
const [ openFilter, setFilterOpen ] = useState(false) |
|||
const [ searchKey, setSearchKey ] = useState(search?.title) |
|||
const [open, setOpen] = useState(false); |
|||
const [openFilter, setFilterOpen] = useState(false); |
|||
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>, |
|||
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 updateRemark({ remark, id } as any) |
|||
} |
|||
}) |
|||
const id = form.getFieldValue("id"); |
|||
const remark = form.getFieldValue("remark"); |
|||
return updateRemark({ remark, id } as any); |
|||
}, |
|||
}); |
|||
|
|||
const columns = useMemo(() => { |
|||
return [ |
|||
{ |
|||
title: 'ID', |
|||
dataIndex: 'id', |
|||
title: "ID", |
|||
dataIndex: "id", |
|||
hideInTable: true, |
|||
hideInSearch: true, |
|||
formItemProps: { hidden: true } |
|||
formItemProps: { hidden: true }, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.name`, '域名'), |
|||
dataIndex: 'name', |
|||
title: t(`${i18nPrefix}.columns.name`, "域名"), |
|||
dataIndex: "name", |
|||
width: 300, |
|||
fieldProps: { |
|||
style: { width: '100%' } |
|||
style: { width: "100%" }, |
|||
}, |
|||
render(_text, record) { |
|||
const edit = <Icon className={'hover'} |
|||
onClick={() => { |
|||
form.setFieldsValue(record) |
|||
openDialog(record) |
|||
}} |
|||
style={{ paddingBlockStart: 0 }} type={'EditTwo'} size={14}/> |
|||
return <Flex vertical={true} style={{ paddingInlineStart: 5 }}> |
|||
<Space> |
|||
<Link to={`/websites/record?id=${record.id}`}>{record.name}</Link> |
|||
{edit} |
|||
</Space> |
|||
<Flex className={'color-gray'}>{record.remark}</Flex> |
|||
</Flex> |
|||
} |
|||
const edit = ( |
|||
<Icon |
|||
className={"hover"} |
|||
onClick={() => { |
|||
form.setFieldsValue(record); |
|||
openDialog(record); |
|||
}} |
|||
style={{ paddingBlockStart: 0 }} |
|||
type={"EditTwo"} |
|||
size={14} |
|||
/> |
|||
); |
|||
return ( |
|||
<Flex vertical={true} style={{ paddingInlineStart: 5 }}> |
|||
<Space> |
|||
<Link to={`/websites/record?id=${record.id}`}>{record.name}</Link> |
|||
{edit} |
|||
</Space> |
|||
<Flex className={"color-gray"}>{record.remark}</Flex> |
|||
</Flex> |
|||
); |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.record_count`, '记录数'), |
|||
dataIndex: 'record_count', |
|||
title: t(`${i18nPrefix}.columns.record_count`, "记录数"), |
|||
dataIndex: "record_count", |
|||
hideInForm: true, |
|||
hideInSearch: true, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'), |
|||
dataIndex: 'dns_account_id', |
|||
valueType: 'select', |
|||
title: t(`${i18nPrefix}.columns.dns_account_id`, "DNS账号"), |
|||
dataIndex: "dns_account_id", |
|||
valueType: "select", |
|||
fieldProps: { |
|||
options: dnsData?.rows?.map?.(item => { |
|||
options: dnsData?.rows?.map?.((item) => { |
|||
return { |
|||
data: item, |
|||
label: item.name, |
|||
value: item.id, |
|||
} |
|||
}) |
|||
} |
|||
}; |
|||
}), |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.status`, '状态'), |
|||
dataIndex: 'status', |
|||
title: t(`${i18nPrefix}.columns.status`, "状态"), |
|||
dataIndex: "status", |
|||
hideInForm: true, |
|||
valueType: 'select', |
|||
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> |
|||
} |
|||
switch (record.status) { |
|||
//active活动,pending等待,delete已经删除,disable禁用,syn正在同步
|
|||
case "enable": |
|||
case "active": |
|||
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> |
|||
</div> |
|||
</Tag> |
|||
); |
|||
case "pending": |
|||
// return <Icon type={"LoadingTwo"} size={14} isLoading={true} />;
|
|||
return ( |
|||
<div className={cx("loadingStyle")}> |
|||
<Spin tip={t(`${i18nPrefix}.columns.pending`, "等待")} size="small"></Spin> |
|||
<div className={cx("loadingStyle_table")}>{t(`${i18nPrefix}.columns.syncing`, "等待")}</div> |
|||
</div> |
|||
); |
|||
case "delete": |
|||
case "disable": |
|||
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> |
|||
</div> |
|||
</Tag> |
|||
); |
|||
case "syncing": |
|||
return ( |
|||
<div className={cx("loadingStyle")}> |
|||
<Spin tip={t(`${i18nPrefix}.columns.syncing`, "同步中")} size="small"></Spin> |
|||
<div className={cx("loadingStyle_table")}>{t(`${i18nPrefix}.columns.syncing`, "同步中")}</div> |
|||
</div> |
|||
); |
|||
} |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.nameservers`, 'DNS服务器地址'), |
|||
dataIndex: 'nameservers', |
|||
title: t(`${i18nPrefix}.columns.nameservers`, "DNS服务器地址"), |
|||
dataIndex: "nameservers", |
|||
width: 150, |
|||
hideInSearch: true, |
|||
hideInForm: true, |
|||
render(_dom, record) { |
|||
return <NameServer data={record}/> |
|||
} |
|||
return <NameServer data={record} />; |
|||
}, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.created`, '创建时间'), |
|||
dataIndex: 'created', |
|||
title: t(`${i18nPrefix}.columns.created`, "创建时间"), |
|||
dataIndex: "created", |
|||
hideInSearch: true, |
|||
hideInForm: true, |
|||
}, |
|||
{ |
|||
title: t(`${i18nPrefix}.columns.option`, '操作'), |
|||
key: 'option', |
|||
valueType: 'option', |
|||
fixed: 'right', |
|||
title: t(`${i18nPrefix}.columns.option`, "操作"), |
|||
key: "option", |
|||
valueType: "option", |
|||
fixed: "right", |
|||
render: (_, record) => [ |
|||
<Link to={`/websites/record?id=${record.id}`}>解析设置</Link>, |
|||
|
|||
/*<Action key="edit" |
|||
as={'a'} |
|||
onClick={() => { |
|||
// form.setFieldsValue(record)
|
|||
// setOpen(true)
|
|||
|
|||
}}>{t('actions.recordSet', '解析设置')}</Action>,*/ |
|||
<Divider type={'vertical'}/>, |
|||
<Action key="sync" |
|||
as={'a'} |
|||
disabled={record.status === 'syncing'} |
|||
onClick={() => { |
|||
// <Link to={`/cert/record?id=${record.id}`}>解析设置</Link>,
|
|||
|
|||
}}>{t('actions.sync', '同步')}</Action>, |
|||
<Action |
|||
key="edit" |
|||
as={"a"} |
|||
onClick={() => { |
|||
form.setFieldsValue(record); |
|||
setOpen(true); |
|||
}} |
|||
> |
|||
{t("actions.recordSet", "设置")} |
|||
</Action>, |
|||
<Divider type={"vertical"} />, |
|||
<Action |
|||
key="record" |
|||
as={"a"} |
|||
onClick={() => { |
|||
navigate({ |
|||
to: `/cert/record`, |
|||
search: { id: record.id }, |
|||
}); |
|||
}} |
|||
> |
|||
{t("actions.recordSet", "记录")} |
|||
</Action>, |
|||
<Divider type={"vertical"} />, |
|||
<Action key="sync" as={"a"} disabled={record.status === "syncing"} onClick={() => {}}> |
|||
{t("actions.sync", "同步")} |
|||
</Action>, |
|||
|
|||
<Divider type={'vertical'}/>, |
|||
<Divider type={"vertical"} />, |
|||
<Popconfirm |
|||
key={'del_confirm'} |
|||
disabled={isDeleting} |
|||
onConfirm={() => { |
|||
deleteWebsiteDomain([ record.id ]) |
|||
}} |
|||
title={t('message.deleteConfirm')}> |
|||
{t('actions.delete', '删除')} |
|||
</Popconfirm> |
|||
] |
|||
} |
|||
] as ProColumns[] |
|||
}, [ isDeleting, currentWebsiteDomain, search, dnsData ]) |
|||
key={"del_confirm"} |
|||
disabled={isDeleting} |
|||
onConfirm={() => { |
|||
deleteWebsiteDomain([record.id]); |
|||
}} |
|||
title={t("message.deleteConfirm")} |
|||
> |
|||
{t("actions.delete", "删除")} |
|||
</Popconfirm>, |
|||
], |
|||
}, |
|||
] as ProColumns[]; |
|||
}, [isDeleting, currentWebsiteDomain, search, dnsData]); |
|||
|
|||
useEffect(() => { |
|||
const queryParams = new URLSearchParams(location.search); |
|||
const dnsAccountId = queryParams.get("id"); |
|||
|
|||
setSearchKey(search?.title) |
|||
filterForm.setFieldsValue(search) |
|||
if (dnsAccountId) { |
|||
setSearch((prev) => ({ |
|||
...prev, |
|||
name: "", |
|||
dns_account_id: dnsAccountId, |
|||
})); |
|||
} |
|||
}, [location.search]); |
|||
|
|||
}, [ search ]) |
|||
useEffect(() => { |
|||
setSearchKey(search?.title); |
|||
filterForm.setFieldsValue(search); |
|||
}, [search]); |
|||
|
|||
useEffect(() => { |
|||
if (isSuccess) { |
|||
setOpen(false) |
|||
setOpen(false); |
|||
} |
|||
}, [ isSuccess ]) |
|||
}, [isSuccess]); |
|||
|
|||
return ( |
|||
<ListPageLayout className={styles.container}> |
|||
<ProTable |
|||
rowKey="id" |
|||
headerTitle={ |
|||
<Space> |
|||
<Button key={'add'} |
|||
onClick={() => { |
|||
form.resetFields() |
|||
form.setFieldsValue({ |
|||
id: 0, |
|||
}) |
|||
setOpen(true) |
|||
}} |
|||
type={'primary'}>{t(`${i18nPrefix}.add`, '添加域名')}</Button> |
|||
</Space> |
|||
} |
|||
tableAlertRender={(props) => { |
|||
return <Space> |
|||
<ModalPro |
|||
alterType={'confirm'} |
|||
title={'删除域名提示'} |
|||
content={<span>确认删除此域名?<br/>删除域名,解析记录会同步删除,且无法恢复。</span>} |
|||
onOk={() => { |
|||
deleteWebsiteDomain(props.selectedRows?.map(item => item.id)) |
|||
}} |
|||
> |
|||
<Button disabled={props.selectedRows?.length == 0}>{t('actions.delete', '删除')}</Button> |
|||
</ModalPro> |
|||
<ModalPro |
|||
alterType={'dialog'} |
|||
title={t(`${i18nPrefix}.group.title`, '移动分组')} |
|||
content={<> |
|||
<Form form={form}> |
|||
<Form.Item |
|||
name={'group_id'} |
|||
label={t(`${i18nPrefix}.group.columns.group`, '移动分组')}> |
|||
<Select |
|||
loading={isGroupLoading} |
|||
options={ |
|||
[ |
|||
{ |
|||
label: t(`${i18nPrefix}.group.default`, '默认分组'), |
|||
value: 0, |
|||
}, |
|||
...(groupData?.rows?.map(item => { |
|||
return { |
|||
label: item.name, |
|||
value: item.id, |
|||
} |
|||
}) ?? []) |
|||
] |
|||
}/> |
|||
</Form.Item> |
|||
</Form> |
|||
</>} |
|||
onLoad={() => { |
|||
form.setFieldsValue({ group_id: 0 }) |
|||
}} |
|||
onOk={() => { |
|||
const group_id = form.getFieldValue('group_id') |
|||
updateGroup({ id: props.selectedRows?.map(item => item.id), group_id }) |
|||
}} |
|||
> |
|||
<Button disabled={props.selectedRows?.length == 0}>{t(`${i18nPrefix}.actions.changeGroup`, '更换分组')}</Button> |
|||
</ModalPro> |
|||
</Space> |
|||
}} |
|||
tableAlertOptionRender={false} |
|||
toolbar={{ |
|||
search: { |
|||
loading: isFetching && !!search?.title, |
|||
onSearch: (value: string) => { |
|||
setSearch(prev => ({ |
|||
...prev, |
|||
title: value |
|||
})) |
|||
}, |
|||
allowClear: true, |
|||
onChange: (e) => { |
|||
setSearchKey(e.target?.value) |
|||
}, |
|||
value: searchKey, |
|||
placeholder: t(`${i18nPrefix}.placeholder`, '输入域名管理名称') |
|||
}, |
|||
actions: [ |
|||
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}> |
|||
<Badge count={getValueCount(search)}> |
|||
<Button |
|||
onClick={() => { |
|||
setFilterOpen(true) |
|||
}} |
|||
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/> |
|||
</Badge> |
|||
</Tooltip>, |
|||
<Divider type={'vertical'} key={'divider'}/>, |
|||
|
|||
] |
|||
}} |
|||
scroll={{ |
|||
// x: 2500,
|
|||
y: 'calc(100vh - 290px)' |
|||
}} |
|||
search={false} |
|||
onRow={(record) => { |
|||
return { |
|||
className: cx({ |
|||
// 'ant-table-row-selected': currentWebsiteDomain?.id === record.id
|
|||
}), |
|||
onClick: () => { |
|||
setWebsiteDomain(record) |
|||
} |
|||
} |
|||
}} |
|||
rowSelection={{ |
|||
type: 'checkbox', |
|||
alwaysShowAlert: true, |
|||
}} |
|||
dateFormatter="string" |
|||
loading={isLoading || isFetching} |
|||
dataSource={data?.rows ?? []} |
|||
columns={columns} |
|||
options={{ |
|||
reload: () => { |
|||
refetch() |
|||
}, |
|||
}} |
|||
pagination={{ |
|||
total: data?.total, |
|||
pageSize: search.pageSize, |
|||
current: search.page, |
|||
onShowSizeChange: (current: number, size: number) => { |
|||
setSearch({ |
|||
...search, |
|||
pageSize: size, |
|||
page: current |
|||
}) |
|||
}, |
|||
onChange: (current, pageSize) => { |
|||
setSearch(prev => { |
|||
return { |
|||
...prev, |
|||
page: current, |
|||
pageSize: pageSize, |
|||
} |
|||
}) |
|||
}, |
|||
}} |
|||
/> |
|||
<BetaSchemaForm |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={1000} |
|||
form={form} |
|||
layout={'vertical'} |
|||
scrollToFirstError={true} |
|||
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '域名管理编辑' : '域名管理添加')} |
|||
layoutType={'DrawerForm'} |
|||
open={open} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
}} |
|||
onOpenChange={(open) => { |
|||
setOpen(open) |
|||
}} |
|||
loading={isSubmitting} |
|||
|
|||
onFinish={async (values) => { |
|||
saveOrUpdate(values) |
|||
}} |
|||
columns={columns as ProFormColumnsType[]}/> |
|||
<BetaSchemaForm |
|||
title={t(`${i18nPrefix}.filter.title`, '域名管理高级查询')} |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={500} |
|||
form={filterForm} |
|||
open={openFilter} |
|||
onOpenChange={open => { |
|||
setFilterOpen(open) |
|||
}} |
|||
layout={'vertical'} |
|||
scrollToFirstError={true} |
|||
layoutType={'DrawerForm'} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
mask: false, |
|||
}} |
|||
submitter={{ |
|||
searchConfig: { |
|||
resetText: t(`${i18nPrefix}.filter.reset`, '清空'), |
|||
submitText: t(`${i18nPrefix}.filter.submit`, '查询'), |
|||
}, |
|||
onReset: () => { |
|||
filterForm.resetFields() |
|||
}, |
|||
render: (props,) => { |
|||
return ( |
|||
<div style={{ textAlign: 'right' }}> |
|||
<Space> |
|||
<Button onClick={() => { |
|||
props.reset() |
|||
|
|||
}}>{props.searchConfig?.resetText}</Button> |
|||
<Button type="primary" |
|||
onClick={() => { |
|||
props.submit() |
|||
}} |
|||
>{props.searchConfig?.submitText}</Button> |
|||
</Space> |
|||
</div> |
|||
) |
|||
}, |
|||
|
|||
<ListPageLayout className={styles.container}> |
|||
<ProTable |
|||
rowKey="id" |
|||
headerTitle={ |
|||
<Space> |
|||
<Button |
|||
key={"add"} |
|||
onClick={() => { |
|||
form.resetFields(); |
|||
form.setFieldsValue({ |
|||
id: 0, |
|||
}); |
|||
setOpen(true); |
|||
}} |
|||
type={"primary"} |
|||
> |
|||
{t(`${i18nPrefix}.add`, "添加域名")} |
|||
</Button> |
|||
</Space> |
|||
} |
|||
tableAlertRender={(props) => { |
|||
return ( |
|||
<Space> |
|||
<ModalPro |
|||
alterType={"confirm"} |
|||
title={"删除域名提示"} |
|||
content={ |
|||
<span> |
|||
确认删除此域名? |
|||
<br /> |
|||
删除域名,解析记录会同步删除,且无法恢复。 |
|||
</span> |
|||
} |
|||
onOk={() => { |
|||
deleteWebsiteDomain(props.selectedRows?.map((item) => item.id)); |
|||
}} |
|||
> |
|||
<Button disabled={props.selectedRows?.length == 0}>{t("actions.delete", "删除")}</Button> |
|||
</ModalPro> |
|||
<ModalPro |
|||
alterType={"dialog"} |
|||
title={t(`${i18nPrefix}.group.title`, "移动分组")} |
|||
content={ |
|||
<> |
|||
<Form form={form}> |
|||
<Form.Item name={"group_id"} label={t(`${i18nPrefix}.group.columns.group`, "移动分组")}> |
|||
<Select |
|||
loading={isGroupLoading} |
|||
options={[ |
|||
{ |
|||
label: t(`${i18nPrefix}.group.default`, "默认分组"), |
|||
value: 0, |
|||
}, |
|||
...(groupData?.rows?.map((item) => { |
|||
return { |
|||
label: item.name, |
|||
value: item.id, |
|||
}; |
|||
}) ?? []), |
|||
]} |
|||
/> |
|||
</Form.Item> |
|||
</Form> |
|||
</> |
|||
} |
|||
onLoad={() => { |
|||
form.setFieldsValue({ group_id: 0 }); |
|||
}} |
|||
onOk={() => { |
|||
const group_id = form.getFieldValue("group_id"); |
|||
updateGroup({ id: props.selectedRows?.map((item) => item.id), group_id }); |
|||
}} |
|||
> |
|||
<Button disabled={props.selectedRows?.length == 0}> |
|||
{t(`${i18nPrefix}.actions.changeGroup`, "更换分组")} |
|||
</Button> |
|||
</ModalPro> |
|||
</Space> |
|||
); |
|||
}} |
|||
tableAlertOptionRender={false} |
|||
toolbar={{ |
|||
search: { |
|||
loading: isFetching && !!search?.title, |
|||
onSearch: (value: string) => { |
|||
setSearch((prev) => ({ |
|||
...prev, |
|||
title: value, |
|||
})); |
|||
}, |
|||
allowClear: true, |
|||
onChange: (e) => { |
|||
setSearchKey(e.target?.value); |
|||
}, |
|||
value: searchKey, |
|||
placeholder: t(`${i18nPrefix}.placeholder`, "输入域名管理名称"), |
|||
}, |
|||
actions: [ |
|||
<Tooltip key={"filter"} title={t(`${i18nPrefix}.filter.tooltip`, "高级查询")}> |
|||
<Badge count={getValueCount(search)}> |
|||
<Button |
|||
onClick={() => { |
|||
setFilterOpen(true); |
|||
}} |
|||
icon={<FilterOutlined />} |
|||
shape={"circle"} |
|||
size={"small"} |
|||
/> |
|||
</Badge> |
|||
</Tooltip>, |
|||
<Divider type={"vertical"} key={"divider"} />, |
|||
], |
|||
}} |
|||
scroll={{ |
|||
// x: 2500,
|
|||
y: "calc(100vh - 290px)", |
|||
}} |
|||
search={false} |
|||
onRow={(record) => { |
|||
return { |
|||
className: cx({ |
|||
// 'ant-table-row-selected': currentWebsiteDomain?.id === record.id
|
|||
}), |
|||
onClick: () => { |
|||
setWebsiteDomain(record); |
|||
}, |
|||
}; |
|||
}} |
|||
rowSelection={{ |
|||
type: "checkbox", |
|||
alwaysShowAlert: true, |
|||
}} |
|||
dateFormatter="string" |
|||
loading={isLoading || isFetching} |
|||
dataSource={data?.rows ?? []} |
|||
columns={columns} |
|||
options={{ |
|||
reload: () => { |
|||
refetch(); |
|||
}, |
|||
}} |
|||
pagination={{ |
|||
total: data?.total, |
|||
pageSize: search.pageSize, |
|||
current: search.page, |
|||
onShowSizeChange: (current: number, size: number) => { |
|||
setSearch({ |
|||
...search, |
|||
pageSize: size, |
|||
page: current, |
|||
}); |
|||
}, |
|||
onChange: (current, pageSize) => { |
|||
setSearch((prev) => { |
|||
return { |
|||
...prev, |
|||
page: current, |
|||
pageSize: pageSize, |
|||
}; |
|||
}); |
|||
}, |
|||
}} |
|||
/> |
|||
<BetaSchemaForm |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={1000} |
|||
form={form} |
|||
layout={"vertical"} |
|||
scrollToFirstError={true} |
|||
title={t( |
|||
`${i18nPrefix}.title_${form.getFieldValue("id") !== 0 ? "edit" : "add"}`, |
|||
form.getFieldValue("id") !== 0 ? "域名管理编辑" : "域名管理添加", |
|||
)} |
|||
layoutType={"DrawerForm"} |
|||
open={open} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
}} |
|||
onOpenChange={(open) => { |
|||
setOpen(open); |
|||
}} |
|||
loading={isSubmitting} |
|||
onFinish={async (values) => { |
|||
saveOrUpdate(values); |
|||
}} |
|||
columns={columns as ProFormColumnsType[]} |
|||
/> |
|||
<BetaSchemaForm |
|||
title={t(`${i18nPrefix}.filter.title`, "域名管理高级查询")} |
|||
grid={true} |
|||
shouldUpdate={false} |
|||
width={500} |
|||
form={filterForm} |
|||
open={openFilter} |
|||
onOpenChange={(open) => { |
|||
setFilterOpen(open); |
|||
}} |
|||
layout={"vertical"} |
|||
scrollToFirstError={true} |
|||
layoutType={"DrawerForm"} |
|||
drawerProps={{ |
|||
maskClosable: false, |
|||
mask: false, |
|||
}} |
|||
submitter={{ |
|||
searchConfig: { |
|||
resetText: t(`${i18nPrefix}.filter.reset`, "清空"), |
|||
submitText: t(`${i18nPrefix}.filter.submit`, "查询"), |
|||
}, |
|||
onReset: () => { |
|||
filterForm.resetFields(); |
|||
}, |
|||
render: (props) => { |
|||
return ( |
|||
<div style={{ textAlign: "right" }}> |
|||
<Space> |
|||
<Button |
|||
onClick={() => { |
|||
props.reset(); |
|||
}} |
|||
|
|||
|
|||
onFinish={async (values) => { |
|||
//处理,变成数组
|
|||
Object.keys(values).forEach(key => { |
|||
if (typeof values[key] === 'string' && values[key].includes(',')) { |
|||
values[key] = values[key].split(',') |
|||
} |
|||
}) |
|||
|
|||
setSearch(values) |
|||
|
|||
> |
|||
{props.searchConfig?.resetText} |
|||
</Button> |
|||
<Button |
|||
type="primary" |
|||
onClick={() => { |
|||
props.submit(); |
|||
}} |
|||
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch) as ProFormColumnsType[])}/> |
|||
{ |
|||
dialog |
|||
> |
|||
{props.searchConfig?.submitText} |
|||
</Button> |
|||
</Space> |
|||
</div> |
|||
); |
|||
}, |
|||
}} |
|||
onFinish={async (values) => { |
|||
//处理,变成数组
|
|||
Object.keys(values).forEach((key) => { |
|||
if (typeof values[key] === "string" && values[key].includes(",")) { |
|||
values[key] = values[key].split(","); |
|||
} |
|||
</ListPageLayout> |
|||
) |
|||
} |
|||
}); |
|||
|
|||
setSearch(values); |
|||
}} |
|||
columns={unSetColumnRules(columns.filter((item) => !item.hideInSearch) as ProFormColumnsType[])} |
|||
/> |
|||
{dialog} |
|||
</ListPageLayout> |
|||
); |
|||
}; |
|||
|
|||
export default WebsiteDomain |
|||
export default WebsiteDomain; |
@ -0,0 +1,52 @@ |
|||
import React, { useMemo } from 'react'; |
|||
import { ProTable } from '@ant-design/pro-components'; |
|||
import type { ProColumns } from '@ant-design/pro-components'; |
|||
|
|||
type DataType = { |
|||
key: number; |
|||
name: string; |
|||
age: number; |
|||
address: string; |
|||
}; |
|||
|
|||
const data: DataType[] = [ |
|||
{ key: 1, name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park' }, |
|||
{ key: 2, name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park' }, |
|||
{ key: 3, name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park' }, |
|||
]; |
|||
|
|||
const MyTable: React.FC = () => { |
|||
const columns: ProColumns<DataType>[] = useMemo(() => [ |
|||
{ |
|||
title: 'Name', |
|||
dataIndex: 'name', |
|||
key: 'name', |
|||
}, |
|||
{ |
|||
title: 'Age', |
|||
dataIndex: 'age', |
|||
key: 'age', |
|||
}, |
|||
{ |
|||
title: 'Address', |
|||
dataIndex: 'address', |
|||
key: 'address', |
|||
}, |
|||
], []); |
|||
|
|||
const memoizedData = useMemo(() => data, []); |
|||
|
|||
return ( |
|||
<ProTable<DataType> |
|||
columns={columns} |
|||
dataSource={memoizedData} |
|||
rowKey="key" |
|||
search={false} |
|||
pagination={{ |
|||
pageSize: 5, |
|||
}} |
|||
/> |
|||
); |
|||
}; |
|||
|
|||
export default MyTable; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue