Complete main UI #37

Merged
FatttSnake merged 192 commits from FatttSnake into dev 2024-02-23 16:31:17 +08:00
7 changed files with 178 additions and 64 deletions
Showing only changes of commit 00887e2e2e - Show all commits

View File

@@ -1,10 +1,8 @@
.loading-mask {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
z-index: 100;
background-color: rgba(200, 200, 200, 0.2);
}

View File

@@ -22,11 +22,8 @@
font-size: 1.2em;
}
.bt {
:nth-child(n+3) {
flex: 0 0 auto;
}
.bt-reset {
color: constants.$font-main-color;
}

1
src/assets/svg/test.svg Normal file
View 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

View 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
View File

@@ -295,22 +295,30 @@ interface AvatarBase64Vo {
base64: string
}
interface SystemSettingVo {
interface SystemSettingsVo {
mail: MailSettingsVo
}
interface MailSettingsVo {
host?: string
port?: number
securityType?: string
username?: string
password?: string
from?: string
fromName?: string
}
interface MailSettingsParam {
host?: string
port?: number
securityType?: string
username?: string
password?: string
from?: string
fromName?: string
}
interface MailSendParam {
to: string
}

View File

@@ -1,16 +1,23 @@
import React from 'react'
import Icon from '@ant-design/icons'
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 HideScrollbar from '@/components/common/HideScrollbar'
import Card from '@/components/common/Card'
import FlexBox from '@/components/common/FlexBox'
import { useUpdatedEffect } from '@/util/hooks.tsx'
import { r_sys_settings_get, r_sys_settings_mail_update } from '@/services/system.tsx'
import LoadingMask from '@/components/common/LoadingMask'
interface SettingsCardProps extends React.PropsWithChildren {
icon: IconComponent
title: string
loading?: boolean
expand?: React.ReactNode
onReset?: () => void
onSave?: () => void
}
@@ -21,25 +28,81 @@ const SettingsCard: React.FC<SettingsCardProps> = (props) => {
<FlexBox direction={'horizontal'} className={'head'}>
<Icon component={props.icon} className={'icon'} />
<div className={'title'}>{props.title}</div>
<AntdButton className={'bt bt-reset'} onClick={props.onReset}>
{!props.loading ? (
<>
{props.expand}
<AntdButton onClick={props.onReset} title={'重置'}>
<Icon component={IconFatwebBack} />
</AntdButton>
<AntdButton className={'bt bt-save'} onClick={props.onSave}>
<AntdButton className={'bt-save'} onClick={props.onSave} title={'保存'}>
<Icon component={IconFatwebSave} />
</AntdButton>
</>
) : undefined}
</FlexBox>
{props.children}
<LoadingMask hidden={!props.loading}>{props.children}</LoadingMask>
</FlexBox>
</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 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 = () => {
props.mail && mailForm.setFieldsValue(props.mail)
getMailSettings()
}
const handleOnSave = () => {
@@ -47,23 +110,46 @@ const MailSettings: React.FC<{ mail?: MailSettingsVo; onSave?: () => void }> = (
const response = res.data
if (response.success) {
void message.success('保存设置成功')
props.onSave && props.onSave()
getMailSettings()
} else {
void message.error('保存设置失败,请稍后重试')
}
})
}
const getMailSettings = () => {
if (loading) {
return
}
setLoading(true)
void r_sys_settings_mail_get().then((res) => {
const response = res.data
if (response.success) {
const data = response.data
data && mailForm.setFieldsValue(data)
setLoading(false)
}
})
}
useUpdatedEffect(() => {
props.mail && mailForm.setFieldsValue(props.mail)
}, [props.mail])
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'}>
@@ -72,6 +158,13 @@ const MailSettings: React.FC<{ mail?: MailSettingsVo; onSave?: () => void }> = (
<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>
@@ -81,38 +174,23 @@ const MailSettings: React.FC<{ mail?: MailSettingsVo; onSave?: () => void }> = (
<AntdForm.Item label={'发送者'} name={'from'}>
<AntdInput />
</AntdForm.Item>
<AntdForm.Item label={'发送者名称'} name={'fromName'}>
<AntdInput />
</AntdForm.Item>
</AntdForm>
</SettingsCard>
{contextHolder}
</>
)
}
const Settings: React.FC = () => {
const [systemSetting, setSystemSetting] = useState<SystemSettingVo>()
const getSystemSetting = () => {
void r_sys_settings_get().then((res) => {
const response = res.data
if (response.success) {
const data = response.data
data && setSystemSetting(data)
}
})
}
const handleOnSave = () => {
getSystemSetting()
}
useUpdatedEffect(() => {
getSystemSetting()
}, [])
return (
<>
<FitFullScreen>
<HideScrollbar isShowVerticalScrollbar autoHideWaitingTime={500}>
<FlexBox className={'root-content'}>
<MailSettings mail={systemSetting?.mail} onSave={handleOnSave} />
<MailSettings />
</FlexBox>
</HideScrollbar>
</FitFullScreen>

View File

@@ -8,7 +8,6 @@ import {
URL_SYS_GROUP,
URL_SYS_GROUP_LIST,
URL_SYS_LOG,
URL_SYS_SETTINGS,
URL_SYS_SETTINGS_MAIL
} from '@/constants/urls.constants'
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) =>
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) =>
request.put(URL_SYS_SETTINGS_MAIL, param)
export const r_sys_settings_mail_send = (param: MailSendParam) =>
request.post(URL_SYS_SETTINGS_MAIL, param)