feat: login

This commit is contained in:
2025-09-09 16:58:30 +08:00
parent d6457f59f7
commit f33f597a9a
41 changed files with 10998 additions and 3594 deletions

View File

@@ -1,80 +1,80 @@
import { PlusOutlined } from '@ant-design/icons';
import {
type ActionType,
ModalForm,
ProFormText,
ProFormTextArea,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl, useRequest } from '@umijs/max';
import { Button, message } from 'antd';
import type { FC } from 'react';
import { addRule } from '@/services/ant-design-pro/api';
// import { PlusOutlined } from '@ant-design/icons';
// import {
// type ActionType,
// ModalForm,
// ProFormText,
// ProFormTextArea,
// } from '@ant-design/pro-components';
// import { FormattedMessage, useIntl, useRequest } from '@umijs/max';
// import { Button, message } from 'antd';
// import type { FC } from 'react';
// import { addRule } from '@/services/ant-design-pro/api';
interface CreateFormProps {
reload?: ActionType['reload'];
}
// interface CreateFormProps {
// reload?: ActionType['reload'];
// }
const CreateForm: FC<CreateFormProps> = (props) => {
const { reload } = props;
// const CreateForm: FC<CreateFormProps> = (props) => {
// const { reload } = props;
const [messageApi, contextHolder] = message.useMessage();
/**
* @en-US International configuration
* @zh-CN 国际化配置
* */
const intl = useIntl();
// const [messageApi, contextHolder] = message.useMessage();
// /**
// * @en-US International configuration
// * @zh-CN 国际化配置
// * */
// const intl = useIntl();
const { run, loading } = useRequest(addRule, {
manual: true,
onSuccess: () => {
messageApi.success('Added successfully');
reload?.();
},
onError: () => {
messageApi.error('Adding failed, please try again!');
},
});
// const { run, loading } = useRequest(addRule, {
// manual: true,
// onSuccess: () => {
// messageApi.success('Added successfully');
// reload?.();
// },
// onError: () => {
// messageApi.error('Adding failed, please try again!');
// },
// });
return (
<>
{contextHolder}
<ModalForm
title={intl.formatMessage({
id: 'pages.searchTable.createForm.newRule',
defaultMessage: 'New rule',
})}
trigger={
<Button type="primary" icon={<PlusOutlined />}>
<FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
</Button>
}
width="400px"
modalProps={{ okButtonProps: { loading } }}
onFinish={async (value) => {
await run({ data: value as API.RuleListItem });
// return (
// <>
// {contextHolder}
// <ModalForm
// title={intl.formatMessage({
// id: 'pages.searchTable.createForm.newRule',
// defaultMessage: 'New rule',
// })}
// trigger={
// <Button type="primary" icon={<PlusOutlined />}>
// <FormattedMessage id="pages.searchTable.new" defaultMessage="New" />
// </Button>
// }
// width="400px"
// modalProps={{ okButtonProps: { loading } }}
// onFinish={async (value) => {
// await run({ data: value as API.RuleListItem });
return true;
}}
>
<ProFormText
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.ruleName"
defaultMessage="Rule name is required"
/>
),
},
]}
width="md"
name="name"
/>
<ProFormTextArea width="md" name="desc" />
</ModalForm>
</>
);
};
// return true;
// }}
// >
// <ProFormText
// rules={[
// {
// required: true,
// message: (
// <FormattedMessage
// id="pages.searchTable.ruleName"
// defaultMessage="Rule name is required"
// />
// ),
// },
// ]}
// width="md"
// name="name"
// />
// <ProFormTextArea width="md" name="desc" />
// </ModalForm>
// </>
// );
// };
export default CreateForm;
// export default CreateForm;

View File

@@ -1,251 +1,251 @@
import {
ProFormDateTimePicker,
ProFormRadio,
ProFormSelect,
ProFormText,
ProFormTextArea,
StepsForm,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl, useRequest } from '@umijs/max';
import { Modal, message } from 'antd';
import React, { cloneElement, useCallback, useState } from 'react';
import { updateRule } from '@/services/ant-design-pro/api';
// import {
// ProFormDateTimePicker,
// ProFormRadio,
// ProFormSelect,
// ProFormText,
// ProFormTextArea,
// StepsForm,
// } from '@ant-design/pro-components';
// import { FormattedMessage, useIntl, useRequest } from '@umijs/max';
// import { Modal, message } from 'antd';
// import React, { cloneElement, useCallback, useState } from 'react';
// import { updateRule } from '@/services/ant-design-pro/api';
export type FormValueType = {
target?: string;
template?: string;
type?: string;
time?: string;
frequency?: string;
} & Partial<API.RuleListItem>;
// export type FormValueType = {
// target?: string;
// template?: string;
// type?: string;
// time?: string;
// frequency?: string;
// } & Partial<API.RuleListItem>;
export type UpdateFormProps = {
trigger?: React.ReactElement<any>;
onOk?: () => void;
values: Partial<API.RuleListItem>;
};
// export type UpdateFormProps = {
// trigger?: React.ReactElement<any>;
// onOk?: () => void;
// values: Partial<API.RuleListItem>;
// };
const UpdateForm: React.FC<UpdateFormProps> = (props) => {
const { onOk, values, trigger } = props;
// const UpdateForm: React.FC<UpdateFormProps> = (props) => {
// const { onOk, values, trigger } = props;
const intl = useIntl();
// const intl = useIntl();
const [open, setOpen] = useState(false);
// const [open, setOpen] = useState(false);
const [messageApi, contextHolder] = message.useMessage();
// const [messageApi, contextHolder] = message.useMessage();
const { run } = useRequest(updateRule, {
manual: true,
onSuccess: () => {
messageApi.success('Configuration is successful');
onOk?.();
},
onError: () => {
messageApi.error('Configuration failed, please try again!');
},
});
// const { run } = useRequest(updateRule, {
// manual: true,
// onSuccess: () => {
// messageApi.success('Configuration is successful');
// onOk?.();
// },
// onError: () => {
// messageApi.error('Configuration failed, please try again!');
// },
// });
const onCancel = useCallback(() => {
setOpen(false);
}, []);
// const onCancel = useCallback(() => {
// setOpen(false);
// }, []);
const onOpen = useCallback(() => {
setOpen(true);
}, []);
// const onOpen = useCallback(() => {
// setOpen(true);
// }, []);
const onFinish = useCallback(
async (values?: any) => {
await run({ data: values });
// const onFinish = useCallback(
// async (values?: any) => {
// await run({ data: values });
onCancel();
},
[onCancel, run],
);
// onCancel();
// },
// [onCancel, run],
// );
return (
<>
{contextHolder}
{trigger
? cloneElement(trigger, {
onClick: onOpen,
})
: null}
<StepsForm
stepsProps={{
size: 'small',
}}
stepsFormRender={(dom, submitter) => {
return (
<Modal
width={640}
styles={{
body: {
padding: '32px 40px 48px',
},
}}
destroyOnHidden
title={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleConfig',
defaultMessage: '规则配置',
})}
open={open}
footer={submitter}
onCancel={onCancel}
>
{dom}
</Modal>
);
}}
onFinish={onFinish}
>
<StepsForm.StepForm
initialValues={values}
title={intl.formatMessage({
id: 'pages.searchTable.updateForm.basicConfig',
defaultMessage: '基本信息',
})}
>
<ProFormText
name="name"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleName.nameLabel',
defaultMessage: '规则名称',
})}
width="md"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameRules"
defaultMessage="请输入规则名称!"
/>
),
},
]}
/>
<ProFormTextArea
name="desc"
width="md"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleDesc.descLabel',
defaultMessage: '规则描述',
})}
placeholder={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleDesc.descPlaceholder',
defaultMessage: '请输入至少五个字符',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleDesc.descRules"
defaultMessage="请输入至少五个字符的规则描述!"
/>
),
min: 5,
},
]}
/>
</StepsForm.StepForm>
<StepsForm.StepForm
initialValues={{
target: '0',
template: '0',
}}
title={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleProps.title',
defaultMessage: '配置规则属性',
})}
>
<ProFormSelect
name="target"
width="md"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.object',
defaultMessage: '监控对象',
})}
valueEnum={{
0: '表一',
1: '表二',
}}
/>
<ProFormSelect
name="template"
width="md"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleProps.templateLabel',
defaultMessage: '规则模板',
})}
valueEnum={{
0: '规则模板一',
1: '规则模板二',
}}
/>
<ProFormRadio.Group
name="type"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.ruleProps.typeLabel',
defaultMessage: '规则类型',
})}
options={[
{
value: '0',
label: '强',
},
{
value: '1',
label: '弱',
},
]}
/>
</StepsForm.StepForm>
<StepsForm.StepForm
initialValues={{
type: '1',
frequency: 'month',
}}
title={intl.formatMessage({
id: 'pages.searchTable.updateForm.schedulingPeriod.title',
defaultMessage: '设定调度周期',
})}
>
<ProFormDateTimePicker
name="time"
width="md"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.schedulingPeriod.timeLabel',
defaultMessage: '开始时间',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.schedulingPeriod.timeRules"
defaultMessage="请选择开始时间!"
/>
),
},
]}
/>
<ProFormSelect
name="frequency"
label={intl.formatMessage({
id: 'pages.searchTable.updateForm.object',
defaultMessage: '监控对象',
})}
width="md"
valueEnum={{
month: '月',
week: '周',
}}
/>
</StepsForm.StepForm>
</StepsForm>
</>
);
};
// return (
// <>
// {contextHolder}
// {trigger
// ? cloneElement(trigger, {
// onClick: onOpen,
// })
// : null}
// <StepsForm
// stepsProps={{
// size: 'small',
// }}
// stepsFormRender={(dom, submitter) => {
// return (
// <Modal
// width={640}
// styles={{
// body: {
// padding: '32px 40px 48px',
// },
// }}
// destroyOnHidden
// title={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleConfig',
// defaultMessage: '规则配置',
// })}
// open={open}
// footer={submitter}
// onCancel={onCancel}
// >
// {dom}
// </Modal>
// );
// }}
// onFinish={onFinish}
// >
// <StepsForm.StepForm
// initialValues={values}
// title={intl.formatMessage({
// id: 'pages.searchTable.updateForm.basicConfig',
// defaultMessage: '基本信息',
// })}
// >
// <ProFormText
// name="name"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleName.nameLabel',
// defaultMessage: '规则名称',
// })}
// width="md"
// rules={[
// {
// required: true,
// message: (
// <FormattedMessage
// id="pages.searchTable.updateForm.ruleName.nameRules"
// defaultMessage="请输入规则名称!"
// />
// ),
// },
// ]}
// />
// <ProFormTextArea
// name="desc"
// width="md"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleDesc.descLabel',
// defaultMessage: '规则描述',
// })}
// placeholder={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleDesc.descPlaceholder',
// defaultMessage: '请输入至少五个字符',
// })}
// rules={[
// {
// required: true,
// message: (
// <FormattedMessage
// id="pages.searchTable.updateForm.ruleDesc.descRules"
// defaultMessage="请输入至少五个字符的规则描述!"
// />
// ),
// min: 5,
// },
// ]}
// />
// </StepsForm.StepForm>
// <StepsForm.StepForm
// initialValues={{
// target: '0',
// template: '0',
// }}
// title={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleProps.title',
// defaultMessage: '配置规则属性',
// })}
// >
// <ProFormSelect
// name="target"
// width="md"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.object',
// defaultMessage: '监控对象',
// })}
// valueEnum={{
// 0: '表一',
// 1: '表二',
// }}
// />
// <ProFormSelect
// name="template"
// width="md"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleProps.templateLabel',
// defaultMessage: '规则模板',
// })}
// valueEnum={{
// 0: '规则模板一',
// 1: '规则模板二',
// }}
// />
// <ProFormRadio.Group
// name="type"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.ruleProps.typeLabel',
// defaultMessage: '规则类型',
// })}
// options={[
// {
// value: '0',
// label: '强',
// },
// {
// value: '1',
// label: '弱',
// },
// ]}
// />
// </StepsForm.StepForm>
// <StepsForm.StepForm
// initialValues={{
// type: '1',
// frequency: 'month',
// }}
// title={intl.formatMessage({
// id: 'pages.searchTable.updateForm.schedulingPeriod.title',
// defaultMessage: '设定调度周期',
// })}
// >
// <ProFormDateTimePicker
// name="time"
// width="md"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.schedulingPeriod.timeLabel',
// defaultMessage: '开始时间',
// })}
// rules={[
// {
// required: true,
// message: (
// <FormattedMessage
// id="pages.searchTable.updateForm.schedulingPeriod.timeRules"
// defaultMessage="请选择开始时间!"
// />
// ),
// },
// ]}
// />
// <ProFormSelect
// name="frequency"
// label={intl.formatMessage({
// id: 'pages.searchTable.updateForm.object',
// defaultMessage: '监控对象',
// })}
// width="md"
// valueEnum={{
// month: '月',
// week: '周',
// }}
// />
// </StepsForm.StepForm>
// </StepsForm>
// </>
// );
// };
export default UpdateForm;
// export default UpdateForm;

View File

@@ -1,330 +1,330 @@
import type {
ActionType,
ProColumns,
ProDescriptionsItemProps,
} from '@ant-design/pro-components';
import {
FooterToolbar,
PageContainer,
ProDescriptions,
ProTable,
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl, useRequest } from '@umijs/max';
import { Button, Drawer, Input, message } from 'antd';
import React, { useCallback, useRef, useState } from 'react';
import { removeRule, rule } from '@/services/ant-design-pro/api';
import CreateForm from './components/CreateForm';
import UpdateForm from './components/UpdateForm';
// import type {
// ActionType,
// ProColumns,
// ProDescriptionsItemProps,
// } from '@ant-design/pro-components';
// import {
// FooterToolbar,
// PageContainer,
// ProDescriptions,
// ProTable,
// } from '@ant-design/pro-components';
// import { FormattedMessage, useIntl, useRequest } from '@umijs/max';
// import { Button, Drawer, Input, message } from 'antd';
// import React, { useCallback, useRef, useState } from 'react';
// import { removeRule, rule } from '@/services/ant-design-pro/api';
// import CreateForm from './components/CreateForm';
// import UpdateForm from './components/UpdateForm';
const TableList: React.FC = () => {
const actionRef = useRef<ActionType | null>(null);
// const TableList: React.FC = () => {
// const actionRef = useRef<ActionType | null>(null);
const [showDetail, setShowDetail] = useState<boolean>(false);
const [currentRow, setCurrentRow] = useState<API.RuleListItem>();
const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]);
// const [showDetail, setShowDetail] = useState<boolean>(false);
// const [currentRow, setCurrentRow] = useState<API.RuleListItem>();
// const [selectedRowsState, setSelectedRows] = useState<API.RuleListItem[]>([]);
/**
* @en-US International configuration
* @zh-CN 国际化配置
* */
const intl = useIntl();
// /**
// * @en-US International configuration
// * @zh-CN 国际化配置
// * */
// const intl = useIntl();
const [messageApi, contextHolder] = message.useMessage();
// const [messageApi, contextHolder] = message.useMessage();
const { run: delRun, loading } = useRequest(removeRule, {
manual: true,
onSuccess: () => {
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
// const { run: delRun, loading } = useRequest(removeRule, {
// manual: true,
// onSuccess: () => {
// setSelectedRows([]);
// actionRef.current?.reloadAndRest?.();
messageApi.success('Deleted successfully and will refresh soon');
},
onError: () => {
messageApi.error('Delete failed, please try again');
},
});
// messageApi.success('Deleted successfully and will refresh soon');
// },
// onError: () => {
// messageApi.error('Delete failed, please try again');
// },
// });
const columns: ProColumns<API.RuleListItem>[] = [
{
title: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameLabel"
defaultMessage="Rule name"
/>
),
dataIndex: 'name',
render: (dom, entity) => {
return (
<a
onClick={() => {
setCurrentRow(entity);
setShowDetail(true);
}}
>
{dom}
</a>
);
},
},
{
title: (
<FormattedMessage
id="pages.searchTable.titleDesc"
defaultMessage="Description"
/>
),
dataIndex: 'desc',
valueType: 'textarea',
},
{
title: (
<FormattedMessage
id="pages.searchTable.titleCallNo"
defaultMessage="Number of service calls"
/>
),
dataIndex: 'callNo',
sorter: true,
hideInForm: true,
renderText: (val: string) =>
`${val}${intl.formatMessage({
id: 'pages.searchTable.tenThousand',
defaultMessage: ' 万 ',
})}`,
},
{
title: (
<FormattedMessage
id="pages.searchTable.titleStatus"
defaultMessage="Status"
/>
),
dataIndex: 'status',
hideInForm: true,
valueEnum: {
0: {
text: (
<FormattedMessage
id="pages.searchTable.nameStatus.default"
defaultMessage="Shut down"
/>
),
status: 'Default',
},
1: {
text: (
<FormattedMessage
id="pages.searchTable.nameStatus.running"
defaultMessage="Running"
/>
),
status: 'Processing',
},
2: {
text: (
<FormattedMessage
id="pages.searchTable.nameStatus.online"
defaultMessage="Online"
/>
),
status: 'Success',
},
3: {
text: (
<FormattedMessage
id="pages.searchTable.nameStatus.abnormal"
defaultMessage="Abnormal"
/>
),
status: 'Error',
},
},
},
{
title: (
<FormattedMessage
id="pages.searchTable.titleUpdatedAt"
defaultMessage="Last scheduled time"
/>
),
sorter: true,
dataIndex: 'updatedAt',
valueType: 'dateTime',
renderFormItem: (item, { defaultRender, ...rest }, form) => {
const status = form.getFieldValue('status');
if (`${status}` === '0') {
return false;
}
if (`${status}` === '3') {
return (
<Input
{...rest}
placeholder={intl.formatMessage({
id: 'pages.searchTable.exception',
defaultMessage: 'Please enter the reason for the exception!',
})}
/>
);
}
return defaultRender(item);
},
},
{
title: (
<FormattedMessage
id="pages.searchTable.titleOption"
defaultMessage="Operating"
/>
),
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<UpdateForm
trigger={
<a>
<FormattedMessage
id="pages.searchTable.config"
defaultMessage="Configuration"
/>
</a>
}
key="config"
onOk={actionRef.current?.reload}
values={record}
/>,
<a key="subscribeAlert" href="https://procomponents.ant.design/">
<FormattedMessage
id="pages.searchTable.subscribeAlert"
defaultMessage="Subscribe to alerts"
/>
</a>,
],
},
];
// const columns: ProColumns<API.RuleListItem>[] = [
// {
// title: (
// <FormattedMessage
// id="pages.searchTable.updateForm.ruleName.nameLabel"
// defaultMessage="Rule name"
// />
// ),
// dataIndex: 'name',
// render: (dom, entity) => {
// return (
// <a
// onClick={() => {
// setCurrentRow(entity);
// setShowDetail(true);
// }}
// >
// {dom}
// </a>
// );
// },
// },
// {
// title: (
// <FormattedMessage
// id="pages.searchTable.titleDesc"
// defaultMessage="Description"
// />
// ),
// dataIndex: 'desc',
// valueType: 'textarea',
// },
// {
// title: (
// <FormattedMessage
// id="pages.searchTable.titleCallNo"
// defaultMessage="Number of service calls"
// />
// ),
// dataIndex: 'callNo',
// sorter: true,
// hideInForm: true,
// renderText: (val: string) =>
// `${val}${intl.formatMessage({
// id: 'pages.searchTable.tenThousand',
// defaultMessage: ' 万 ',
// })}`,
// },
// {
// title: (
// <FormattedMessage
// id="pages.searchTable.titleStatus"
// defaultMessage="Status"
// />
// ),
// dataIndex: 'status',
// hideInForm: true,
// valueEnum: {
// 0: {
// text: (
// <FormattedMessage
// id="pages.searchTable.nameStatus.default"
// defaultMessage="Shut down"
// />
// ),
// status: 'Default',
// },
// 1: {
// text: (
// <FormattedMessage
// id="pages.searchTable.nameStatus.running"
// defaultMessage="Running"
// />
// ),
// status: 'Processing',
// },
// 2: {
// text: (
// <FormattedMessage
// id="pages.searchTable.nameStatus.online"
// defaultMessage="Online"
// />
// ),
// status: 'Success',
// },
// 3: {
// text: (
// <FormattedMessage
// id="pages.searchTable.nameStatus.abnormal"
// defaultMessage="Abnormal"
// />
// ),
// status: 'Error',
// },
// },
// },
// {
// title: (
// <FormattedMessage
// id="pages.searchTable.titleUpdatedAt"
// defaultMessage="Last scheduled time"
// />
// ),
// sorter: true,
// dataIndex: 'updatedAt',
// valueType: 'dateTime',
// renderFormItem: (item, { defaultRender, ...rest }, form) => {
// const status = form.getFieldValue('status');
// if (`${status}` === '0') {
// return false;
// }
// if (`${status}` === '3') {
// return (
// <Input
// {...rest}
// placeholder={intl.formatMessage({
// id: 'pages.searchTable.exception',
// defaultMessage: 'Please enter the reason for the exception!',
// })}
// />
// );
// }
// return defaultRender(item);
// },
// },
// {
// title: (
// <FormattedMessage
// id="pages.searchTable.titleOption"
// defaultMessage="Operating"
// />
// ),
// dataIndex: 'option',
// valueType: 'option',
// render: (_, record) => [
// <UpdateForm
// trigger={
// <a>
// <FormattedMessage
// id="pages.searchTable.config"
// defaultMessage="Configuration"
// />
// </a>
// }
// key="config"
// onOk={actionRef.current?.reload}
// values={record}
// />,
// <a key="subscribeAlert" href="https://procomponents.ant.design/">
// <FormattedMessage
// id="pages.searchTable.subscribeAlert"
// defaultMessage="Subscribe to alerts"
// />
// </a>,
// ],
// },
// ];
/**
* Delete node
* @zh-CN 删除节点
*
* @param selectedRows
*/
const handleRemove = useCallback(
async (selectedRows: API.RuleListItem[]) => {
if (!selectedRows?.length) {
messageApi.warning('请选择删除项');
// /**
// * Delete node
// * @zh-CN 删除节点
// *
// * @param selectedRows
// */
// const handleRemove = useCallback(
// async (selectedRows: API.RuleListItem[]) => {
// if (!selectedRows?.length) {
// messageApi.warning('请选择删除项');
return;
}
// return;
// }
await delRun({
data: {
key: selectedRows.map((row) => row.key),
},
});
},
[delRun, messageApi.warning],
);
// await delRun({
// data: {
// key: selectedRows.map((row) => row.key),
// },
// });
// },
// [delRun, messageApi.warning],
// );
return (
<PageContainer>
{contextHolder}
<ProTable<API.RuleListItem, API.PageParams>
headerTitle={intl.formatMessage({
id: 'pages.searchTable.title',
defaultMessage: 'Enquiry form',
})}
actionRef={actionRef}
rowKey="key"
search={{
labelWidth: 120,
}}
toolBarRender={() => [
<CreateForm key="create" reload={actionRef.current?.reload} />,
]}
request={rule}
columns={columns}
rowSelection={{
onChange: (_, selectedRows) => {
setSelectedRows(selectedRows);
},
}}
/>
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
<FormattedMessage
id="pages.searchTable.chosen"
defaultMessage="Chosen"
/>{' '}
<a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '}
<FormattedMessage
id="pages.searchTable.item"
defaultMessage="项"
/>
&nbsp;&nbsp;
<span>
<FormattedMessage
id="pages.searchTable.totalServiceCalls"
defaultMessage="Total number of service calls"
/>{' '}
{selectedRowsState.reduce(
(pre, item) => pre + (item.callNo ?? 0),
0,
)}{' '}
<FormattedMessage
id="pages.searchTable.tenThousand"
defaultMessage="万"
/>
</span>
</div>
}
>
<Button
loading={loading}
onClick={() => {
handleRemove(selectedRowsState);
}}
>
<FormattedMessage
id="pages.searchTable.batchDeletion"
defaultMessage="Batch deletion"
/>
</Button>
<Button type="primary">
<FormattedMessage
id="pages.searchTable.batchApproval"
defaultMessage="Batch approval"
/>
</Button>
</FooterToolbar>
)}
// return (
// <PageContainer>
// {contextHolder}
// <ProTable<API.RuleListItem, API.PageParams>
// headerTitle={intl.formatMessage({
// id: 'pages.searchTable.title',
// defaultMessage: 'Enquiry form',
// })}
// actionRef={actionRef}
// rowKey="key"
// search={{
// labelWidth: 120,
// }}
// toolBarRender={() => [
// <CreateForm key="create" reload={actionRef.current?.reload} />,
// ]}
// request={rule}
// columns={columns}
// rowSelection={{
// onChange: (_, selectedRows) => {
// setSelectedRows(selectedRows);
// },
// }}
// />
// {selectedRowsState?.length > 0 && (
// <FooterToolbar
// extra={
// <div>
// <FormattedMessage
// id="pages.searchTable.chosen"
// defaultMessage="Chosen"
// />{' '}
// <a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a>{' '}
// <FormattedMessage
// id="pages.searchTable.item"
// defaultMessage="项"
// />
// &nbsp;&nbsp;
// <span>
// <FormattedMessage
// id="pages.searchTable.totalServiceCalls"
// defaultMessage="Total number of service calls"
// />{' '}
// {selectedRowsState.reduce(
// (pre, item) => pre + (item.callNo ?? 0),
// 0,
// )}{' '}
// <FormattedMessage
// id="pages.searchTable.tenThousand"
// defaultMessage="万"
// />
// </span>
// </div>
// }
// >
// <Button
// loading={loading}
// onClick={() => {
// handleRemove(selectedRowsState);
// }}
// >
// <FormattedMessage
// id="pages.searchTable.batchDeletion"
// defaultMessage="Batch deletion"
// />
// </Button>
// <Button type="primary">
// <FormattedMessage
// id="pages.searchTable.batchApproval"
// defaultMessage="Batch approval"
// />
// </Button>
// </FooterToolbar>
// )}
<Drawer
width={600}
open={showDetail}
onClose={() => {
setCurrentRow(undefined);
setShowDetail(false);
}}
closable={false}
>
{currentRow?.name && (
<ProDescriptions<API.RuleListItem>
column={2}
title={currentRow?.name}
request={async () => ({
data: currentRow || {},
})}
params={{
id: currentRow?.name,
}}
columns={columns as ProDescriptionsItemProps<API.RuleListItem>[]}
/>
)}
</Drawer>
</PageContainer>
);
};
// <Drawer
// width={600}
// open={showDetail}
// onClose={() => {
// setCurrentRow(undefined);
// setShowDetail(false);
// }}
// closable={false}
// >
// {currentRow?.name && (
// <ProDescriptions<API.RuleListItem>
// column={2}
// title={currentRow?.name}
// request={async () => ({
// data: currentRow || {},
// })}
// params={{
// id: currentRow?.name,
// }}
// columns={columns as ProDescriptionsItemProps<API.RuleListItem>[]}
// />
// )}
// </Drawer>
// </PageContainer>
// );
// };
export default TableList;
// export default TableList;

File diff suppressed because it is too large Load Diff

View File

@@ -1,397 +1,283 @@
import {
AlipayCircleOutlined,
AlipayOutlined,
LockOutlined,
MobileOutlined,
TaobaoCircleOutlined,
TaobaoOutlined,
UserOutlined,
WeiboCircleOutlined,
} from '@ant-design/icons';
WeiboOutlined,
} from "@ant-design/icons";
import {
LoginForm,
LoginFormPage,
ProConfigProvider,
ProFormCaptcha,
ProFormCheckbox,
ProFormText,
} from '@ant-design/pro-components';
import {
FormattedMessage,
Helmet,
SelectLang,
useIntl,
useModel,
} from '@umijs/max';
import { Alert, App, Tabs } from 'antd';
import { createStyles } from 'antd-style';
import React, { useState } from 'react';
import { flushSync } from 'react-dom';
import { Footer } from '@/components';
import { login } from '@/services/ant-design-pro/api';
import { getFakeCaptcha } from '@/services/ant-design-pro/login';
import Settings from '../../../../config/defaultSettings';
} from "@ant-design/pro-components";
import { Button, Divider, Space, Tabs, message, theme } from "antd";
import type { CSSProperties } from "react";
import { useState } from "react";
const useStyles = createStyles(({ token }) => {
return {
action: {
marginLeft: '8px',
color: 'rgba(0, 0, 0, 0.2)',
fontSize: '24px',
verticalAlign: 'middle',
cursor: 'pointer',
transition: 'color 0.3s',
'&:hover': {
color: token.colorPrimaryActive,
},
},
lang: {
width: 42,
height: 42,
lineHeight: '42px',
position: 'fixed',
right: 16,
borderRadius: token.borderRadius,
':hover': {
backgroundColor: token.colorBgTextHover,
},
},
container: {
display: 'flex',
flexDirection: 'column',
height: '100vh',
overflow: 'auto',
backgroundImage:
"url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
backgroundSize: '100% 100%',
},
};
});
type LoginType = "phone" | "account";
const ActionIcons = () => {
const { styles } = useStyles();
return (
<>
<AlipayCircleOutlined
key="AlipayCircleOutlined"
className={styles.action}
/>
<TaobaoCircleOutlined
key="TaobaoCircleOutlined"
className={styles.action}
/>
<WeiboCircleOutlined
key="WeiboCircleOutlined"
className={styles.action}
/>
</>
);
const iconStyles: CSSProperties = {
color: "rgba(0, 0, 0, 0.2)",
fontSize: "18px",
verticalAlign: "middle",
cursor: "pointer",
};
const Lang = () => {
const { styles } = useStyles();
const Page = () => {
const [loginType, setLoginType] = useState<LoginType>("phone");
const { token } = theme.useToken();
return (
<div className={styles.lang} data-lang>
{SelectLang && <SelectLang />}
</div>
);
};
const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => {
return (
<Alert
<div
style={{
marginBottom: 24,
backgroundColor: "white",
height: "100vh",
}}
message={content}
type="error"
showIcon
/>
);
};
const Login: React.FC = () => {
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
const [type, setType] = useState<string>('account');
const { initialState, setInitialState } = useModel('@@initialState');
const { styles } = useStyles();
const { message } = App.useApp();
const intl = useIntl();
const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) {
flushSync(() => {
setInitialState((s) => ({
...s,
currentUser: userInfo,
}));
});
}
};
const handleSubmit = async (values: API.LoginParams) => {
try {
// 登录
const msg = await login({ ...values, type });
if (msg.status === 'ok') {
const defaultLoginSuccessMessage = intl.formatMessage({
id: 'pages.login.success',
defaultMessage: '登录成功!',
});
message.success(defaultLoginSuccessMessage);
await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams;
window.location.href = urlParams.get('redirect') || '/';
return;
}
console.log(msg);
// 如果失败去设置用户错误信息
setUserLoginState(msg);
} catch (error) {
const defaultLoginFailureMessage = intl.formatMessage({
id: 'pages.login.failure',
defaultMessage: '登录失败,请重试!',
});
console.log(error);
message.error(defaultLoginFailureMessage);
}
};
const { status, type: loginType } = userLoginState;
return (
<div className={styles.container}>
<Helmet>
<title>
{intl.formatMessage({
id: 'menu.login',
defaultMessage: '登录页',
})}
{Settings.title && ` - ${Settings.title}`}
</title>
</Helmet>
<Lang />
<div
style={{
flex: '1',
padding: '32px 0',
>
<LoginFormPage
backgroundImageUrl="https://mdn.alipayobjects.com/huamei_gcee1x/afts/img/A*y0ZTS6WLwvgAAAAAAAAAAAAADml6AQ/fmt.webp"
logo="https://github.githubassets.com/favicons/favicon.png"
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
title="Github"
containerStyle={{
backgroundColor: "rgba(0, 0, 0,0.65)",
backdropFilter: "blur(4px)",
}}
>
<LoginForm
contentStyle={{
minWidth: 280,
maxWidth: '75vw',
}}
logo={<img alt="logo" src="/logo.svg" />}
title="Ant Design"
subTitle={intl.formatMessage({
id: 'pages.layouts.userLayout.title',
})}
initialValues={{
autoLogin: true,
}}
actions={[
<FormattedMessage
key="loginWith"
id="pages.login.loginWith"
defaultMessage="其他登录方式"
/>,
<ActionIcons key="icons" />,
]}
onFinish={async (values) => {
await handleSubmit(values as API.LoginParams);
}}
>
<Tabs
activeKey={type}
onChange={setType}
centered
items={[
{
key: 'account',
label: intl.formatMessage({
id: 'pages.login.accountLogin.tab',
defaultMessage: '账户密码登录',
}),
},
{
key: 'mobile',
label: intl.formatMessage({
id: 'pages.login.phoneLogin.tab',
defaultMessage: '手机号登录',
}),
},
]}
/>
{status === 'error' && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/ant.design)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin or user',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: ant.design',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
</>
)}
{status === 'error' && loginType === 'mobile' && (
<LoginMessage content="验证码错误" />
)}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
}
return intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
},
]}
onGetCaptcha={async (phone) => {
const result = await getFakeCaptcha({
phone,
});
if (!result) {
return;
}
message.success('获取验证码成功验证码为1234');
}}
/>
</>
)}
<div
style={{
marginBottom: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage
id="pages.login.rememberMe"
defaultMessage="自动登录"
/>
</ProFormCheckbox>
<a
subTitle="全球最大的代码托管平台"
activityConfig={{
style: {
boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)",
color: token.colorTextHeading,
borderRadius: 8,
backgroundColor: "rgba(255,255,255,0.25)",
backdropFilter: "blur(4px)",
},
title: "活动标题,可配置图片",
subTitle: "活动介绍说明文字",
action: (
<Button
size="large"
style={{
float: 'right',
borderRadius: 20,
background: token.colorBgElevated,
color: token.colorPrimary,
width: 120,
}}
>
<FormattedMessage
id="pages.login.forgotPassword"
defaultMessage="忘记密码"
/>
</a>
</Button>
),
}}
actions={
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
}}
>
<Divider plain>
<span
style={{
color: token.colorTextPlaceholder,
fontWeight: "normal",
fontSize: 14,
}}
>
</span>
</Divider>
<Space align="center" size={24}>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: "1px solid " + token.colorPrimaryBorder,
borderRadius: "50%",
}}
>
<AlipayOutlined style={{ ...iconStyles, color: "#1677FF" }} />
</div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: "1px solid " + token.colorPrimaryBorder,
borderRadius: "50%",
}}
>
<TaobaoOutlined style={{ ...iconStyles, color: "#FF6A10" }} />
</div>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
height: 40,
width: 40,
border: "1px solid " + token.colorPrimaryBorder,
borderRadius: "50%",
}}
>
<WeiboOutlined style={{ ...iconStyles, color: "#1890ff" }} />
</div>
</Space>
</div>
</LoginForm>
</div>
<Footer />
}
>
<Tabs
centered
activeKey={loginType}
onChange={(activeKey) => setLoginType(activeKey as LoginType)}
>
<Tabs.TabPane key={"account"} tab={"账号密码登录"} />
<Tabs.TabPane key={"phone"} tab={"手机号登录"} />
</Tabs>
{loginType === "account" && (
<>
<ProFormText
name="username"
fieldProps={{
size: "large",
prefix: (
<UserOutlined
style={{
color: token.colorText,
}}
className={"prefixIcon"}
/>
),
}}
placeholder={"用户名: admin or user"}
rules={[
{
required: true,
message: "请输入用户名!",
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: "large",
prefix: (
<LockOutlined
style={{
color: token.colorText,
}}
className={"prefixIcon"}
/>
),
}}
placeholder={"密码: ant.design"}
rules={[
{
required: true,
message: "请输入密码!",
},
]}
/>
</>
)}
{loginType === "phone" && (
<>
<ProFormText
fieldProps={{
size: "large",
prefix: (
<MobileOutlined
style={{
color: token.colorText,
}}
className={"prefixIcon"}
/>
),
}}
name="mobile"
placeholder={"手机号"}
rules={[
{
required: true,
message: "请输入手机号!",
},
{
pattern: /^1\d{10}$/,
message: "手机号格式错误!",
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: "large",
prefix: (
<LockOutlined
style={{
color: token.colorText,
}}
className={"prefixIcon"}
/>
),
}}
captchaProps={{
size: "large",
}}
placeholder={"请输入验证码"}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${"获取验证码"}`;
}
return "获取验证码";
}}
name="captcha"
rules={[
{
required: true,
message: "请输入验证码!",
},
]}
onGetCaptcha={async () => {
message.success("获取验证码成功验证码为1234");
}}
/>
</>
)}
<div
style={{
marginBlockEnd: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
</ProFormCheckbox>
<a
style={{
float: "right",
}}
>
</a>
</div>
</LoginFormPage>
</div>
);
};
export default Login;
export default () => {
return (
<ProConfigProvider dark>
<Page />
</ProConfigProvider>
);
};

View File

@@ -1,102 +0,0 @@
// @ts-ignore
import { startMock } from '@@/requestRecordMock';
import { TestBrowser } from '@@/testBrowser';
import { fireEvent, render } from '@testing-library/react';
import React, { act } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
let server: {
close: () => void;
};
describe('Login Page', () => {
beforeAll(async () => {
server = await startMock({
port: 8000,
scene: 'login',
});
});
afterAll(() => {
server?.close();
});
it('should show login form', async () => {
const historyRef = React.createRef<any>();
const rootContainer = render(
<TestBrowser
historyRef={historyRef}
location={{
pathname: '/user/login',
}}
/>,
);
await rootContainer.findAllByText('Ant Design');
act(() => {
historyRef.current?.push('/user/login');
});
expect(
rootContainer.baseElement?.querySelector('.ant-pro-form-login-desc')
?.textContent,
).toBe(
'Ant Design is the most influential web design specification in Xihu district',
);
expect(rootContainer.asFragment()).toMatchSnapshot();
rootContainer.unmount();
});
it('should login success', async () => {
const historyRef = React.createRef<any>();
const rootContainer = render(
<TestBrowser
historyRef={historyRef}
location={{
pathname: '/user/login',
}}
/>,
);
await rootContainer.findAllByText('Ant Design');
const userNameInput = await rootContainer.findByPlaceholderText(
'Username: admin or user',
);
act(() => {
fireEvent.change(userNameInput, { target: { value: 'admin' } });
});
const passwordInput = await rootContainer.findByPlaceholderText(
'Password: ant.design',
);
act(() => {
fireEvent.change(passwordInput, { target: { value: 'ant.design' } });
});
await (await rootContainer.findByText('Login')).click();
// 等待接口返回结果
await waitTime(5000);
await rootContainer.findAllByText('Ant Design Pro');
expect(rootContainer.asFragment()).toMatchSnapshot();
await waitTime(2000);
rootContainer.unmount();
});
});