V1.0.4
1. 新增用户注册需要邮箱验证码 2. 机器码、软件权限控制、用户 隔离,除非超级管理员,其他用户只能看到自己下面的用户,管理员可以看到除超级管理员以外的所有
This commit is contained in:
parent
208b887f79
commit
9a2bc86427
@ -73,7 +73,13 @@ export default [
|
|||||||
name: 'laitoolOptions',
|
name: 'laitoolOptions',
|
||||||
path: '/options/laitoolOptions',
|
path: '/options/laitoolOptions',
|
||||||
component: './Options/LaitoolOptions/LaitoolOptions/index',
|
component: './Options/LaitoolOptions/LaitoolOptions/index',
|
||||||
access: 'canLaitoolOptions',
|
access: 'canOptions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'systemOptions',
|
||||||
|
path: '/options/systemOptions',
|
||||||
|
component: './Options/SystemOptions/index',
|
||||||
|
access: 'canOptions',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lms",
|
"name": "lms",
|
||||||
"version": "1.0.1",
|
"version": "1.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "An out-of-box UI solution for enterprise applications",
|
"description": "An out-of-box UI solution for enterprise applications",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
|||||||
isAdminOrSuperAdmin: false,
|
isAdminOrSuperAdmin: false,
|
||||||
|
|
||||||
canOptions: false,
|
canOptions: false,
|
||||||
canLaitoolOptions: false,
|
|
||||||
|
|
||||||
canApplySoftwareControl: false,
|
canApplySoftwareControl: false,
|
||||||
canSofrwareControlManagement: false,
|
canSofrwareControlManagement: false,
|
||||||
@ -51,14 +50,16 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
|||||||
canUserManagement: true,
|
canUserManagement: true,
|
||||||
|
|
||||||
canMachineManagement: true,
|
canMachineManagement: true,
|
||||||
canUpgradeMachine: true
|
canUpgradeMachine: true,
|
||||||
|
|
||||||
|
canSofrwareControlManagement: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser?.roleNames?.includes("Admin")) {
|
if (currentUser?.roleNames?.includes("Admin")) {
|
||||||
access = {
|
access = {
|
||||||
...access,
|
...access,
|
||||||
canPrompt: true,
|
canPrompt: false,
|
||||||
|
|
||||||
canUserManagement: true,
|
canUserManagement: true,
|
||||||
canEditUser: true,
|
canEditUser: true,
|
||||||
@ -66,8 +67,7 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
|||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
isAdminOrSuperAdmin: true,
|
isAdminOrSuperAdmin: true,
|
||||||
|
|
||||||
canOptions: true,
|
canOptions: false,
|
||||||
canLaitoolOptions: true,
|
|
||||||
|
|
||||||
canMachineManagement: true,
|
canMachineManagement: true,
|
||||||
canEditMachine: true,
|
canEditMachine: true,
|
||||||
@ -93,7 +93,6 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
|||||||
isAdminOrSuperAdmin: true,
|
isAdminOrSuperAdmin: true,
|
||||||
|
|
||||||
canOptions: true,
|
canOptions: true,
|
||||||
canLaitoolOptions: true,
|
|
||||||
|
|
||||||
canMachineManagement: true,
|
canMachineManagement: true,
|
||||||
canEditMachine: true,
|
canEditMachine: true,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export default {
|
|||||||
|
|
||||||
'menu.options': '配置管理',
|
'menu.options': '配置管理',
|
||||||
'menu.options.laitoolOptions': 'Laitool配置',
|
'menu.options.laitoolOptions': 'Laitool配置',
|
||||||
|
'menu.options.systemOptions': '系统配置',
|
||||||
|
|
||||||
'menu.roleManagement': '角色管理',
|
'menu.roleManagement': '角色管理',
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Form, Card, Row, Col, InputNumber, Button, Input, message } from 'antd'
|
|||||||
import TextArea from 'antd/es/input/TextArea';
|
import TextArea from 'antd/es/input/TextArea';
|
||||||
import { useSoftStore } from '@/store/software';
|
import { useSoftStore } from '@/store/software';
|
||||||
import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool';
|
import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool';
|
||||||
import { OptionKeyName } from '@/services/enum/optionEnum';
|
import { AllOptionKeyName, OptionKeyName } from '@/services/enum/optionEnum';
|
||||||
|
|
||||||
interface ImageOptionsProps {
|
interface ImageOptionsProps {
|
||||||
// Add your props here
|
// Add your props here
|
||||||
@ -19,7 +19,7 @@ const ImageOptions: React.FC<ImageOptionsProps> = ({ visible }) => {
|
|||||||
if (!visible) return;
|
if (!visible) return;
|
||||||
setTopSpinning(true);
|
setTopSpinning(true);
|
||||||
setTopSpinTip("加载信息中");
|
setTopSpinTip("加载信息中");
|
||||||
GetOptions("image").then((res) => {
|
GetOptions(AllOptionKeyName.Image).then((res) => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
[OptionKeyName.LaitoolFluxApiModelList]: getOptionsStringValue(res, OptionKeyName.LaitoolFluxApiModelList, "{}"),
|
[OptionKeyName.LaitoolFluxApiModelList]: getOptionsStringValue(res, OptionKeyName.LaitoolFluxApiModelList, "{}"),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { AllOptionKeyName } from '@/services/enum/optionEnum';
|
||||||
import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool';
|
import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool';
|
||||||
import { useOptionsStore } from '@/store/options';
|
import { useOptionsStore } from '@/store/options';
|
||||||
import { useSoftStore } from '@/store/software';
|
import { useSoftStore } from '@/store/software';
|
||||||
@ -23,7 +24,7 @@ const SimpleOptions: React.FC<SimpleOptionsProps> = ({ visible }) => {
|
|||||||
setTopSpinning(true);
|
setTopSpinning(true);
|
||||||
setTopSpinTip("加载信息中");
|
setTopSpinTip("加载信息中");
|
||||||
// 这边加载所有的配音数据
|
// 这边加载所有的配音数据
|
||||||
GetOptions("software").then((res) => {
|
GetOptions(AllOptionKeyName.Software).then((res) => {
|
||||||
setLaitoolOptions(res);
|
setLaitoolOptions(res);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
LaitoolHomePage: getOptionsStringValue(res, 'LaitoolHomePage', ""),
|
LaitoolHomePage: getOptionsStringValue(res, 'LaitoolHomePage', ""),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { AllOptionKeyName } from '@/services/enum/optionEnum';
|
||||||
import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool';
|
import { GetOptions, getOptionsStringValue, SaveOptions } from '@/services/services/options/optionsTool';
|
||||||
import { useOptionsStore } from '@/store/options';
|
import { useOptionsStore } from '@/store/options';
|
||||||
import { useSoftStore } from '@/store/software';
|
import { useSoftStore } from '@/store/software';
|
||||||
@ -40,7 +41,7 @@ const DubSettingTTsOptions: React.FC<DubSettingTTsOptionsProps> = ({ visible })
|
|||||||
setTopSpinning(true);
|
setTopSpinning(true);
|
||||||
setTopSpinTip("加载信息中");
|
setTopSpinTip("加载信息中");
|
||||||
// 这边加载所有的配音数据
|
// 这边加载所有的配音数据
|
||||||
GetOptions('tts').then((res) => {
|
GetOptions(AllOptionKeyName.TTS).then((res) => {
|
||||||
setTTsOptions(res);
|
setTTsOptions(res);
|
||||||
form.setFieldsValue({ edgeTTsRoles: getOptionsStringValue(res, 'EdgeTTsRoles', "{}") })
|
form.setFieldsValue({ edgeTTsRoles: getOptionsStringValue(res, 'EdgeTTsRoles', "{}") })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,28 +9,23 @@ import BasicOptions from '../BasicOptions';
|
|||||||
const LaitoolOptions: React.FC = () => {
|
const LaitoolOptions: React.FC = () => {
|
||||||
|
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
const [tabKey, setTabKey] = React.useState<string | undefined>(undefined);
|
|
||||||
|
|
||||||
const items = [{
|
const items = [{
|
||||||
label: `软件设置`,
|
label: `软件设置`,
|
||||||
key: "software",
|
key: "software",
|
||||||
children: <BasicOptions />,
|
children: <BasicOptions />,
|
||||||
style: undefined,
|
style: undefined,
|
||||||
}, {
|
}, {
|
||||||
label: `配音设置`,
|
label: `配音设置`,
|
||||||
key: "dub",
|
key: "dub",
|
||||||
children: <DubSetting />,
|
children: <DubSetting />,
|
||||||
style: undefined,
|
style: undefined,
|
||||||
destroyInactiveTabPane : true
|
destroyInactiveTabPane: true
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const onChange = (key: string) => {
|
|
||||||
setTabKey(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TemplateContainer title={false} navTheme={initialState?.settings?.navTheme ?? "realDark"}>
|
<TemplateContainer title={false} navTheme={initialState?.settings?.navTheme ?? "realDark"}>
|
||||||
<Tabs defaultActiveKey="1" destroyInactiveTabPane={true} items={items} onChange={onChange} />
|
<Tabs defaultActiveKey="1" destroyInactiveTabPane={true} items={items} />
|
||||||
</TemplateContainer>
|
</TemplateContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
207
src/pages/Options/SystemOptions/MailSettingOption.tsx
Normal file
207
src/pages/Options/SystemOptions/MailSettingOption.tsx
Normal file
@ -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<MailSettingProps> = ({ 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<MailConfig>(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<ApiResponse.SuccessItem<string>>('/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 (
|
||||||
|
<Card title="邮箱设置" extra={<MailOutlined />}>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={onFinish}
|
||||||
|
disabled={topSpinning}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', gap: '24px' }}>
|
||||||
|
<Form.Item
|
||||||
|
name="enableMailService"
|
||||||
|
valuePropName="checked"
|
||||||
|
label='开启/关闭邮箱服务'
|
||||||
|
>
|
||||||
|
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="enableSSL"
|
||||||
|
valuePropName="checked"
|
||||||
|
label='启用SMTP SSL'
|
||||||
|
>
|
||||||
|
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="smtpServer"
|
||||||
|
label="SMTP 服务器"
|
||||||
|
rules={[{ required: true, message: '请输入SMTP服务器' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="e.g. smtp.exmail.qq.com" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="port"
|
||||||
|
label="发信端口号"
|
||||||
|
rules={[{ required: true, message: '请输入端口号' }]}
|
||||||
|
>
|
||||||
|
<Input type="number" placeholder="e.g. 465" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="username"
|
||||||
|
label="发信用户名"
|
||||||
|
rules={[{ required: true, message: '请输入用户名' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="用户名" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="password"
|
||||||
|
label="SMTP访问凭证"
|
||||||
|
rules={[{ required: true, message: '请输入SMTP访问凭证' }]}
|
||||||
|
>
|
||||||
|
<Input.Password placeholder="请输入SMTP访问凭证" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="senderEmail"
|
||||||
|
label="发信邮箱"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入发送用户邮箱' },
|
||||||
|
{ type: 'email', message: '请输入发送用户邮箱' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="e.g. noreply@example.com" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="testReceiveMail"
|
||||||
|
label="测试收信邮箱"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入接收测试邮件的邮箱' },
|
||||||
|
{ type: 'email', message: '请输入接收测试邮件的邮箱' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="e.g. test@example.com" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" loading={topSpinning} style={{ marginRight: 16 }}>
|
||||||
|
保存邮箱设置
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleTestEmail} loading={topSpinning}>
|
||||||
|
测试邮箱发送
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
{messageHolder}
|
||||||
|
{modalHolder}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MailSettingOption;
|
||||||
31
src/pages/Options/SystemOptions/index.tsx
Normal file
31
src/pages/Options/SystemOptions/index.tsx
Normal file
@ -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<string[]>([]);
|
||||||
|
|
||||||
|
const items = [{
|
||||||
|
label: `邮件设置`,
|
||||||
|
key: "mail",
|
||||||
|
style: undefined,
|
||||||
|
children: <MailSettingOption visible={activeKeys.includes('imageOptions')} />
|
||||||
|
}]
|
||||||
|
|
||||||
|
const onChange = (key: string | string[]) => {
|
||||||
|
setActiveKeys(Array.isArray(key) ? key : [key]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TemplateContainer title={false} navTheme={initialState?.settings?.navTheme ?? "realDark"}>
|
||||||
|
<Tabs defaultActiveKey="1" destroyInactiveTabPane={true} items={items} onChange={onChange} />
|
||||||
|
</TemplateContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LaitoolOptions;
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import TemplateContainer from '@/pages/TemplateContainer';
|
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 { Button, Dropdown, Form, Input, message, Modal, Select, Table, TableProps, Tag } from 'antd';
|
||||||
import { FilterValue, SorterResult, TableCurrentDataSource, TablePaginationConfig } from 'antd/es/table/interface';
|
import { FilterValue, SorterResult, TableCurrentDataSource, TablePaginationConfig } from 'antd/es/table/interface';
|
||||||
import { Software, SoftwareControl } from '@/services/services/software';
|
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 { DeleteOutlined, EditOutlined, MenuOutlined, PlusSquareOutlined } from '@ant-design/icons';
|
||||||
import { GetOptions, getOptionsStringValue } from '@/services/services/options/optionsTool';
|
import { GetOptions, getOptionsStringValue } from '@/services/services/options/optionsTool';
|
||||||
import { useSoftStore } from '@/store/software';
|
import { useSoftStore } from '@/store/software';
|
||||||
|
import { AllOptionKeyName } from '@/services/enum/optionEnum';
|
||||||
|
|
||||||
|
|
||||||
interface SoftwareControlManagementProps {
|
interface SoftwareControlManagementProps {
|
||||||
@ -24,6 +25,7 @@ const SoftwareControlManagement: FC<SoftwareControlManagementProps> = () => {
|
|||||||
const [softwareBasicInfo, setSoftwareBasicInfo] = useState<SoftwareModel.SoftwareBasicInfo[]>();
|
const [softwareBasicInfo, setSoftwareBasicInfo] = useState<SoftwareModel.SoftwareBasicInfo[]>();
|
||||||
const [softwareOptions, setSoftwareOptions] = useState<any>([]);
|
const [softwareOptions, setSoftwareOptions] = useState<any>([]);
|
||||||
const [data, setData] = React.useState<SoftwareModel.SoftwareControlBase[]>([]);
|
const [data, setData] = React.useState<SoftwareModel.SoftwareControlBase[]>([]);
|
||||||
|
const access = useAccess();
|
||||||
const [tableParams, setTableParams] = useState<TableModel.TableParams>({
|
const [tableParams, setTableParams] = useState<TableModel.TableParams>({
|
||||||
pagination: {
|
pagination: {
|
||||||
current: 1,
|
current: 1,
|
||||||
@ -99,6 +101,7 @@ const SoftwareControlManagement: FC<SoftwareControlManagementProps> = () => {
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
hidden: !access.isAdminOrSuperAdmin,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
@ -218,7 +221,7 @@ const SoftwareControlManagement: FC<SoftwareControlManagementProps> = () => {
|
|||||||
setTopSpinning(true);
|
setTopSpinning(true);
|
||||||
setTopSpinTip("加载信息中");
|
setTopSpinTip("加载信息中");
|
||||||
let LaiToolTrialDays = 1;
|
let LaiToolTrialDays = 1;
|
||||||
let res = await GetOptions("trial");
|
let res = await GetOptions(AllOptionKeyName.Trial);
|
||||||
days = Number(getOptionsStringValue(res, 'LaiToolTrialDays', "") || LaiToolTrialDays);
|
days = Number(getOptionsStringValue(res, 'LaiToolTrialDays', "") || LaiToolTrialDays);
|
||||||
setTopSpinning(false);
|
setTopSpinning(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
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 { UserRegistr } from '@/services/services/login';
|
||||||
import { set } from 'lodash';
|
import { history, request } from '@umijs/max';
|
||||||
import { history } from '@umijs/max';
|
|
||||||
|
|
||||||
const Register: React.FC = () => {
|
const Register: React.FC = () => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [spinning, setSpinning] = useState(false);
|
const [spinning, setSpinning] = useState(false);
|
||||||
const [messageApi, messageHolder] = message.useMessage();
|
const [messageApi, messageHolder] = message.useMessage();
|
||||||
|
const [countdown, setCountdown] = useState(0); // 倒计时状态
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 检查当前网址是不是包含query,并且?aff=后面有6位数字
|
// 检查当前网址是不是包含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<ApiResponse.SuccessItem<string>>(`/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) => {
|
const onFinish = async (values: UserModel.UserRegisterParams) => {
|
||||||
// 判断两次密码是否一致
|
// 判断两次密码是否一致
|
||||||
if (values.password !== values.confirm) {
|
if (values.password !== values.confirm) {
|
||||||
messageApi.warning('两次密码不一致!');
|
messageApi.warning('两次密码不一致!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
debugger
|
||||||
|
|
||||||
// 开始注册
|
// 开始注册
|
||||||
setSpinning(true);
|
setSpinning(true);
|
||||||
@ -41,8 +88,6 @@ const Register: React.FC = () => {
|
|||||||
finally {
|
finally {
|
||||||
setSpinning(false);
|
setSpinning(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -80,6 +125,34 @@ const Register: React.FC = () => {
|
|||||||
<Input placeholder='邮箱号' />
|
<Input placeholder='邮箱号' />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="verificationCode"
|
||||||
|
rules={[{ required: true, message: '请输入邮箱验证码!' }]}
|
||||||
|
>
|
||||||
|
<Row gutter={8}>
|
||||||
|
<Col flex="auto">
|
||||||
|
<Input placeholder='邮箱验证码' />
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={sendVerificationCode}
|
||||||
|
disabled={countdown > 0}
|
||||||
|
style={{
|
||||||
|
width: '100px',
|
||||||
|
background: countdown > 0 ? '#f0f0f0' : '#1890ff',
|
||||||
|
borderColor: countdown > 0 ? '#d9d9d9' : '#1890ff',
|
||||||
|
color: countdown > 0 ? '#595959' : '#fff',
|
||||||
|
fontWeight: 500,
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{countdown > 0 ? `${countdown}秒后重试` : '获取验证码'}
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
rules={[
|
rules={[
|
||||||
|
|||||||
@ -2,12 +2,44 @@ export enum OptionType {
|
|||||||
String = 1,
|
String = 1,
|
||||||
JSON = 2,
|
JSON = 2,
|
||||||
Number = 3,
|
Number = 3,
|
||||||
|
Boolean = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export enum AllOptionKeyName {
|
||||||
|
/** 获取所有的 Option */
|
||||||
|
All = "all",
|
||||||
|
|
||||||
|
/** 获取TTS相关的 Option */
|
||||||
|
TTS = "tts",
|
||||||
|
|
||||||
|
/** 获取软件相关的 Option */
|
||||||
|
Software = "software",
|
||||||
|
|
||||||
|
/** 软件试用相关 Option */
|
||||||
|
Trial = "trial",
|
||||||
|
|
||||||
|
/** 出图相关的 Option */
|
||||||
|
Image = "image",
|
||||||
|
|
||||||
|
/** 邮件设置相关 Option */
|
||||||
|
MailSetting = "mailSetting",
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export enum OptionKeyName {
|
export enum OptionKeyName {
|
||||||
|
|
||||||
/** laitool Flux API 模型类型 */
|
/** laitool Flux API 模型类型 */
|
||||||
LaitoolFluxApiModelList = "LaitoolFluxApiModelList",
|
LaitoolFluxApiModelList = "LaitoolFluxApiModelList",
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SMTP的邮件设置
|
||||||
|
/// </summary>
|
||||||
|
SMTPMailSetting = "SMTPMailSetting",
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否开启邮箱服务
|
||||||
|
/// </summary>
|
||||||
|
EnableMailService = "EnableMailService",
|
||||||
}
|
}
|
||||||
@ -129,7 +129,8 @@ export async function UserRegistr(params: UserModel.UserRegisterParams): Promise
|
|||||||
email: params.email ?? '',
|
email: params.email ?? '',
|
||||||
password: secPassword,
|
password: secPassword,
|
||||||
tokenId: publicKey.token,
|
tokenId: publicKey.token,
|
||||||
affiliateCode: params.affiliateCode
|
affiliateCode: params.affiliateCode,
|
||||||
|
verificationCode: params.verificationCode
|
||||||
}
|
}
|
||||||
let res = await request<ApiResponse.SuccessItem<string>>('/lms/User/Register', {
|
let res = await request<ApiResponse.SuccessItem<string>>('/lms/User/Register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { OptionType } from "@/services/enum/optionEnum";
|
import { AllOptionKeyName, OptionKeyName, OptionType } from "@/services/enum/optionEnum";
|
||||||
import { isEmpty } from "lodash";
|
import { isEmpty } from "lodash";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,6 +32,8 @@ export function getOptionsValue<T>(options: OptionModel.Option[], keyName: strin
|
|||||||
return JSON.parse(option.value) as T;
|
return JSON.parse(option.value) as T;
|
||||||
case OptionType.Number:
|
case OptionType.Number:
|
||||||
return Number(option.value) as T;
|
return Number(option.value) as T;
|
||||||
|
case OptionType.Boolean:
|
||||||
|
return Boolean(option.value as string) as T;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported option type: ${option.type}`);
|
throw new Error(`Unsupported option type: ${option.type}`);
|
||||||
}
|
}
|
||||||
@ -74,6 +76,8 @@ export function getOptionsStringValue(options: OptionModel.Option[], keyName: st
|
|||||||
return String(option.value);
|
return String(option.value);
|
||||||
case OptionType.Number:
|
case OptionType.Number:
|
||||||
return String(Number(option.value));
|
return String(Number(option.value));
|
||||||
|
case OptionType.Boolean:
|
||||||
|
return String(Boolean(option.value));
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported option type: ${option.type}`);
|
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";
|
import { request } from "@umijs/max";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 TTS 选项
|
* 获取指定的选项,会校验权限
|
||||||
* @returns {Promise<OptionModel.Option[]>} 返回一个包含 OptionModel.Option 对象的 Promise
|
* @returns {Promise<OptionModel.Option[]>} 返回一个包含 OptionModel.Option 对象的 Promise
|
||||||
* @throws {Error} 如果响应代码不是 1,则抛出错误
|
* @throws {Error} 如果响应代码不是 1,则抛出错误
|
||||||
*/
|
*/
|
||||||
export async function GetOptions(optionsKey: string): Promise<OptionModel.Option[]> {
|
export async function GetOptions(optionsKey: AllOptionKeyName): Promise<OptionModel.Option[]> {
|
||||||
let res = await request<ApiResponse.SuccessItem<OptionModel.Option[]>>(`/lms/LaitoolOptions/GetAllOptions/${optionsKey}`, {
|
let res = await request<ApiResponse.SuccessItem<OptionModel.Option[]>>(`/lms/LaitoolOptions/GetAllOptions/${optionsKey}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@ -105,6 +109,25 @@ export async function GetOptions(optionsKey: string): Promise<OptionModel.Option
|
|||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通用的选项,不会校验权限
|
||||||
|
* @param optionsKey
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function GetSimpleOptions(optionsKey: OptionKeyName): Promise<OptionModel.Option[]> {
|
||||||
|
debugger
|
||||||
|
let res = await request<ApiResponse.SuccessItem<OptionModel.Option[]>>(`/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 选项的键
|
* @param {string} key 选项的键
|
||||||
@ -113,7 +136,7 @@ export async function GetOptions(optionsKey: string): Promise<OptionModel.Option
|
|||||||
* @throws {Error} 如果响应代码不是 1,则抛出错误
|
* @throws {Error} 如果响应代码不是 1,则抛出错误
|
||||||
*/
|
*/
|
||||||
export async function SaveOptions(options: object): Promise<void> {
|
export async function SaveOptions(options: object): Promise<void> {
|
||||||
|
debugger
|
||||||
let data: { key: string; value: any; }[] = [];
|
let data: { key: string; value: any; }[] = [];
|
||||||
Object.entries(options).reduce((acc, [key, value]) => {
|
Object.entries(options).reduce((acc, [key, value]) => {
|
||||||
data.push({ key: key, value: value.toString() });
|
data.push({ key: key, value: value.toString() });
|
||||||
|
|||||||
2
src/services/typing/access.d.ts
vendored
2
src/services/typing/access.d.ts
vendored
@ -21,8 +21,6 @@ declare namespace AccessType {
|
|||||||
//#region 软件配置项操作权限
|
//#region 软件配置项操作权限
|
||||||
/** 是不是可以操作配置型 */
|
/** 是不是可以操作配置型 */
|
||||||
canOptions: boolean;
|
canOptions: boolean;
|
||||||
/** 是不是可以操作软件配置项 */
|
|
||||||
canLaitoolOptions: boolean;
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 机器权限
|
//#region 机器权限
|
||||||
|
|||||||
1
src/services/typing/user.d.ts
vendored
1
src/services/typing/user.d.ts
vendored
@ -13,6 +13,7 @@ declare namespace UserModel {
|
|||||||
email?: string
|
email?: string
|
||||||
confirm?: string
|
confirm?: string
|
||||||
affiliateCode: string
|
affiliateCode: string
|
||||||
|
verificationCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user