Complete main UI #37
@@ -1,10 +1,8 @@
|
|||||||
.loading-mask {
|
.loading-mask {
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background-color: rgba(200, 200, 200, 0.2);
|
|
||||||
}
|
}
|
||||||
@@ -22,11 +22,8 @@
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bt {
|
:nth-child(n+3) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
|
||||||
|
|
||||||
.bt-reset {
|
|
||||||
color: constants.$font-main-color;
|
color: constants.$font-main-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
src/assets/svg/test.svg
Normal file
1
src/assets/svg/test.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M725.333333 85.333333v85.333334h-42.666666v597.333333c0 94.293333-76.373333 170.666667-170.666667 170.666667s-170.666667-76.373333-170.666667-170.666667V170.666667H298.666667V85.333333h426.666666z m-170.666666 554.666667a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 0 0-85.333333z m-85.333334-128a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 0 0-85.333333z m128-341.333333h-170.666666v170.666666h170.666666V170.666667z" /></svg>
|
||||||
|
After Width: | Height: | Size: 535 B |
30
src/components/common/LoadingMask.tsx
Normal file
30
src/components/common/LoadingMask.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import '@/assets/css/components/common/loading-mask.scss'
|
||||||
|
import { COLOR_FONT_MAIN } from '@/constants/common.constants'
|
||||||
|
|
||||||
|
interface LoadingMaskProps extends React.PropsWithChildren {
|
||||||
|
hidden?: boolean
|
||||||
|
}
|
||||||
|
const LoadingMask: React.FC<LoadingMaskProps> = (props) => {
|
||||||
|
const loadingIcon = (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
component={IconFatwebLoading}
|
||||||
|
style={{ fontSize: 24, color: COLOR_FONT_MAIN }}
|
||||||
|
spin
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
return props.hidden ? (
|
||||||
|
props.children
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={'loading-mask'}>
|
||||||
|
<AntdSpin indicator={loadingIcon} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadingMask
|
||||||
10
src/global.d.ts
vendored
10
src/global.d.ts
vendored
@@ -295,22 +295,30 @@ interface AvatarBase64Vo {
|
|||||||
base64: string
|
base64: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SystemSettingVo {
|
interface SystemSettingsVo {
|
||||||
mail: MailSettingsVo
|
mail: MailSettingsVo
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MailSettingsVo {
|
interface MailSettingsVo {
|
||||||
host?: string
|
host?: string
|
||||||
port?: number
|
port?: number
|
||||||
|
securityType?: string
|
||||||
username?: string
|
username?: string
|
||||||
password?: string
|
password?: string
|
||||||
from?: string
|
from?: string
|
||||||
|
fromName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MailSettingsParam {
|
interface MailSettingsParam {
|
||||||
host?: string
|
host?: string
|
||||||
port?: number
|
port?: number
|
||||||
|
securityType?: string
|
||||||
username?: string
|
username?: string
|
||||||
password?: string
|
password?: string
|
||||||
from?: string
|
from?: string
|
||||||
|
fromName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MailSendParam {
|
||||||
|
to: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import '@/assets/css/pages/system/settings.scss'
|
import '@/assets/css/pages/system/settings.scss'
|
||||||
|
import { useUpdatedEffect } from '@/util/hooks'
|
||||||
|
import {
|
||||||
|
r_sys_settings_mail_get,
|
||||||
|
r_sys_settings_mail_send,
|
||||||
|
r_sys_settings_mail_update
|
||||||
|
} from '@/services/system'
|
||||||
import FitFullScreen from '@/components/common/FitFullScreen'
|
import FitFullScreen from '@/components/common/FitFullScreen'
|
||||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||||
import Card from '@/components/common/Card'
|
import Card from '@/components/common/Card'
|
||||||
import FlexBox from '@/components/common/FlexBox'
|
import FlexBox from '@/components/common/FlexBox'
|
||||||
import { useUpdatedEffect } from '@/util/hooks.tsx'
|
import LoadingMask from '@/components/common/LoadingMask'
|
||||||
import { r_sys_settings_get, r_sys_settings_mail_update } from '@/services/system.tsx'
|
|
||||||
|
|
||||||
interface SettingsCardProps extends React.PropsWithChildren {
|
interface SettingsCardProps extends React.PropsWithChildren {
|
||||||
icon: IconComponent
|
icon: IconComponent
|
||||||
title: string
|
title: string
|
||||||
|
loading?: boolean
|
||||||
|
expand?: React.ReactNode
|
||||||
onReset?: () => void
|
onReset?: () => void
|
||||||
onSave?: () => void
|
onSave?: () => void
|
||||||
}
|
}
|
||||||
@@ -21,25 +28,81 @@ const SettingsCard: React.FC<SettingsCardProps> = (props) => {
|
|||||||
<FlexBox direction={'horizontal'} className={'head'}>
|
<FlexBox direction={'horizontal'} className={'head'}>
|
||||||
<Icon component={props.icon} className={'icon'} />
|
<Icon component={props.icon} className={'icon'} />
|
||||||
<div className={'title'}>{props.title}</div>
|
<div className={'title'}>{props.title}</div>
|
||||||
<AntdButton className={'bt bt-reset'} onClick={props.onReset}>
|
{!props.loading ? (
|
||||||
<Icon component={IconFatwebBack} />
|
<>
|
||||||
</AntdButton>
|
{props.expand}
|
||||||
<AntdButton className={'bt bt-save'} onClick={props.onSave}>
|
<AntdButton onClick={props.onReset} title={'重置'}>
|
||||||
<Icon component={IconFatwebSave} />
|
<Icon component={IconFatwebBack} />
|
||||||
</AntdButton>
|
</AntdButton>
|
||||||
|
<AntdButton className={'bt-save'} onClick={props.onSave} title={'保存'}>
|
||||||
|
<Icon component={IconFatwebSave} />
|
||||||
|
</AntdButton>
|
||||||
|
</>
|
||||||
|
) : undefined}
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
{props.children}
|
<LoadingMask hidden={!props.loading}>{props.children}</LoadingMask>
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MailSettings: React.FC<{ mail?: MailSettingsVo; onSave?: () => void }> = (props) => {
|
const MailSettings: React.FC = () => {
|
||||||
|
const [modal, contextHolder] = AntdModal.useModal()
|
||||||
const [mailForm] = AntdForm.useForm<MailSettingsParam>()
|
const [mailForm] = AntdForm.useForm<MailSettingsParam>()
|
||||||
const mailFormValues = AntdForm.useWatch([], mailForm)
|
const mailFormValues = AntdForm.useWatch([], mailForm)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [mailSendForm] = AntdForm.useForm<MailSendParam>()
|
||||||
|
|
||||||
|
const handleOnTest = () => {
|
||||||
|
void modal.confirm({
|
||||||
|
title: '发送测试邮件',
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<AntdForm form={mailSendForm}>
|
||||||
|
<AntdForm.Item
|
||||||
|
name={'to'}
|
||||||
|
label={'接收人'}
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
rules={[{ required: true, type: 'email' }]}
|
||||||
|
>
|
||||||
|
<AntdInput />
|
||||||
|
</AntdForm.Item>
|
||||||
|
</AntdForm>
|
||||||
|
<AntdTag style={{ whiteSpace: 'normal' }}>
|
||||||
|
将使用服务器已保存的邮件设置进行发送邮件,请保证编辑的内容已保存
|
||||||
|
</AntdTag>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
onOk: () =>
|
||||||
|
mailSendForm.validateFields().then(
|
||||||
|
() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
void r_sys_settings_mail_send({
|
||||||
|
to: mailSendForm.getFieldValue('to') as string
|
||||||
|
}).then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
void message.success('发送成功')
|
||||||
|
resolve(true)
|
||||||
|
} else {
|
||||||
|
void message.error('发送失败,请检查配置后重试')
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
return new Promise((_, reject) => {
|
||||||
|
reject('未输入接收者')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleOnReset = () => {
|
const handleOnReset = () => {
|
||||||
props.mail && mailForm.setFieldsValue(props.mail)
|
getMailSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnSave = () => {
|
const handleOnSave = () => {
|
||||||
@@ -47,72 +110,87 @@ const MailSettings: React.FC<{ mail?: MailSettingsVo; onSave?: () => void }> = (
|
|||||||
const response = res.data
|
const response = res.data
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
void message.success('保存设置成功')
|
void message.success('保存设置成功')
|
||||||
props.onSave && props.onSave()
|
getMailSettings()
|
||||||
} else {
|
} else {
|
||||||
void message.error('保存设置失败,请稍后重试')
|
void message.error('保存设置失败,请稍后重试')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useUpdatedEffect(() => {
|
const getMailSettings = () => {
|
||||||
props.mail && mailForm.setFieldsValue(props.mail)
|
if (loading) {
|
||||||
}, [props.mail])
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
setLoading(true)
|
||||||
<SettingsCard
|
void r_sys_settings_mail_get().then((res) => {
|
||||||
icon={IconFatwebEmail}
|
|
||||||
title={'邮件'}
|
|
||||||
onReset={handleOnReset}
|
|
||||||
onSave={handleOnSave}
|
|
||||||
>
|
|
||||||
<AntdForm form={mailForm} labelCol={{ flex: '8em' }}>
|
|
||||||
<AntdForm.Item label={'SMTP 服务器'} name={'host'}>
|
|
||||||
<AntdInput />
|
|
||||||
</AntdForm.Item>
|
|
||||||
<AntdForm.Item label={'端口'} name={'port'}>
|
|
||||||
<AntdInputNumber min={0} max={65535} style={{ width: '100%' }} />
|
|
||||||
</AntdForm.Item>
|
|
||||||
<AntdForm.Item label={'用户名'} name={'username'}>
|
|
||||||
<AntdInput />
|
|
||||||
</AntdForm.Item>
|
|
||||||
<AntdForm.Item label={'密码'} name={'password'}>
|
|
||||||
<AntdInput.Password />
|
|
||||||
</AntdForm.Item>
|
|
||||||
<AntdForm.Item label={'发送者'} name={'from'}>
|
|
||||||
<AntdInput />
|
|
||||||
</AntdForm.Item>
|
|
||||||
</AntdForm>
|
|
||||||
</SettingsCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Settings: React.FC = () => {
|
|
||||||
const [systemSetting, setSystemSetting] = useState<SystemSettingVo>()
|
|
||||||
|
|
||||||
const getSystemSetting = () => {
|
|
||||||
void r_sys_settings_get().then((res) => {
|
|
||||||
const response = res.data
|
const response = res.data
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
const data = response.data
|
const data = response.data
|
||||||
data && setSystemSetting(data)
|
data && mailForm.setFieldsValue(data)
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnSave = () => {
|
|
||||||
getSystemSetting()
|
|
||||||
}
|
|
||||||
|
|
||||||
useUpdatedEffect(() => {
|
useUpdatedEffect(() => {
|
||||||
getSystemSetting()
|
getMailSettings()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsCard
|
||||||
|
icon={IconFatwebEmail}
|
||||||
|
title={'邮件'}
|
||||||
|
loading={loading}
|
||||||
|
onReset={handleOnReset}
|
||||||
|
onSave={handleOnSave}
|
||||||
|
expand={
|
||||||
|
<AntdButton onClick={handleOnTest} title={'测试'}>
|
||||||
|
<Icon component={IconFatwebTest} />
|
||||||
|
</AntdButton>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AntdForm form={mailForm} labelCol={{ flex: '8em' }}>
|
||||||
|
<AntdForm.Item label={'SMTP 服务器'} name={'host'}>
|
||||||
|
<AntdInput />
|
||||||
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item label={'端口'} name={'port'}>
|
||||||
|
<AntdInputNumber min={0} max={65535} style={{ width: '100%' }} />
|
||||||
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item label={'安全类型'} name={'securityType'}>
|
||||||
|
<AntdSelect>
|
||||||
|
<AntdSelect.Option key={'None'}>None</AntdSelect.Option>
|
||||||
|
<AntdSelect.Option key={'SSL/TLS'}>SSL/TLS</AntdSelect.Option>
|
||||||
|
<AntdSelect.Option key={'StartTls'}>StartTls</AntdSelect.Option>
|
||||||
|
</AntdSelect>
|
||||||
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item label={'用户名'} name={'username'}>
|
||||||
|
<AntdInput />
|
||||||
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item label={'密码'} name={'password'}>
|
||||||
|
<AntdInput.Password />
|
||||||
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item label={'发送者'} name={'from'}>
|
||||||
|
<AntdInput />
|
||||||
|
</AntdForm.Item>
|
||||||
|
<AntdForm.Item label={'发送者名称'} name={'fromName'}>
|
||||||
|
<AntdInput />
|
||||||
|
</AntdForm.Item>
|
||||||
|
</AntdForm>
|
||||||
|
</SettingsCard>
|
||||||
|
{contextHolder}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Settings: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FitFullScreen>
|
<FitFullScreen>
|
||||||
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={500}>
|
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={500}>
|
||||||
<FlexBox className={'root-content'}>
|
<FlexBox className={'root-content'}>
|
||||||
<MailSettings mail={systemSetting?.mail} onSave={handleOnSave} />
|
<MailSettings />
|
||||||
</FlexBox>
|
</FlexBox>
|
||||||
</HideScrollbar>
|
</HideScrollbar>
|
||||||
</FitFullScreen>
|
</FitFullScreen>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
URL_SYS_GROUP,
|
URL_SYS_GROUP,
|
||||||
URL_SYS_GROUP_LIST,
|
URL_SYS_GROUP_LIST,
|
||||||
URL_SYS_LOG,
|
URL_SYS_LOG,
|
||||||
URL_SYS_SETTINGS,
|
|
||||||
URL_SYS_SETTINGS_MAIL
|
URL_SYS_SETTINGS_MAIL
|
||||||
} from '@/constants/urls.constants'
|
} from '@/constants/urls.constants'
|
||||||
import request from '@/services/index'
|
import request from '@/services/index'
|
||||||
@@ -66,7 +65,10 @@ export const r_sys_group_delete_list = (ids: React.Key[]) => request.delete(URL_
|
|||||||
export const r_sys_log_get = (param: SysLogGetParam) =>
|
export const r_sys_log_get = (param: SysLogGetParam) =>
|
||||||
request.get<PageVo<SysLogGetVo>>(URL_SYS_LOG, param)
|
request.get<PageVo<SysLogGetVo>>(URL_SYS_LOG, param)
|
||||||
|
|
||||||
export const r_sys_settings_get = () => request.get<SystemSettingVo>(URL_SYS_SETTINGS)
|
export const r_sys_settings_mail_get = () => request.get<MailSettingsVo>(URL_SYS_SETTINGS_MAIL)
|
||||||
|
|
||||||
export const r_sys_settings_mail_update = (param: MailSettingsParam) =>
|
export const r_sys_settings_mail_update = (param: MailSettingsParam) =>
|
||||||
request.put(URL_SYS_SETTINGS_MAIL, param)
|
request.put(URL_SYS_SETTINGS_MAIL, param)
|
||||||
|
|
||||||
|
export const r_sys_settings_mail_send = (param: MailSendParam) =>
|
||||||
|
request.post(URL_SYS_SETTINGS_MAIL, param)
|
||||||
|
|||||||
Reference in New Issue
Block a user