218 lines
11 KiB
JavaScript
218 lines
11 KiB
JavaScript
import React, { useEffect, useState, useRef } from "react";
|
||
import { Switch, Alert, Typography, Form, Input, Radio, Tag, Tooltip, Flex } from 'antd';
|
||
import Styles from './web.module.less';
|
||
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
|
||
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
|
||
import { PlusOutlined, CloseOutlined } from '@ant-design/icons';
|
||
const { TextArea } = Input;
|
||
const { Text } = Typography;
|
||
function RenderEmailSuffixes(props) {
|
||
const { emailSuffixes, onChange, t } = props;
|
||
const [inputVisible, setInputVisible] = useState(false);
|
||
const [inputValue, setInputValue] = useState('');
|
||
const inputRef = useRef(null);
|
||
const tagInputStyle = {
|
||
width: 100,
|
||
height: 22,
|
||
marginInlineEnd: 8,
|
||
verticalAlign: 'top',
|
||
};
|
||
const tagPlusStyle = {
|
||
height: 22,
|
||
borderStyle: 'dashed',
|
||
};
|
||
const handleInputChange = (e) => {
|
||
setInputValue(e.target.value);
|
||
};
|
||
const handleInputConfirm = () => {
|
||
if (inputValue && !emailSuffixes?.includes(inputValue)) {
|
||
onChange([...emailSuffixes || [], inputValue]);
|
||
}
|
||
setInputVisible(false);
|
||
setInputValue('');
|
||
};
|
||
const handleClose = (removedTag) => {
|
||
const emailSuffixes2 = emailSuffixes.filter((tag) => tag !== removedTag);
|
||
onChange(emailSuffixes2);
|
||
};
|
||
const showInput = () => {
|
||
setInputVisible(true);
|
||
};
|
||
return (<Flex gap="4px 0" wrap="wrap">
|
||
{(emailSuffixes || []).map((tag, index) => {
|
||
const isLongTag = tag.length > 20;
|
||
const tagElem = (<Tag closeIcon={<CloseOutlined />} key={tag} onClose={() => handleClose(tag)}>
|
||
<span>
|
||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||
</span>
|
||
</Tag>);
|
||
return isLongTag ? (<Tooltip title={tag} key={tag}>
|
||
{tagElem}
|
||
</Tooltip>) : (tagElem);
|
||
})}
|
||
{inputVisible ? (<Input ref={inputRef} type="text" size="small" style={tagInputStyle} value={inputValue} onChange={handleInputChange} onBlur={handleInputConfirm} onPressEnter={handleInputConfirm}/>) : (<Tag style={tagPlusStyle} icon={<PlusOutlined />} onClick={showInput}>
|
||
{t('common::action.add')}
|
||
</Tag>)}
|
||
</Flex>);
|
||
}
|
||
export default function Email(props) {
|
||
const { passport, t, changeEnabled, updateConfig } = props;
|
||
const { id, type, enabled, stateColor } = passport;
|
||
const config = passport.config || {};
|
||
const [subject, setSubject] = useState(config?.subject || '');
|
||
const [eContentType, setEContentType] = useState('text');
|
||
const [htmlType, setHtmlType] = useState('html');
|
||
const [text, setText] = useState(config?.text || '');
|
||
const [html, setHtml] = useState(config?.html || '');
|
||
const [emailCodeDuration, setEmailCodeDuration] = useState(config?.codeDuration || '');
|
||
const [emailDigit, setEmailDigit] = useState(config?.digit || '');
|
||
const [emailSuffixes, setEmailSuffixes] = useState(config?.emailSuffixes || []);
|
||
// editor 实例
|
||
const [editor, setEditor] = useState(null); // TS 语法
|
||
// 工具栏配置
|
||
const toolbarConfig = {}; // TS 语法
|
||
// 编辑器配置
|
||
const editorConfig = {
|
||
autoFocus: false,
|
||
placeholder: '请输入内容...',
|
||
};
|
||
// 及时销毁 editor
|
||
useEffect(() => {
|
||
return () => {
|
||
if (editor == null)
|
||
return;
|
||
editor.destroy();
|
||
setEditor(null);
|
||
};
|
||
}, [editor]);
|
||
useEffect(() => {
|
||
setSubject(config?.subject || '');
|
||
setText(config?.text || '');
|
||
setHtml(config?.html || '');
|
||
setEmailCodeDuration(config?.codeDuration || '');
|
||
setEmailDigit(config?.digit || '');
|
||
setEmailSuffixes(config?.emailSuffixes || []);
|
||
if (config?.html) {
|
||
setEContentType('html');
|
||
}
|
||
else {
|
||
setEContentType('text');
|
||
}
|
||
}, [config]);
|
||
return (<div className={Styles.item}>
|
||
<div className={Styles.title}>
|
||
<Tag color={stateColor}>{t(`passport:v.type.${type}`)}</Tag>
|
||
<Switch checkedChildren="开启" unCheckedChildren="关闭" checked={enabled} onChange={(checked) => {
|
||
changeEnabled(checked);
|
||
}}/>
|
||
</div>
|
||
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} style={{ maxWidth: 900, marginTop: 16 }}>
|
||
<Form.Item label="账号">
|
||
<Input type="text" value={config.account} disabled={true}/>
|
||
</Form.Item>
|
||
</Form>
|
||
{enabled &&
|
||
<div>
|
||
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} style={{ maxWidth: 900, marginTop: 16 }}>
|
||
<Form.Item label="模拟发送" tooltip="开启模拟发送验证码,发邮件不会调用api">
|
||
<Switch checkedChildren="是" unCheckedChildren="否" checked={config?.mockSend} onChange={(checked) => {
|
||
updateConfig(id, config, 'mockSend', checked, 'email');
|
||
}}/>
|
||
</Form.Item>
|
||
<Form.Item label="邮件主题">
|
||
<Input placeholder="请输入邮件主题" type="text" value={subject} onChange={(e) => {
|
||
setSubject(e.target.value);
|
||
}} onBlur={() => {
|
||
if (subject !== config?.subject) {
|
||
updateConfig(id, config, 'subject', subject, 'email');
|
||
}
|
||
}}/>
|
||
</Form.Item>
|
||
<Form.Item label="邮件内容模板">
|
||
<>
|
||
<Radio.Group onChange={(e) => setEContentType(e.target.value)} value={eContentType} style={{ marginBottom: 12 }}>
|
||
<Radio.Button value="text">纯文本</Radio.Button>
|
||
<Radio.Button value="html">HTML</Radio.Button>
|
||
</Radio.Group>
|
||
<Alert message={<div>
|
||
<span>请使用</span>
|
||
<Text mark> ${'{code}'}</Text>
|
||
<span>作为验证码占位符,</span>
|
||
<Text mark> ${'{duration}'}</Text>
|
||
<span>作为验证码有效时间占位符(不包含单位分钟)。</span>
|
||
</div>} type="info" style={{ marginBottom: 12 }}/>
|
||
{eContentType === 'text' ? (<TextArea rows={8} value={text} onChange={(e) => {
|
||
setText(e.target.value);
|
||
}} onBlur={() => {
|
||
if (text !== config?.text) {
|
||
updateConfig(id, config, 'text', text, 'email');
|
||
}
|
||
}}/>) : (<>
|
||
<Radio.Group onChange={(e) => setHtmlType(e.target.value)} value={htmlType} style={{ marginBottom: 12 }}>
|
||
<Radio value="richText">富文本</Radio>
|
||
<Radio value="html">HTML</Radio>
|
||
</Radio.Group>
|
||
{htmlType === 'richText' ? (<div style={{ border: '1px solid #ccc' }}>
|
||
<Toolbar editor={editor} defaultConfig={toolbarConfig} mode="default" style={{ borderBottom: '1px solid #ccc' }}/>
|
||
<Editor defaultConfig={editorConfig} value={html} onCreated={setEditor} onChange={editor => {
|
||
setHtml(editor.getHtml());
|
||
updateConfig(id, config, 'html', editor.getHtml(), 'email');
|
||
}} mode="default" style={{ height: '260px', overflowY: 'hidden' }}/>
|
||
</div>) : (<TextArea rows={8} value={html} onChange={(e) => {
|
||
setHtml(e.target.value);
|
||
}} onBlur={() => {
|
||
if (html !== config?.html) {
|
||
updateConfig(id, config, 'html', html, 'email');
|
||
}
|
||
}}/>)}
|
||
</>)}
|
||
</>
|
||
</Form.Item>
|
||
<Form.Item label="验证码有效时间" tooltip="邮箱验证码发送有效时间,不填为5分钟">
|
||
<Input placeholder="请输入验证码有效时间" type="number" value={emailCodeDuration} min={0} onChange={(e) => {
|
||
const val = e.target.value;
|
||
if (val) {
|
||
setEmailCodeDuration(Number(val));
|
||
}
|
||
else {
|
||
setEmailCodeDuration('');
|
||
}
|
||
}} onBlur={() => {
|
||
if (Number(emailCodeDuration) > 0) {
|
||
updateConfig(id, config, 'codeDuration', emailCodeDuration, 'email');
|
||
}
|
||
else {
|
||
updateConfig(id, config, 'codeDuration', undefined, 'email');
|
||
}
|
||
}} suffix="分钟"/>
|
||
</Form.Item>
|
||
<Form.Item label="验证码位数" tooltip="邮箱验证码位数,可设置4~8位">
|
||
<Input placeholder="请输入验证码有效位数" type="number" value={emailDigit} min={4} max={8} onChange={(e) => {
|
||
const val = e.target.value;
|
||
if (val) {
|
||
setEmailDigit(Number(val));
|
||
}
|
||
else {
|
||
setEmailDigit('');
|
||
}
|
||
}} onBlur={() => {
|
||
if (Number(emailDigit) > 0) {
|
||
updateConfig(id, config, 'digit', emailDigit, 'email');
|
||
}
|
||
else {
|
||
updateConfig(id, config, 'digit', undefined, 'email');
|
||
}
|
||
}}/>
|
||
</Form.Item>
|
||
<Form.Item label="邮箱后缀" tooltip="允许的邮箱后缀(如: qq.com),不填则不校验">
|
||
<RenderEmailSuffixes t={t} emailSuffixes={emailSuffixes} onChange={(v) => {
|
||
if (v !== config?.emailSuffixes) {
|
||
updateConfig(id, config, 'emailSuffixes', v, 'email');
|
||
}
|
||
}}/>
|
||
</Form.Item>
|
||
</Form>
|
||
</div>}
|
||
</div>);
|
||
}
|