12 changed files with 625 additions and 10 deletions
			
			
		- 
					4src/App.css
 - 
					6src/components/action/Action.tsx
 - 
					5src/components/modal-pro/index.tsx
 - 
					5src/components/table/style.ts
 - 
					BINsrc/pages/websites/cert/assets/google.png
 - 
					BINsrc/pages/websites/cert/assets/lets_encrypt.png
 - 
					BINsrc/pages/websites/cert/assets/zerossl.png
 - 
					394src/pages/websites/cert/index.tsx
 - 
					26src/pages/websites/cert/style.ts
 - 
					68src/service/websites.ts
 - 
					107src/store/websites/cert.ts
 - 
					20src/types/website/cert.d.ts
 
| 
		 After Width: 200 | Height: 67 | Size: 8.4 KiB  | 
| 
		 After Width: 339 | Height: 81 | Size: 3.4 KiB  | 
| 
		 After Width: 373 | Height: 95 | Size: 2.8 KiB  | 
@ -0,0 +1,394 @@ | 
				
			|||
import { useTranslation } from '@/i18n.ts' | 
				
			|||
import { Button, Form, Popconfirm, Divider, Space, Tooltip, Tag, Input, Progress, Flex } from 'antd' | 
				
			|||
import { useAtom, useAtomValue } from 'jotai' | 
				
			|||
import { | 
				
			|||
  algorithmTypes, | 
				
			|||
  bandTypes, | 
				
			|||
  certAtom, certsAtom, | 
				
			|||
  certSearchAtom, | 
				
			|||
  deleteCertAtom, | 
				
			|||
  saveOrUpdateCertAtom, StatusText, | 
				
			|||
} from '@/store/websites/cert' | 
				
			|||
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 { CheckCircleFilled, ExclamationCircleFilled, } from '@ant-design/icons' | 
				
			|||
import { Table as ProTable } from '@/components/table' | 
				
			|||
import google from './assets/google.png' | 
				
			|||
import lets_encrypt from './assets/lets_encrypt.png' | 
				
			|||
import zerossl from './assets/zerossl.png' | 
				
			|||
import ModalPro from '@/components/modal-pro' | 
				
			|||
 | 
				
			|||
const i18nPrefix = 'cert.list' | 
				
			|||
 | 
				
			|||
const Cert = () => { | 
				
			|||
 | 
				
			|||
  const { styles, cx } = useStyle() | 
				
			|||
  const { t } = useTranslation() | 
				
			|||
  const [ form ] = Form.useForm() | 
				
			|||
  const [ filterForm ] = Form.useForm() | 
				
			|||
  const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateCertAtom) | 
				
			|||
  const [ search, setSearch ] = useAtom(certSearchAtom) | 
				
			|||
  const [ currentCert, setCert ] = useAtom(certAtom) | 
				
			|||
  const { data, isFetching, isLoading, refetch } = useAtomValue(certsAtom) | 
				
			|||
  const { mutate: deleteCert, isPending: isDeleting } = useAtomValue(deleteCertAtom) | 
				
			|||
 | 
				
			|||
  const [ open, setOpen ] = useState(false) | 
				
			|||
  const [ openFilter, setFilterOpen ] = useState(false) | 
				
			|||
  const [ searchKey, setSearchKey ] = useState(search?.name) | 
				
			|||
 | 
				
			|||
  const columns = useMemo(() => { | 
				
			|||
    return [ | 
				
			|||
      { | 
				
			|||
        title: 'ID', | 
				
			|||
        dataIndex: 'id', | 
				
			|||
        hideInTable: true, | 
				
			|||
        hideInSearch: true, | 
				
			|||
        formItemProps: { hidden: true } | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.domains`, '域名'), | 
				
			|||
        dataIndex: 'domains', | 
				
			|||
        width: 350, | 
				
			|||
        fieldProps: { | 
				
			|||
          style: { width: '100%' }, | 
				
			|||
        }, | 
				
			|||
        formItemProps: { | 
				
			|||
          rules: [ { required: true, message: t(`${i18nPrefix}.columns.domains.required`, '请输入域名') } ] | 
				
			|||
        }, | 
				
			|||
        renderFormItem: (_schema, config) => { | 
				
			|||
          return <Input.TextArea {...config} rows={10} placeholder={`请输入域名,每行一个,支持泛解析域名;如:
 | 
				
			|||
    *.google.com | 
				
			|||
    *.a.baidu.com | 
				
			|||
    hello.alibaba.com`}/>
 | 
				
			|||
        } | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.type`, '域名验证'), | 
				
			|||
        dataIndex: 'type', | 
				
			|||
        hideInSearch: true, | 
				
			|||
        align: 'center', | 
				
			|||
        render: (_text, record) => { | 
				
			|||
          if (record.type === 4) | 
				
			|||
            return <><CheckCircleFilled className={'color-green'}/></> | 
				
			|||
          return <ExclamationCircleFilled className={'color-red'}/> | 
				
			|||
        } | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.brand`, '证书品牌'), | 
				
			|||
        dataIndex: 'brand', | 
				
			|||
        valueType: 'select', | 
				
			|||
        width: 100, | 
				
			|||
        fieldProps: { | 
				
			|||
          style: { width: '100%' }, | 
				
			|||
          options: bandTypes | 
				
			|||
        }, | 
				
			|||
        render: (_text, record) => { | 
				
			|||
 | 
				
			|||
          if (record.brand === 'Google') | 
				
			|||
            return <img src={google} style={{ height: '1rem' }}/> | 
				
			|||
          if (record.brand === 'ZeroSSL') | 
				
			|||
            return <img src={zerossl} style={{ height: '1rem' }}/> | 
				
			|||
          return <img src={lets_encrypt} style={{ height: '1rem' }}/> | 
				
			|||
        }, | 
				
			|||
        formItemProps: { | 
				
			|||
          rules: [ { required: true, message: t(`${i18nPrefix}.columns.brand.required`, '请选择证书品牌') } ] | 
				
			|||
        } | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.createTime`, '有效期(天)'), | 
				
			|||
        dataIndex: 'createTime', | 
				
			|||
        hideInSearch: true, | 
				
			|||
        hideInForm: true, | 
				
			|||
        width: 220, | 
				
			|||
        render: (_text, record) => { | 
				
			|||
 | 
				
			|||
          const content = () => { | 
				
			|||
 | 
				
			|||
            if (record.lifeTime) | 
				
			|||
              return <Progress size={'small'} percent={record.remainingTime} format={() => { | 
				
			|||
                return `${record.remainingTime}/${record.lifeTime}` | 
				
			|||
              }}>{record.expire}</Progress> | 
				
			|||
            return <Progress size={'small'} format={() => ``}/> | 
				
			|||
          } | 
				
			|||
          return <Flex style={{ width: 180 }}> | 
				
			|||
            <Tooltip | 
				
			|||
                    placement={'right'} | 
				
			|||
                    title={<div style={{ fontSize: 12 }}> | 
				
			|||
                      <div>生效时间:{record.notBefore}</div> | 
				
			|||
                      <div>失效时间:{record.notAfter}</div> | 
				
			|||
                      <div>创建时间:{record.createTime}</div> | 
				
			|||
                    </div>} | 
				
			|||
            > | 
				
			|||
              {content()} | 
				
			|||
            </Tooltip> | 
				
			|||
          </Flex> | 
				
			|||
        } | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.algorithm`, '加密方式'), | 
				
			|||
        dataIndex: 'algorithm', | 
				
			|||
        valueType: 'select', | 
				
			|||
        width: 100, | 
				
			|||
        fieldProps: { | 
				
			|||
          style: { width: '100%' }, | 
				
			|||
          options: algorithmTypes, | 
				
			|||
        }, | 
				
			|||
        render: (text, record) => { | 
				
			|||
          if (record.algorithm === 'ECC') | 
				
			|||
            return <Tag color={'green'}>{text}</Tag> | 
				
			|||
          return <Tag color={'processing'}>{text}</Tag> | 
				
			|||
        }, | 
				
			|||
        formItemProps: { | 
				
			|||
          rules: [ { required: true, message: t(`${i18nPrefix}.columns.algorithm.required`, '请选择加密方式') } ] | 
				
			|||
        } | 
				
			|||
      }, | 
				
			|||
 | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.status`, '状态'), | 
				
			|||
        dataIndex: 'status', | 
				
			|||
        width: 100, | 
				
			|||
        hideInSearch: true, | 
				
			|||
        hideInForm: true, | 
				
			|||
        render: (_text, record) => { | 
				
			|||
          const [ text, color ] = StatusText[record.status] | 
				
			|||
          return <Tag color={color}>{text} </Tag> | 
				
			|||
        } | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.remark`, '备注 '), | 
				
			|||
        dataIndex: 'remark', | 
				
			|||
      }, | 
				
			|||
      { | 
				
			|||
        title: t(`${i18nPrefix}.columns.option`, '操作'), | 
				
			|||
        key: 'option', | 
				
			|||
        width: 150, | 
				
			|||
        valueType: 'option', | 
				
			|||
        fixed: 'right', | 
				
			|||
        render: (_, record) => [ | 
				
			|||
          <Action key="edit" | 
				
			|||
                  as={'a'} | 
				
			|||
                  onClick={() => { | 
				
			|||
                    form.setFieldsValue(record) | 
				
			|||
                    setOpen(true) | 
				
			|||
                  }}>{t('actions.edit')}</Action>, | 
				
			|||
          <Divider type={'vertical'}/>, | 
				
			|||
          <ModalPro alterType={'dialog'} | 
				
			|||
                    title={t(`${i18nPrefix}.download`, '下载证书')} | 
				
			|||
                    disabled={record.status === 3}> | 
				
			|||
            <Action as={'a'} | 
				
			|||
                    disabled={record.status === 3} key={'download'}>{t(`actions.download`, '下载')}</Action> | 
				
			|||
          </ModalPro>, | 
				
			|||
          <Divider type={'vertical'}/>, | 
				
			|||
          <Popconfirm | 
				
			|||
                  key={'del_confirm'} | 
				
			|||
                  disabled={isDeleting} | 
				
			|||
                  onConfirm={() => { | 
				
			|||
                    deleteCert([ record.id ]) | 
				
			|||
                  }} | 
				
			|||
                  title={t('message.deleteConfirm')}> | 
				
			|||
            <a key="del"> | 
				
			|||
              {t('actions.delete', '删除')} | 
				
			|||
            </a> | 
				
			|||
          </Popconfirm> | 
				
			|||
        ] | 
				
			|||
      } | 
				
			|||
    ] as ProColumns[] | 
				
			|||
  }, [ isDeleting, currentCert, search ]) | 
				
			|||
 | 
				
			|||
  useEffect(() => { | 
				
			|||
 | 
				
			|||
    setSearchKey(search?.name) | 
				
			|||
    filterForm.setFieldsValue(search) | 
				
			|||
 | 
				
			|||
  }, [ search ]) | 
				
			|||
 | 
				
			|||
  useEffect(() => { | 
				
			|||
    if (isSuccess) { | 
				
			|||
      setOpen(false) | 
				
			|||
    } | 
				
			|||
  }, [ isSuccess ]) | 
				
			|||
 | 
				
			|||
  return ( | 
				
			|||
          <ListPageLayout className={styles.container}> | 
				
			|||
            <ProTable | 
				
			|||
                    rowKey="id" | 
				
			|||
                    headerTitle={ | 
				
			|||
                      <Button key={'add'} | 
				
			|||
                              onClick={() => { | 
				
			|||
                                form.resetFields() | 
				
			|||
                                form.setFieldsValue({ | 
				
			|||
                                  id: 0, | 
				
			|||
                                }) | 
				
			|||
                                setOpen(true) | 
				
			|||
                              }} | 
				
			|||
                              type={'primary'}>{t(`${i18nPrefix}.add`, '免费申请证明')}</Button> | 
				
			|||
 | 
				
			|||
                    } | 
				
			|||
                    toolbar={{ | 
				
			|||
                      search: { | 
				
			|||
                        loading: isFetching && !!search?.name, | 
				
			|||
                        onSearch: (value: string) => { | 
				
			|||
                          setSearch(prev => ({ | 
				
			|||
                            ...prev, | 
				
			|||
                            name: 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: 1100, y: 'calc(100vh - 290px)' | 
				
			|||
                    }} | 
				
			|||
                    search={false} | 
				
			|||
                    onRow={(record) => { | 
				
			|||
                      return { | 
				
			|||
                        className: cx({ | 
				
			|||
                          // 'ant-table-row-selected': currentCert?.id === record.id
 | 
				
			|||
                        }), | 
				
			|||
                        onClick: () => { | 
				
			|||
                          setCert(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, | 
				
			|||
                          } | 
				
			|||
                        }) | 
				
			|||
                      }, | 
				
			|||
                    }} | 
				
			|||
            /> | 
				
			|||
            <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) => { | 
				
			|||
                      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> | 
				
			|||
                        ) | 
				
			|||
                      }, | 
				
			|||
 | 
				
			|||
                    }} | 
				
			|||
                    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) | 
				
			|||
 | 
				
			|||
                    }} | 
				
			|||
                    columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> | 
				
			|||
          </ListPageLayout> | 
				
			|||
  ) | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export default Cert | 
				
			|||
@ -0,0 +1,26 @@ | 
				
			|||
import { createStyles } from '@/theme' | 
				
			|||
 | 
				
			|||
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { | 
				
			|||
    const prefix = `${prefixCls}-${token?.proPrefix}-domainGroup-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), | 
				
			|||
    } | 
				
			|||
}) | 
				
			|||
@ -0,0 +1,107 @@ | 
				
			|||
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.ts' | 
				
			|||
 | 
				
			|||
 | 
				
			|||
type SearchParams = IPage & { | 
				
			|||
  name?: string | 
				
			|||
} | 
				
			|||
 | 
				
			|||
export const bandTypes = [ | 
				
			|||
  { label: 'Google', value: 'Google' }, | 
				
			|||
  { label: 'ZeroSSL', value: 'ZeroSSL' }, | 
				
			|||
  { label: 'Let\'s Encrypt', value: 'Let\'s Encrypt' }, | 
				
			|||
] | 
				
			|||
 | 
				
			|||
export const algorithmTypes = [ | 
				
			|||
  { label: 'RSA', value: 'RSA' }, | 
				
			|||
  { label: 'ECC', value: 'ECC' }, | 
				
			|||
] | 
				
			|||
 | 
				
			|||
 | 
				
			|||
export const StatusText = { | 
				
			|||
  1: [ '已签发', 'green' ], | 
				
			|||
  2: [ '申请中', 'default' ], | 
				
			|||
  3: [ '申请失败', 'red' ] | 
				
			|||
} | 
				
			|||
 | 
				
			|||
 | 
				
			|||
export const certIdAtom = atom(0) | 
				
			|||
 | 
				
			|||
export const certIdsAtom = atom<number[]>([]) | 
				
			|||
 | 
				
			|||
export const certAtom = atom<ICertificate>(undefined as unknown as ICertificate) | 
				
			|||
 | 
				
			|||
export const certSearchAtom = atom<SearchParams>({ | 
				
			|||
  // key: '',
 | 
				
			|||
  pageSize: 10, | 
				
			|||
  page: 1, | 
				
			|||
} as SearchParams) | 
				
			|||
 | 
				
			|||
export const certPageAtom = atom<IPage>({ | 
				
			|||
  pageSize: 10, | 
				
			|||
  page: 1, | 
				
			|||
}) | 
				
			|||
 | 
				
			|||
export const certsAtom = atomWithQuery((get) => { | 
				
			|||
  return { | 
				
			|||
    queryKey: [ 'certs', get(certSearchAtom) ], | 
				
			|||
    queryFn: async ({ queryKey: [ , params ] }) => { | 
				
			|||
      return await websitesServ.cert.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 saveOrUpdateCertAtom = atomWithMutation<IApiResult, ICertificate>((get) => { | 
				
			|||
 | 
				
			|||
  return { | 
				
			|||
    mutationKey: [ 'updateCert' ], | 
				
			|||
    mutationFn: async (data) => { | 
				
			|||
      //data.status = data.status ? '1' : '0'
 | 
				
			|||
      if (data.id === 0) { | 
				
			|||
        return await websitesServ.cert.add(data) | 
				
			|||
      } | 
				
			|||
      return await websitesServ.cert.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: [ 'certs', get(certSearchAtom) ] }) | 
				
			|||
 | 
				
			|||
      return res | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
}) | 
				
			|||
 | 
				
			|||
export const deleteCertAtom = atomWithMutation((get) => { | 
				
			|||
  return { | 
				
			|||
    mutationKey: [ 'deleteCert' ], | 
				
			|||
    mutationFn: async (ids: number[]) => { | 
				
			|||
      return await websitesServ.cert.batchDelete(ids ?? get(certIdsAtom)) | 
				
			|||
    }, | 
				
			|||
    onSuccess: (res) => { | 
				
			|||
      message.success('message.deleteSuccess') | 
				
			|||
      //更新列表
 | 
				
			|||
      get(queryClientAtom).invalidateQueries({ queryKey: [ 'certs', get(certSearchAtom) ] }) | 
				
			|||
      return res | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
}) | 
				
			|||
@ -0,0 +1,20 @@ | 
				
			|||
interface ICertificate { | 
				
			|||
  id: number; | 
				
			|||
  brand: string; | 
				
			|||
  domains: string; | 
				
			|||
  type: number; | 
				
			|||
  level: number; | 
				
			|||
  status: number; | 
				
			|||
  createTime: string; | 
				
			|||
  notAfter: string; | 
				
			|||
  notBefore: string; | 
				
			|||
  version: number; | 
				
			|||
  sigAlgName: string; | 
				
			|||
  remark: string; | 
				
			|||
  name: string; | 
				
			|||
  lifeTime: number; | 
				
			|||
  remainingTime: number; | 
				
			|||
  algorithm: string; | 
				
			|||
  bit: number; | 
				
			|||
  fromType: number; | 
				
			|||
} | 
				
			|||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue