From 9a2bc86427f59f80df579d9e9cc2e113173efc1e Mon Sep 17 00:00:00 2001 From: lq1405 <2769838458@qq.com> Date: Sun, 16 Mar 2025 23:00:31 +0800 Subject: [PATCH] =?UTF-8?q?V1.0.4=201.=20=E6=96=B0=E5=A2=9E=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=B3=A8=E5=86=8C=E9=9C=80=E8=A6=81=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81=202.=20=E6=9C=BA=E5=99=A8=E7=A0=81?= =?UTF-8?q?=E3=80=81=E8=BD=AF=E4=BB=B6=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E3=80=81=E7=94=A8=E6=88=B7=20=E9=9A=94=E7=A6=BB=EF=BC=8C?= =?UTF-8?q?=E9=99=A4=E9=9D=9E=E8=B6=85=E7=BA=A7=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=EF=BC=8C=E5=85=B6=E4=BB=96=E7=94=A8=E6=88=B7=E5=8F=AA=E8=83=BD?= =?UTF-8?q?=E7=9C=8B=E5=88=B0=E8=87=AA=E5=B7=B1=E4=B8=8B=E9=9D=A2=E7=9A=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=EF=BC=8C=E7=AE=A1=E7=90=86=E5=91=98=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E7=9C=8B=E5=88=B0=E9=99=A4=E8=B6=85=E7=BA=A7=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E4=BB=A5=E5=A4=96=E7=9A=84=E6=89=80=E6=9C=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes.ts | 8 +- package.json | 2 +- src/access.ts | 11 +- src/locales/zh-CN/menu.ts | 1 + .../BasicOptions/ImageOptions.tsx | 4 +- .../BasicOptions/SimpleOptions/index.tsx | 3 +- .../DubSetting/DubSettingTTsOptions/index.tsx | 3 +- .../LaitoolOptions/LaitoolOptions/index.tsx | 13 +- .../SystemOptions/MailSettingOption.tsx | 207 ++++++++++++++++++ src/pages/Options/SystemOptions/index.tsx | 31 +++ .../SofrwareControlManagement.tsx | 7 +- src/pages/User/Register/index.tsx | 83 ++++++- src/services/enum/optionEnum.ts | 32 +++ src/services/services/login.ts | 3 +- src/services/services/options/optionsTool.ts | 31 ++- src/services/typing/access.d.ts | 2 - src/services/typing/user.d.ts | 1 + 17 files changed, 407 insertions(+), 35 deletions(-) create mode 100644 src/pages/Options/SystemOptions/MailSettingOption.tsx create mode 100644 src/pages/Options/SystemOptions/index.tsx diff --git a/config/routes.ts b/config/routes.ts index 98b6d26..c3564fa 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -73,7 +73,13 @@ export default [ name: 'laitoolOptions', path: '/options/laitoolOptions', component: './Options/LaitoolOptions/LaitoolOptions/index', - access: 'canLaitoolOptions', + access: 'canOptions', + }, + { + name: 'systemOptions', + path: '/options/systemOptions', + component: './Options/SystemOptions/index', + access: 'canOptions', } ] }, diff --git a/package.json b/package.json index e3f033d..1246552 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lms", - "version": "1.0.1", + "version": "1.0.4", "private": true, "description": "An out-of-box UI solution for enterprise applications", "scripts": { diff --git a/src/access.ts b/src/access.ts index 7a8a872..366d77e 100644 --- a/src/access.ts +++ b/src/access.ts @@ -16,7 +16,6 @@ export default function access(initialState: { currentUser?: API.CurrentUser } | isAdminOrSuperAdmin: false, canOptions: false, - canLaitoolOptions: false, canApplySoftwareControl: false, canSofrwareControlManagement: false, @@ -51,14 +50,16 @@ export default function access(initialState: { currentUser?: API.CurrentUser } | canUserManagement: true, canMachineManagement: true, - canUpgradeMachine: true + canUpgradeMachine: true, + + canSofrwareControlManagement: true, } } if (currentUser?.roleNames?.includes("Admin")) { access = { ...access, - canPrompt: true, + canPrompt: false, canUserManagement: true, canEditUser: true, @@ -66,8 +67,7 @@ export default function access(initialState: { currentUser?: API.CurrentUser } | isAdmin: true, isAdminOrSuperAdmin: true, - canOptions: true, - canLaitoolOptions: true, + canOptions: false, canMachineManagement: true, canEditMachine: true, @@ -93,7 +93,6 @@ export default function access(initialState: { currentUser?: API.CurrentUser } | isAdminOrSuperAdmin: true, canOptions: true, - canLaitoolOptions: true, canMachineManagement: true, canEditMachine: true, diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts index 52563db..22f8d17 100644 --- a/src/locales/zh-CN/menu.ts +++ b/src/locales/zh-CN/menu.ts @@ -9,6 +9,7 @@ export default { 'menu.options': '配置管理', 'menu.options.laitoolOptions': 'Laitool配置', + 'menu.options.systemOptions': '系统配置', 'menu.roleManagement': '角色管理', diff --git a/src/pages/Options/LaitoolOptions/BasicOptions/ImageOptions.tsx b/src/pages/Options/LaitoolOptions/BasicOptions/ImageOptions.tsx index 1030877..b076d7f 100644 --- a/src/pages/Options/LaitoolOptions/BasicOptions/ImageOptions.tsx +++ b/src/pages/Options/LaitoolOptions/BasicOptions/ImageOptions.tsx @@ -3,7 +3,7 @@ import { Form, Card, Row, Col, InputNumber, Button, Input, message } from 'antd' import TextArea from 'antd/es/input/TextArea'; import { useSoftStore } from '@/store/software'; import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool'; -import { OptionKeyName } from '@/services/enum/optionEnum'; +import { AllOptionKeyName, OptionKeyName } from '@/services/enum/optionEnum'; interface ImageOptionsProps { // Add your props here @@ -19,7 +19,7 @@ const ImageOptions: React.FC = ({ visible }) => { if (!visible) return; setTopSpinning(true); setTopSpinTip("加载信息中"); - GetOptions("image").then((res) => { + GetOptions(AllOptionKeyName.Image).then((res) => { form.setFieldsValue({ [OptionKeyName.LaitoolFluxApiModelList]: getOptionsStringValue(res, OptionKeyName.LaitoolFluxApiModelList, "{}"), }); diff --git a/src/pages/Options/LaitoolOptions/BasicOptions/SimpleOptions/index.tsx b/src/pages/Options/LaitoolOptions/BasicOptions/SimpleOptions/index.tsx index 004614b..57701b5 100644 --- a/src/pages/Options/LaitoolOptions/BasicOptions/SimpleOptions/index.tsx +++ b/src/pages/Options/LaitoolOptions/BasicOptions/SimpleOptions/index.tsx @@ -1,3 +1,4 @@ +import { AllOptionKeyName } from '@/services/enum/optionEnum'; import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool'; import { useOptionsStore } from '@/store/options'; import { useSoftStore } from '@/store/software'; @@ -23,7 +24,7 @@ const SimpleOptions: React.FC = ({ visible }) => { setTopSpinning(true); setTopSpinTip("加载信息中"); // 这边加载所有的配音数据 - GetOptions("software").then((res) => { + GetOptions(AllOptionKeyName.Software).then((res) => { setLaitoolOptions(res); form.setFieldsValue({ LaitoolHomePage: getOptionsStringValue(res, 'LaitoolHomePage', ""), diff --git a/src/pages/Options/LaitoolOptions/DubSetting/DubSettingTTsOptions/index.tsx b/src/pages/Options/LaitoolOptions/DubSetting/DubSettingTTsOptions/index.tsx index 7ae702f..73323c6 100644 --- a/src/pages/Options/LaitoolOptions/DubSetting/DubSettingTTsOptions/index.tsx +++ b/src/pages/Options/LaitoolOptions/DubSetting/DubSettingTTsOptions/index.tsx @@ -1,3 +1,4 @@ +import { AllOptionKeyName } from '@/services/enum/optionEnum'; import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool'; import { useOptionsStore } from '@/store/options'; import { useSoftStore } from '@/store/software'; @@ -40,7 +41,7 @@ const DubSettingTTsOptions: React.FC = ({ visible }) setTopSpinning(true); setTopSpinTip("加载信息中"); // 这边加载所有的配音数据 - GetOptions('tts').then((res) => { + GetOptions(AllOptionKeyName.TTS).then((res) => { setTTsOptions(res); form.setFieldsValue({ edgeTTsRoles: getOptionsStringValue(res, 'EdgeTTsRoles', "{}") }) } diff --git a/src/pages/Options/LaitoolOptions/LaitoolOptions/index.tsx b/src/pages/Options/LaitoolOptions/LaitoolOptions/index.tsx index 830079e..7d2c482 100644 --- a/src/pages/Options/LaitoolOptions/LaitoolOptions/index.tsx +++ b/src/pages/Options/LaitoolOptions/LaitoolOptions/index.tsx @@ -9,28 +9,23 @@ import BasicOptions from '../BasicOptions'; const LaitoolOptions: React.FC = () => { const { initialState } = useModel('@@initialState'); - const [tabKey, setTabKey] = React.useState(undefined); const items = [{ label: `软件设置`, key: "software", - children: , + children: , style: undefined, }, { label: `配音设置`, key: "dub", - children: , + children: , style: undefined, - destroyInactiveTabPane : true + destroyInactiveTabPane: true }] - const onChange = (key: string) => { - setTabKey(key); - }; - return ( - + ); }; diff --git a/src/pages/Options/SystemOptions/MailSettingOption.tsx b/src/pages/Options/SystemOptions/MailSettingOption.tsx new file mode 100644 index 0000000..326b895 --- /dev/null +++ b/src/pages/Options/SystemOptions/MailSettingOption.tsx @@ -0,0 +1,207 @@ +import React, { useState, useEffect } from 'react'; +import { Form, Input, Button, Card, message, Switch, Modal } from 'antd'; +import { MailOutlined } from '@ant-design/icons'; +import { useSoftStore } from '@/store/software'; +import { GetOptions, getOptionsValue, GetSimpleOptions, SaveOptions } from '@/services/services/options/optionsTool'; +import { AllOptionKeyName, OptionKeyName } from '@/services/enum/optionEnum'; +import { request } from '@umijs/max'; + +interface MailSettingProps { + visible?: boolean; +} + +interface MailConfig { + smtpServer: string; + port: number; + username: string; + password: string; + senderEmail: string; + enableSSL: boolean; + enableMailService: boolean; + testReceiveMail: string; +} + +const MailSettingOption: React.FC = ({ visible }) => { + const [form] = Form.useForm(); + const { setTopSpinning, setTopSpinTip, topSpinning } = useSoftStore(); + const [messageApi, messageHolder] = message.useMessage(); + const [modalApi, modalHolder] = Modal.useModal(); + + useEffect(() => { + // Fetch current mail settings + fetchMailSettings(); + }, [visible]); + + const fetchMailSettings = async () => { + try { + setTopSpinTip("正在加载邮箱设置"); + setTopSpinning(true); + const response = await GetOptions(AllOptionKeyName.MailSetting); + let mailSetting = getOptionsValue(response, OptionKeyName.SMTPMailSetting, { + smtpServer: '', + port: 465, + username: '', + password: '', + senderEmail: '', + enableSSL: false, + enableMailService: false, + testReceiveMail: '' + }); + form.setFieldsValue(mailSetting); + messageApi.success("数据加载成功!"); + } catch (error: any) { + messageApi.error("数据加载失败!" + error.message); + } finally { + setTopSpinning(false); + } + }; + + const onFinish = async (values: MailConfig) => { + try { + setTopSpinTip("正在保存邮箱设置"); + setTopSpinning(true); + console.log(values); + // Replace with actual API call + await SaveOptions({ + [OptionKeyName.SMTPMailSetting]: JSON.stringify({ + ...values, + enableSSL: values.enableSSL ? true : false, + enableMailService: values.enableMailService ? true : false + }), + [OptionKeyName.EnableMailService]: values.enableMailService ? String(true) : String(false) + }); + messageApi.success('Mail settings saved successfully'); + } catch (error) { + messageApi.error('Failed to save mail settings'); + } finally { + setTopSpinning(false); + } + }; + + const handleTestEmail = async () => { + try { + + const confirmed = await modalApi.confirm({ + title: '温馨提示', + content: '在执行词操作之前,请先保存邮件设置,否则无法发送测试邮件', + okText: '发送', + cancelText: '取消', + }); + if (confirmed == false) { + return; + } + setTopSpinning(true); + setTopSpinTip('正在发送测试邮件'); + + + let res = await request>('/lms/LaitoolOptions/TestSendMail', { + method: 'POST', + }); + + if (res.code != 1) { + messageApi.error(res.message); + return; + } + messageApi.success('测试邮件发送成功,请查看收件箱'); + } catch (error) { + messageApi.error('Failed to send test email'); + } finally { + setTopSpinning(false); + } + }; + + return ( + }> +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {messageHolder} + {modalHolder} +
+ ); +}; + +export default MailSettingOption; \ No newline at end of file diff --git a/src/pages/Options/SystemOptions/index.tsx b/src/pages/Options/SystemOptions/index.tsx new file mode 100644 index 0000000..d883488 --- /dev/null +++ b/src/pages/Options/SystemOptions/index.tsx @@ -0,0 +1,31 @@ +import TemplateContainer from '@/pages/TemplateContainer'; +import { useModel } from '@umijs/max'; +import { Tabs, TabsProps, theme } from 'antd'; +import React from 'react'; +import MailSettingOption from './MailSettingOption'; + + +const LaitoolOptions: React.FC = () => { + + const { initialState } = useModel('@@initialState'); + const [activeKeys, setActiveKeys] = React.useState([]); + + const items = [{ + label: `邮件设置`, + key: "mail", + style: undefined, + children: + }] + + const onChange = (key: string | string[]) => { + setActiveKeys(Array.isArray(key) ? key : [key]); + }; + + return ( + + + + ); +}; + +export default LaitoolOptions; \ No newline at end of file diff --git a/src/pages/Software/SofrwareControl/SofrwareControlManagement.tsx b/src/pages/Software/SofrwareControl/SofrwareControlManagement.tsx index 1553507..a48e76e 100644 --- a/src/pages/Software/SofrwareControl/SofrwareControlManagement.tsx +++ b/src/pages/Software/SofrwareControl/SofrwareControlManagement.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import type { FC } from 'react'; import TemplateContainer from '@/pages/TemplateContainer'; -import { useModel } from '@umijs/max'; +import { useAccess, useModel } from '@umijs/max'; import { Button, Dropdown, Form, Input, message, Modal, Select, Table, TableProps, Tag } from 'antd'; import { FilterValue, SorterResult, TableCurrentDataSource, TablePaginationConfig } from 'antd/es/table/interface'; import { Software, SoftwareControl } from '@/services/services/software'; @@ -9,6 +9,7 @@ import moment from 'moment'; import { DeleteOutlined, EditOutlined, MenuOutlined, PlusSquareOutlined } from '@ant-design/icons'; import { GetOptions, getOptionsStringValue } from '@/services/services/options/optionsTool'; import { useSoftStore } from '@/store/software'; +import { AllOptionKeyName } from '@/services/enum/optionEnum'; interface SoftwareControlManagementProps { @@ -24,6 +25,7 @@ const SoftwareControlManagement: FC = () => { const [softwareBasicInfo, setSoftwareBasicInfo] = useState(); const [softwareOptions, setSoftwareOptions] = useState([]); const [data, setData] = React.useState([]); + const access = useAccess(); const [tableParams, setTableParams] = useState({ pagination: { current: 1, @@ -99,6 +101,7 @@ const SoftwareControlManagement: FC = () => { title: '操作', key: 'action', width: 100, + hidden: !access.isAdminOrSuperAdmin, render: (_, record) => ( = () => { setTopSpinning(true); setTopSpinTip("加载信息中"); let LaiToolTrialDays = 1; - let res = await GetOptions("trial"); + let res = await GetOptions(AllOptionKeyName.Trial); days = Number(getOptionsStringValue(res, 'LaiToolTrialDays', "") || LaiToolTrialDays); setTopSpinning(false); } diff --git a/src/pages/User/Register/index.tsx b/src/pages/User/Register/index.tsx index f5c6d78..941362c 100644 --- a/src/pages/User/Register/index.tsx +++ b/src/pages/User/Register/index.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useState } from 'react'; -import { Form, Input, Button, Spin, message } from 'antd'; +import { Form, Input, Button, Spin, message, Row, Col } from 'antd'; import { UserRegistr } from '@/services/services/login'; -import { set } from 'lodash'; -import { history } from '@umijs/max'; +import { history, request } from '@umijs/max'; const Register: React.FC = () => { const [form] = Form.useForm(); const [spinning, setSpinning] = useState(false); const [messageApi, messageHolder] = message.useMessage(); + const [countdown, setCountdown] = useState(0); // 倒计时状态 useEffect(() => { // 检查当前网址是不是包含query,并且?aff=后面有6位数字 @@ -18,12 +18,59 @@ const Register: React.FC = () => { } }, []); + // 发送邮箱验证码 + const sendVerificationCode = async () => { + try { + debugger; + const email = form.getFieldsValue().email; + if (!email) { + messageApi.warning('请先填写邮箱'); + return; + } + + // 验证邮箱格式 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + messageApi.warning('请输入有效的邮箱格式'); + return; + } + + // 开始请求发送的接口 + + let res = await request>(`/lms/User/SendVerificationCode`, { + method: 'POST', + data: { email } + }); + if (res.code != 1) { + throw new Error(res.message); + } + // 设置倒计时 + setCountdown(60); + const timer = setInterval(() => { + setCountdown((prevCountdown) => { + if (prevCountdown <= 1) { + clearInterval(timer); + return 0; + } + return prevCountdown - 1; + }); + }, 1000); + + messageApi.success('验证码已发送,请查收邮箱'); + } catch (error: any) { + messageApi.error(error.message || '发送验证码失败'); + } finally { + + } + }; + const onFinish = async (values: UserModel.UserRegisterParams) => { // 判断两次密码是否一致 if (values.password !== values.confirm) { messageApi.warning('两次密码不一致!'); return; } + debugger // 开始注册 setSpinning(true); @@ -41,8 +88,6 @@ const Register: React.FC = () => { finally { setSpinning(false); } - - }; return ( @@ -80,6 +125,34 @@ const Register: React.FC = () => { + + + + + + + + + + + + /// SMTP的邮件设置 + /// + SMTPMailSetting = "SMTPMailSetting", + + /// + /// 是否开启邮箱服务 + /// + EnableMailService = "EnableMailService", } \ No newline at end of file diff --git a/src/services/services/login.ts b/src/services/services/login.ts index 6655e2b..d974707 100644 --- a/src/services/services/login.ts +++ b/src/services/services/login.ts @@ -129,7 +129,8 @@ export async function UserRegistr(params: UserModel.UserRegisterParams): Promise email: params.email ?? '', password: secPassword, tokenId: publicKey.token, - affiliateCode: params.affiliateCode + affiliateCode: params.affiliateCode, + verificationCode: params.verificationCode } let res = await request>('/lms/User/Register', { method: 'POST', diff --git a/src/services/services/options/optionsTool.ts b/src/services/services/options/optionsTool.ts index fcbff47..3f8fd27 100644 --- a/src/services/services/options/optionsTool.ts +++ b/src/services/services/options/optionsTool.ts @@ -1,4 +1,4 @@ -import { OptionType } from "@/services/enum/optionEnum"; +import { AllOptionKeyName, OptionKeyName, OptionType } from "@/services/enum/optionEnum"; import { isEmpty } from "lodash"; /** @@ -32,6 +32,8 @@ export function getOptionsValue(options: OptionModel.Option[], keyName: strin return JSON.parse(option.value) as T; case OptionType.Number: return Number(option.value) as T; + case OptionType.Boolean: + return Boolean(option.value as string) as T; default: throw new Error(`Unsupported option type: ${option.type}`); } @@ -74,6 +76,8 @@ export function getOptionsStringValue(options: OptionModel.Option[], keyName: st return String(option.value); case OptionType.Number: return String(Number(option.value)); + case OptionType.Boolean: + return String(Boolean(option.value)); default: throw new Error(`Unsupported option type: ${option.type}`); } @@ -88,11 +92,11 @@ export function getOptionsStringValue(options: OptionModel.Option[], keyName: st import { request } from "@umijs/max"; /** - * 获取 TTS 选项 + * 获取指定的选项,会校验权限 * @returns {Promise} 返回一个包含 OptionModel.Option 对象的 Promise * @throws {Error} 如果响应代码不是 1,则抛出错误 */ -export async function GetOptions(optionsKey: string): Promise { +export async function GetOptions(optionsKey: AllOptionKeyName): Promise { let res = await request>(`/lms/LaitoolOptions/GetAllOptions/${optionsKey}`, { method: 'GET', headers: { @@ -105,6 +109,25 @@ export async function GetOptions(optionsKey: string): Promise { + debugger + let res = await request>(`/lms/LaitoolOptions/GetSimpleOptions/${optionsKey}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + } + }); + if (res.code != 1) { + throw new Error(res.message); + } + return res.data; +} + /** * 保存选项 * @param {string} key 选项的键 @@ -113,7 +136,7 @@ export async function GetOptions(optionsKey: string): Promise { - + debugger let data: { key: string; value: any; }[] = []; Object.entries(options).reduce((acc, [key, value]) => { data.push({ key: key, value: value.toString() }); diff --git a/src/services/typing/access.d.ts b/src/services/typing/access.d.ts index f4cd51c..deb1513 100644 --- a/src/services/typing/access.d.ts +++ b/src/services/typing/access.d.ts @@ -21,8 +21,6 @@ declare namespace AccessType { //#region 软件配置项操作权限 /** 是不是可以操作配置型 */ canOptions: boolean; - /** 是不是可以操作软件配置项 */ - canLaitoolOptions: boolean; //#endregion //#region 机器权限 diff --git a/src/services/typing/user.d.ts b/src/services/typing/user.d.ts index 54b7f69..5a81b6a 100644 --- a/src/services/typing/user.d.ts +++ b/src/services/typing/user.d.ts @@ -13,6 +13,7 @@ declare namespace UserModel { email?: string confirm?: string affiliateCode: string + verificationCode: string } /**