diff --git a/src/pages/websites/ssl/record/index.tsx b/src/pages/websites/ssl/record/index.tsx new file mode 100644 index 0000000..13c2d10 --- /dev/null +++ b/src/pages/websites/ssl/record/index.tsx @@ -0,0 +1,318 @@ +import { useTranslation } from '@/i18n.ts' +import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' +import { useAtom, useAtomValue } from 'jotai' +import { + deleteWebsiteDnsRecordsAtom, + saveOrUpdateWebsiteDnsRecordsAtom, websiteDnsRecordsAtom, websiteDnsRecordssAtom, websiteDnsRecordsSearchAtom, +} from '@/store/websites/record' +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 } from '@/utils' +import { Table as ProTable } from '@/components/table' + +const i18nPrefix = 'websiteDnsRecordss.list' + +const WebsiteDnsRecords = () => { + + const { styles, cx } = useStyle() + const { t } = useTranslation() + const [ form ] = Form.useForm() + const [ filterForm ] = Form.useForm() + const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateWebsiteDnsRecordsAtom) + const [ search, setSearch ] = useAtom(websiteDnsRecordsSearchAtom) + const [ currentWebsiteDnsRecords, setWebsiteDnsRecords ] = useAtom(websiteDnsRecordsAtom) + const { data, isFetching, isLoading, refetch } = useAtomValue(websiteDnsRecordssAtom) + const { mutate: deleteWebsiteDnsRecords, isPending: isDeleting } = useAtomValue(deleteWebsiteDnsRecordsAtom) + + const [ open, setOpen ] = useState(false) + const [ openFilter, setFilterOpen ] = useState(false) + const [ searchKey, setSearchKey ] = useState(search?.title) + + const columns = useMemo(() => { + return [ + { + title: 'ID', + dataIndex: 'id', + hideInTable: true, + 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.option`, '操作'), + key: 'option', + valueType: 'option', + fixed: 'right', + render: (_, record) => [ + { + form.setFieldsValue(record) + setOpen(true) + }}>{t('actions.edit')}, + { + deleteWebsiteDnsRecords([ record.id ]) + }} + title={t('message.deleteConfirm')}> + + {t('actions.delete', '删除')} + + + ] + } + ] as ProColumns[] + }, [ isDeleting, currentWebsiteDnsRecords, search ]) + + useEffect(() => { + + setSearchKey(search?.title) + filterForm.setFieldsValue(search) + + }, [ search ]) + + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [ isSuccess ]) + + return ( + + { + setSearch(prev => ({ + ...prev, + title: value + })) + }, + allowClear: true, + onChange: (e) => { + setSearchKey(e.target?.value) + }, + value: searchKey, + placeholder: t(`${i18nPrefix}.placeholder`, '输入记录管理名称') + }, + actions: [ + + + + ] + }} + scroll={{ + x: 2500, y: 'calc(100vh - 290px)' + }} + search={false} + onRow={(record) => { + return { + className: cx({ + 'ant-table-row-selected': currentWebsiteDnsRecords?.id === record.id + }), + onClick: () => { + setWebsiteDnsRecords(record) + } + } + }} + 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, + } + }) + }, + }} + /> + { + setOpen(open) + }} + loading={isSubmitting} + onFinish={async (values) => { + saveOrUpdate(values) + }} + columns={columns as ProFormColumnsType[]}/> + { + 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 ( +
+ + + + +
+ ) + }, + + }} + onFinish={async (values) => { + //处理,变成数组 + Object.keys(values).forEach(key => { + if (typeof values[key] === 'string' && values[key].includes(',')) { + values[key] = values[key].split(',') + } + }) + + setSearch(values) + + }} + columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> +
+ ) +} + +export default WebsiteDnsRecords \ No newline at end of file diff --git a/src/pages/websites/ssl/record/style.ts b/src/pages/websites/ssl/record/style.ts new file mode 100644 index 0000000..99188e0 --- /dev/null +++ b/src/pages/websites/ssl/record/style.ts @@ -0,0 +1,26 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-websiteDnsRecords-list-page` + + const container = css` + .ant-table-cell{ + .ant-tag{ + padding-inline: 3px; + margin-inline-end: 3px; + } + } + .ant-table-empty { + .ant-table-body{ + height: calc(100vh - 350px) + } + } + .ant-pro-table-highlight{ + + } + ` + + return { + container: cx(prefix, props?.className, container), + } +}) \ No newline at end of file diff --git a/src/service/websites.ts b/src/service/websites.ts index 5b68484..d715623 100644 --- a/src/service/websites.ts +++ b/src/service/websites.ts @@ -30,6 +30,9 @@ const websitesServ = { }, domain: { ...createCURD('/website/domain'), + }, + record: { + ...createCURD('/website/record'), } } diff --git a/src/store/websites/domain.ts b/src/store/websites/domain.ts index d2da2cb..467372c 100644 --- a/src/store/websites/domain.ts +++ b/src/store/websites/domain.ts @@ -4,8 +4,7 @@ import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack import { message } from 'antd' import { t } from 'i18next' import websitesServ from '@/service/websites.ts' - -import { Websites } from '@/types/website/domain' +import { IWebsiteDomain } from '@/types/website/domain' type SearchParams = IPage & { key?: string @@ -17,7 +16,7 @@ export const websiteDomainIdAtom = atom(0) export const websiteDomainIdsAtom = atom([]) -export const websiteDomainAtom = atom(undefined as unknown as Websites.IWebsiteDomain ) +export const websiteDomainAtom = atom(undefined as unknown as IWebsiteDomain ) export const websiteDomainSearchAtom = atom({ key: '', @@ -51,7 +50,7 @@ export const websiteDomainsAtom = atomWithQuery((get) => { }) //saveOrUpdateAtom -export const saveOrUpdateWebsiteDomainAtom = atomWithMutation((get) => { +export const saveOrUpdateWebsiteDomainAtom = atomWithMutation((get) => { return { mutationKey: [ 'updateWebsiteDomain' ], diff --git a/src/store/websites/record.ts b/src/store/websites/record.ts new file mode 100644 index 0000000..488fbb5 --- /dev/null +++ b/src/store/websites/record.ts @@ -0,0 +1,90 @@ +import { atom } from 'jotai' +import { IApiResult, IPage } from '@/global' +import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' +import { message } from 'antd' +import { t } from 'i18next' +import websitesServ from '@/service/websites' +import { IWebsiteDnsRecords } from '@/types/website/record' + +type SearchParams = IPage & { + key?: string + + [key: string]: any +} + +export const websiteDnsRecordsIdAtom = atom(0) + +export const websiteDnsRecordsIdsAtom = atom([]) + +export const websiteDnsRecordsAtom = atom(undefined as unknown as IWebsiteDnsRecords ) + +export const websiteDnsRecordsSearchAtom = atom({ + key: '', + pageSize: 10, + page: 1, +} as SearchParams) + +export const websiteDnsRecordsPageAtom = atom({ + pageSize: 10, + page: 1, +}) + +export const websiteDnsRecordssAtom = atomWithQuery((get) => { + return { + queryKey: [ 'websiteDnsRecordss', get(websiteDnsRecordsSearchAtom) ], + queryFn: async ({ queryKey: [ , params ] }) => { + return await websitesServ.record.list(params as SearchParams) + }, + select: res => { + const data = res.data + data.rows = data.rows?.map(row => { + return { + ...row, + //status: convertToBool(row.status) + } + }) + return data + } + } +}) + +//saveOrUpdateAtom +export const saveOrUpdateWebsiteDnsRecordsAtom = atomWithMutation((get) => { + + return { + mutationKey: [ 'updateWebsiteDnsRecords' ], + mutationFn: async (data) => { + //data.status = data.status ? '1' : '0' + if (data.id === 0) { + return await websitesServ.record.add(data) + } + return await websitesServ.record.update(data) + }, + onSuccess: (res) => { + const isAdd = !!res.data?.id + message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) + + //更新列表 + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore fix + get(queryClientAtom).invalidateQueries({ queryKey: [ 'websiteDnsRecordss', get(websiteDnsRecordsSearchAtom) ] }) + + return res + } + } +}) + +export const deleteWebsiteDnsRecordsAtom = atomWithMutation((get) => { + return { + mutationKey: [ 'deleteWebsiteDnsRecords' ], + mutationFn: async (ids: number[]) => { + return await websitesServ.record.batchDelete(ids ?? get(websiteDnsRecordsIdsAtom)) + }, + onSuccess: (res) => { + message.success('message.deleteSuccess') + //更新列表 + get(queryClientAtom).invalidateQueries({ queryKey: [ 'websiteDnsRecordss', get(websiteDnsRecordsSearchAtom) ] }) + return res + } + } +}) diff --git a/src/types/website/domain.d.ts b/src/types/website/domain.d.ts index 628a0ff..4f25376 100644 --- a/src/types/website/domain.d.ts +++ b/src/types/website/domain.d.ts @@ -1,14 +1,12 @@ -export namespace Websites { - export interface IWebsiteDomain { - id: number; - name: string; - dns_account_id: number; - soure_id: string; - status: string; - created: string; - modified: string; - nameservers: string; - tag: string; - remark: string; - } -} \ No newline at end of file +export interface IWebsiteDomain { + id: number; + name: string; + dns_account_id: number; + soure_id: string; + status: string; + created: string; + modified: string; + nameservers: string; + tag: string; + remark: string; +} diff --git a/src/types/website/record.d.ts b/src/types/website/record.d.ts new file mode 100644 index 0000000..294cbf2 --- /dev/null +++ b/src/types/website/record.d.ts @@ -0,0 +1,15 @@ +export interface IWebsiteDnsRecords { + id: number; + record_id: string; + domain_id: string; + name: string; + content: string; + poxy: number; + ttl: number; + type: string; + status: number; + tag: string; + remark: string; + created: string; + modified: string; +}