refactor(components): 调整组件导入路径并新增验证码组件
- 将多个组件中的 @chageable/components 导入改为从 @/components 导入 - 在 BoxBrandList.tsx、CostList.tsx、ProductDataList.tsx 等文件中更新 ProFormBizSelect 相关组件导入 - 在 CaptchaModal/index.tsx 中将 Captcha 组件导入从 @chageable/components 改为 @/components - 在 ChannelList.tsx、CompanyList.tsx、EmployeeList.tsx 等文件中更新 ProFormUploadMaterial 导入 - 在 MaterialList.tsx 中更新 ProFormBizTreeSelect 和 ProFormUploadOss 导入 - 在 MenuList.tsx、OrderCostList.tsx 中更新 ProFormBizSelect 相关组件导入 - 在 DealerModal.tsx、MaterialModal.tsx、OrderModal.tsx 等文件中更新 SelectModal 导入 - 在 app.tsx 中将 LeftMenu 导入从 @chageable/components 改为 @/components - 新增 UploadMaterial 组件并添加到 components/index.ts 导出 - 在 Captcha 组件中添加滑动验证码功能,包含样式和交互逻辑 - 在 companyPaymentAccount 工具函数中添加 branchName 字段支持
This commit is contained in:
parent
4bb4dd0ec4
commit
1429319b01
@ -24,7 +24,6 @@
|
|||||||
"@ant-design/pro-components": "^2.8.6",
|
"@ant-design/pro-components": "^2.8.6",
|
||||||
"@ant-design/pro-editor": "^1.3.0",
|
"@ant-design/pro-editor": "^1.3.0",
|
||||||
"@ant-design/x": "^1.1.0",
|
"@ant-design/x": "^1.1.0",
|
||||||
"@chageable/components": "workspace:^",
|
|
||||||
"@formily/antd-v5": "^1.2.3",
|
"@formily/antd-v5": "^1.2.3",
|
||||||
"@formily/core": "^2.3.2",
|
"@formily/core": "^2.3.2",
|
||||||
"@formily/react": "^2.3.2",
|
"@formily/react": "^2.3.2",
|
||||||
|
|||||||
@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||||
import { VersionChecker } from '@/components';
|
import { VersionChecker, LeftMenu } from '@/components';
|
||||||
import Avatar from '@/layout/guide/Avatar';
|
import Avatar from '@/layout/guide/Avatar';
|
||||||
import { auth } from '@/services';
|
import { auth } from '@/services';
|
||||||
import { Navigate } from '@@/exports';
|
import { Navigate } from '@@/exports';
|
||||||
import { RunTimeLayoutConfig } from '@@/plugin-layout/types';
|
import { RunTimeLayoutConfig } from '@@/plugin-layout/types';
|
||||||
import { RequestConfig } from '@@/plugin-request/request';
|
import { RequestConfig } from '@@/plugin-request/request';
|
||||||
import { InfoCircleFilled } from '@ant-design/icons';
|
import { InfoCircleFilled } from '@ant-design/icons';
|
||||||
import { LeftMenu } from '@chageable/components';
|
|
||||||
import '@nutui/nutui-react/dist/style.scss';
|
import '@nutui/nutui-react/dist/style.scss';
|
||||||
import { history } from '@umijs/max';
|
import { history } from '@umijs/max';
|
||||||
import { Alert, Button, message, notification, Spin } from 'antd';
|
import { Alert, Button, message, notification, Spin } from 'antd';
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import {
|
|||||||
BoxSpecList,
|
BoxSpecList,
|
||||||
MaterialList,
|
MaterialList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormBizSelect,
|
||||||
|
ProFormBizSelectHandles,
|
||||||
|
ProFormUploadMaterial,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
@ -15,11 +18,6 @@ import {
|
|||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProListMetas } from '@ant-design/pro-list';
|
import { ProListMetas } from '@ant-design/pro-list';
|
||||||
import {
|
|
||||||
ProFormBizSelect,
|
|
||||||
ProFormBizSelectHandles,
|
|
||||||
ProFormUploadMaterial,
|
|
||||||
} from '@chageable/components';
|
|
||||||
import { Image } from 'antd';
|
import { Image } from 'antd';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import {
|
|||||||
BizValueType,
|
BizValueType,
|
||||||
CostItemList,
|
CostItemList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormBizSelect,
|
||||||
|
ProFormBizSelectHandles,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import groupby from '@/utils/groupby';
|
import groupby from '@/utils/groupby';
|
||||||
@ -16,10 +18,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import {
|
|
||||||
ProFormBizSelect,
|
|
||||||
ProFormBizSelectHandles,
|
|
||||||
} from '@chageable/components';
|
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
interface ICostListProps {
|
interface ICostListProps {
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import {
|
|||||||
BizValueType,
|
BizValueType,
|
||||||
CostList,
|
CostList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormBizSelect,
|
||||||
|
ProFormBizSelectHandles,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import groupby from '@/utils/groupby';
|
import groupby from '@/utils/groupby';
|
||||||
@ -14,10 +16,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import {
|
|
||||||
ProFormBizSelect,
|
|
||||||
ProFormBizSelectHandles,
|
|
||||||
} from '@chageable/components';
|
|
||||||
import { Space, Typography } from 'antd';
|
import { Space, Typography } from 'antd';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import auth from '@/services/auth';
|
import auth from '@/services/auth';
|
||||||
import { Captcha, CheckCaptchaProps } from '@chageable/components';
|
import { Captcha, CheckCaptchaProps } from '@/components';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface ICaptchaModalProps {
|
interface ICaptchaModalProps {
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { BizContainer, BizValueType, MaterialList } from '@/components';
|
import {
|
||||||
|
BizContainer,
|
||||||
|
BizValueType,
|
||||||
|
MaterialList,
|
||||||
|
ProFormUploadMaterial,
|
||||||
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { useIntl } from '@@/exports';
|
import { useIntl } from '@@/exports';
|
||||||
@ -9,7 +14,6 @@ import {
|
|||||||
ProFormTextArea,
|
ProFormTextArea,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
export default function ChannelList() {
|
export default function ChannelList() {
|
||||||
|
|||||||
@ -3,13 +3,13 @@ import {
|
|||||||
BizValueType,
|
BizValueType,
|
||||||
CompanyPaymentAccountList,
|
CompanyPaymentAccountList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormUploadMaterial,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { useIntl } from '@@/exports';
|
import { useIntl } from '@@/exports';
|
||||||
import { ProColumns, ProFormText } from '@ant-design/pro-components';
|
import { ProColumns, ProFormText } from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface ICompanyListProps {
|
interface ICompanyListProps {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
LightFilter,
|
LightFilter,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
import { SelectModal } from '@/components';
|
||||||
import { Alert, ModalProps, Row, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DealerList } from '@/components';
|
import { DealerList, SelectModal } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { pagination } from '@/utils/pagination';
|
import { pagination } from '@/utils/pagination';
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
LightFilter,
|
LightFilter,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
|
||||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useIntl } from '@@/exports';
|
import { useIntl } from '@@/exports';
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
EmployeeRoleUpdate,
|
EmployeeRoleUpdate,
|
||||||
MaterialList,
|
MaterialList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormUploadMaterial,
|
||||||
RestPassword,
|
RestPassword,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
|
|
||||||
export interface IEmployeeListProps {
|
export interface IEmployeeListProps {
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { ProFormItemProps, ProFormText } from '@ant-design/pro-components';
|
||||||
|
import { IAddressProps, TMapModal } from '@/components';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import useFormInstance from 'antd/es/form/hooks/useFormInstance';
|
||||||
|
import React from 'react';
|
||||||
|
import '../index.less';
|
||||||
|
|
||||||
|
interface ProFormUploadMaterialProps extends ProFormItemProps {
|
||||||
|
fieldProps?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProFormTMap: React.FC<ProFormUploadMaterialProps> = ({ ...rest }) => {
|
||||||
|
const formInstance = useFormInstance();
|
||||||
|
console.log('form', formInstance.getFieldsValue());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProFormText
|
||||||
|
{...rest}
|
||||||
|
addonAfter={
|
||||||
|
<TMapModal
|
||||||
|
trigger={<Button type={'primary'}>选择地址</Button>}
|
||||||
|
onFinish={(address?: IAddressProps) => {
|
||||||
|
formInstance?.setFieldValue('address', address?.poiaddress);
|
||||||
|
formInstance?.setFieldValue('latlng', address?.latlng);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ProFormTMap;
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
ProFormDependency,
|
||||||
|
ProFormField,
|
||||||
|
ProFormItemProps,
|
||||||
|
ProTableProps,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { UploadMaterial } from '@/components';
|
||||||
|
import React, { MutableRefObject } from 'react';
|
||||||
|
|
||||||
|
interface ProFormUploadMaterialProps extends ProFormItemProps {
|
||||||
|
fieldProps: {
|
||||||
|
maxCount: number;
|
||||||
|
onChange?: (fileList: any[]) => void;
|
||||||
|
fileList?: any[];
|
||||||
|
request: ProTableProps<any, any>['request'];
|
||||||
|
toolBarRender?: ProTableProps<any, any>['toolBarRender'];
|
||||||
|
actionRef?: MutableRefObject<ActionType | undefined>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProFormUploadMaterial: React.FC<ProFormUploadMaterialProps> = ({
|
||||||
|
fieldProps,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ProFormDependency name={[rest.name]}>
|
||||||
|
{(data, form) => (
|
||||||
|
<ProFormField {...rest}>
|
||||||
|
<UploadMaterial
|
||||||
|
onChange={(fileList) => {
|
||||||
|
form.setFieldValue(
|
||||||
|
rest.name,
|
||||||
|
fileList?.map((file: any) => file.url),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
fileList={data[rest.name]?.map((url: string, index: number) => {
|
||||||
|
return {
|
||||||
|
uid: index.toString(),
|
||||||
|
name: url,
|
||||||
|
// @ts-ignore
|
||||||
|
status: 'success',
|
||||||
|
thumbUrl: url,
|
||||||
|
url: url,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
{...fieldProps}
|
||||||
|
/>
|
||||||
|
</ProFormField>
|
||||||
|
)}
|
||||||
|
</ProFormDependency>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ProFormUploadMaterial;
|
||||||
@ -3,6 +3,9 @@ import {
|
|||||||
BizValueType,
|
BizValueType,
|
||||||
MaterialCategoryList,
|
MaterialCategoryList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormBizTreeSelect,
|
||||||
|
ProFormBizTreeSelectHandles,
|
||||||
|
ProFormUploadOss,
|
||||||
Remark,
|
Remark,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
@ -12,11 +15,6 @@ import {
|
|||||||
ProFormDependency,
|
ProFormDependency,
|
||||||
ProFormRadio,
|
ProFormRadio,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import {
|
|
||||||
ProFormBizTreeSelect,
|
|
||||||
ProFormBizTreeSelectHandles,
|
|
||||||
ProFormUploadOss,
|
|
||||||
} from '@chageable/components';
|
|
||||||
import { useModel } from '@umijs/max';
|
import { useModel } from '@umijs/max';
|
||||||
import { TreeSelect } from 'antd';
|
import { TreeSelect } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
@ -305,7 +303,7 @@ export default function MaterialList(props: IMaterialListProps) {
|
|||||||
const ossToken = await getOssToken();
|
const ossToken = await getOssToken();
|
||||||
const client = new OSS({
|
const client = new OSS({
|
||||||
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
|
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
|
||||||
region: "oss-" + ossToken?.region,
|
region: 'oss-' + ossToken?.region,
|
||||||
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
||||||
accessKeyId: ossToken?.accessKeyId,
|
accessKeyId: ossToken?.accessKeyId,
|
||||||
accessKeySecret: ossToken?.accessKeySecret,
|
accessKeySecret: ossToken?.accessKeySecret,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Remark } from '@/components';
|
import { Remark, SelectModal } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { pagination } from '@/utils/pagination';
|
import { pagination } from '@/utils/pagination';
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormTreeSelect,
|
ProFormTreeSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
|
||||||
import { ModalProps, TreeSelect } from 'antd';
|
import { ModalProps, TreeSelect } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import {
|
|||||||
IconPicker,
|
IconPicker,
|
||||||
ModeType,
|
ModeType,
|
||||||
PermissionList,
|
PermissionList,
|
||||||
|
ProFormBizSelect,
|
||||||
|
ProFormBizSelectHandles,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import useSearch from '@/models/useSearch';
|
import useSearch from '@/models/useSearch';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
@ -18,10 +20,6 @@ import {
|
|||||||
ProFormTreeSelect,
|
ProFormTreeSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import {
|
|
||||||
ProFormBizSelect,
|
|
||||||
ProFormBizSelectHandles,
|
|
||||||
} from '@chageable/components';
|
|
||||||
import { TreeSelect } from 'antd';
|
import { TreeSelect } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import {
|
|||||||
CostList,
|
CostList,
|
||||||
ModeType,
|
ModeType,
|
||||||
OrderSelect,
|
OrderSelect,
|
||||||
|
ProFormBizSelect,
|
||||||
|
ProFormBizSelectHandles,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import groupby from '@/utils/groupby';
|
import groupby from '@/utils/groupby';
|
||||||
@ -16,8 +18,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProFormBizSelect } from '@chageable/components';
|
|
||||||
import { ProFormBizSelectHandles } from '@chageable/components/src';
|
|
||||||
import { Button, Space } from 'antd';
|
import { Button, Space } from 'antd';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -8,10 +8,9 @@ import {
|
|||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
import { OrderFormItem, OrderList, SelectModal } from '@/components';
|
||||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { OrderFormItem, OrderList } from '@/components';
|
|
||||||
|
|
||||||
export interface IOrderModalProps extends ModalProps {
|
export interface IOrderModalProps extends ModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -142,16 +141,12 @@ export default function OrderModal(props: IOrderModalProps) {
|
|||||||
|
|
||||||
function setDealerVOStorage(orderVO: BusinessAPI.OrderVO) {
|
function setDealerVOStorage(orderVO: BusinessAPI.OrderVO) {
|
||||||
const localOrderList = localStorage.getItem(sessionKey);
|
const localOrderList = localStorage.getItem(sessionKey);
|
||||||
const orderList = localOrderList
|
const orderList = localOrderList ? JSON.parse(localOrderList) : [];
|
||||||
? JSON.parse(localOrderList)
|
orderList.forEach((item: BusinessAPI.OrderVO, index: number) => {
|
||||||
: [];
|
|
||||||
orderList.forEach(
|
|
||||||
(item: BusinessAPI.OrderVO, index: number) => {
|
|
||||||
if (item.orderId === orderVO.orderId) {
|
if (item.orderId === orderVO.orderId) {
|
||||||
orderList.splice(index, 1);
|
orderList.splice(index, 1);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
if (orderList.length < 5) {
|
if (orderList.length < 5) {
|
||||||
orderList.unshift(orderVO);
|
orderList.unshift(orderVO);
|
||||||
localStorage.setItem(sessionKey, JSON.stringify(orderList));
|
localStorage.setItem(sessionKey, JSON.stringify(orderList));
|
||||||
@ -189,13 +184,8 @@ export default function OrderModal(props: IOrderModalProps) {
|
|||||||
...params,
|
...params,
|
||||||
},
|
},
|
||||||
request: async (params, sorter, filter) => {
|
request: async (params, sorter, filter) => {
|
||||||
const { data, success, totalCount } =
|
const { data, success, totalCount } = await business.order.pageOrder({
|
||||||
await business.order.pageOrder({
|
orderPageQry: formatParam<typeof params>(params, sorter, filter),
|
||||||
orderPageQry: formatParam<typeof params>(
|
|
||||||
params,
|
|
||||||
sorter,
|
|
||||||
filter,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -210,10 +200,7 @@ export default function OrderModal(props: IOrderModalProps) {
|
|||||||
},
|
},
|
||||||
tableAlertRender: ({ selectedRowKeys, selectedRows }) => {
|
tableAlertRender: ({ selectedRowKeys, selectedRows }) => {
|
||||||
// selectedRows 和 selectedList 组合在一起,去重,
|
// selectedRows 和 selectedList 组合在一起,去重,
|
||||||
const selectedRowsMap = new Map<
|
const selectedRowsMap = new Map<string, BusinessAPI.OrderVO>();
|
||||||
string,
|
|
||||||
BusinessAPI.OrderVO
|
|
||||||
>();
|
|
||||||
selectedRows.forEach((item: BusinessAPI.OrderVO) => {
|
selectedRows.forEach((item: BusinessAPI.OrderVO) => {
|
||||||
if (item) {
|
if (item) {
|
||||||
if (!selectedRowsMap.has(item.orderId)) {
|
if (!selectedRowsMap.has(item.orderId)) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { CompanyList, OrderList } from '@/components';
|
import { CompanyList, OrderList, SelectModal } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatBankCard, formatIdCard, formatPhone } from '@/utils/format';
|
import { formatBankCard, formatIdCard, formatPhone } from '@/utils/format';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
@ -11,7 +11,6 @@ import {
|
|||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormSelect,
|
ProFormSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
|
||||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
InsertPosition,
|
InsertPosition,
|
||||||
MaterialList,
|
MaterialList,
|
||||||
OrderSupplierInvoiceList,
|
OrderSupplierInvoiceList,
|
||||||
|
ProFormUploadMaterial,
|
||||||
SupplierFarmerList,
|
SupplierFarmerList,
|
||||||
SupplierInvoiceList,
|
SupplierInvoiceList,
|
||||||
SupplierSelect,
|
SupplierSelect,
|
||||||
@ -24,7 +25,6 @@ import {
|
|||||||
RouteContext,
|
RouteContext,
|
||||||
RouteContextType,
|
RouteContextType,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import { Col, Row, Space, Table } from 'antd';
|
import { Col, Row, Space, Table } from 'antd';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { BizEditor, ButtonAccess, MaterialList } from '@/components';
|
import {
|
||||||
|
BizEditor,
|
||||||
|
ButtonAccess,
|
||||||
|
MaterialList,
|
||||||
|
ProFormUploadMaterial,
|
||||||
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { formLayout } from '@/utils/formLayout';
|
import { formLayout } from '@/utils/formLayout';
|
||||||
@ -11,7 +16,6 @@ import {
|
|||||||
RouteContext,
|
RouteContext,
|
||||||
RouteContextType,
|
RouteContextType,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import { message, Space, Typography } from 'antd';
|
import { message, Space, Typography } from 'antd';
|
||||||
|
|
||||||
interface IChargingPilePurchaseConfigProps {
|
interface IChargingPilePurchaseConfigProps {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { MaterialList } from '@/components';
|
import { MaterialList, ProFormUploadMaterial } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { formLayout } from '@/utils/formLayout';
|
import { formLayout } from '@/utils/formLayout';
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
ProFormUploadDragger,
|
ProFormUploadDragger,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import { Col, message, Row, Space } from 'antd';
|
import { Col, message, Row, Space } from 'antd';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { MaterialList } from '@/components';
|
import { MaterialList, ProFormUploadMaterial } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { formLayout } from '@/utils/formLayout';
|
import { formLayout } from '@/utils/formLayout';
|
||||||
import { ActionType, ProForm, ProFormText } from '@ant-design/pro-components';
|
import { ActionType, ProForm, ProFormText } from '@ant-design/pro-components';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import { Col, message, Row, Space } from 'antd';
|
import { Col, message, Row, Space } from 'antd';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
BizValueType,
|
BizValueType,
|
||||||
MaterialList,
|
MaterialList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormUploadMaterial,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatBankCard, formatIdCard, formatPhone } from '@/utils/format';
|
import { formatBankCard, formatIdCard, formatPhone } from '@/utils/format';
|
||||||
@ -15,7 +16,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
interface ISupplierFarmerListProps {
|
interface ISupplierFarmerListProps {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
ProColumns,
|
ProColumns,
|
||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
import { SelectModal } from '@/components';
|
||||||
import { Alert, ModalProps, Row, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
BizValueType,
|
BizValueType,
|
||||||
MaterialList,
|
MaterialList,
|
||||||
ModeType,
|
ModeType,
|
||||||
|
ProFormUploadMaterial,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatBankCard, formatPhone } from '@/utils/format';
|
import { formatBankCard, formatPhone } from '@/utils/format';
|
||||||
@ -15,7 +16,6 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||||
import { ProFormUploadMaterial } from '@chageable/components';
|
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
interface ISupplierStallListProps {
|
interface ISupplierStallListProps {
|
||||||
|
|||||||
128
packages/app-operation/src/components/UploadMaterial/index.tsx
Normal file
128
packages/app-operation/src/components/UploadMaterial/index.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import { ActionType, ProTableProps } from '@ant-design/pro-components';
|
||||||
|
import { SelectModal } from '@/components';
|
||||||
|
import { Upload } from 'antd';
|
||||||
|
import React, { MutableRefObject, useEffect, useState } from 'react';
|
||||||
|
import useStyle from './style.style';
|
||||||
|
|
||||||
|
export interface UploadMaterialProps {
|
||||||
|
maxCount: number;
|
||||||
|
onChange: (fileList: any[]) => void;
|
||||||
|
fileList?: any[];
|
||||||
|
request: ProTableProps<any, any>['request'];
|
||||||
|
toolBarRender?: ProTableProps<any, any>['toolBarRender'];
|
||||||
|
actionRef?: MutableRefObject<ActionType | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UploadMaterial: React.FC<UploadMaterialProps> = (props) => {
|
||||||
|
const {
|
||||||
|
maxCount,
|
||||||
|
fileList: initialFileList,
|
||||||
|
onChange,
|
||||||
|
request,
|
||||||
|
toolBarRender,
|
||||||
|
actionRef,
|
||||||
|
} = props;
|
||||||
|
const [fileList, setFileList] = useState<any[]>([]);
|
||||||
|
const { styles } = useStyle();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFileList(initialFileList || []);
|
||||||
|
}, [initialFileList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Upload
|
||||||
|
listType={'picture-card'}
|
||||||
|
fileList={fileList}
|
||||||
|
onRemove={(file) => {
|
||||||
|
const index = fileList.indexOf(file);
|
||||||
|
const newFileList = fileList.slice();
|
||||||
|
newFileList.splice(index, 1);
|
||||||
|
setFileList(newFileList);
|
||||||
|
onChange(newFileList);
|
||||||
|
}}
|
||||||
|
showUploadList={true}
|
||||||
|
multiple={false}
|
||||||
|
accept={'image/*'}
|
||||||
|
maxCount={maxCount || 1}
|
||||||
|
openFileDialogOnClick={false}
|
||||||
|
>
|
||||||
|
{fileList.length >= (maxCount || 1) ? null : (
|
||||||
|
<div className={styles.uploadImage} onClick={() => setOpen(true)}>
|
||||||
|
<div className={styles.uploadImageIcon}>
|
||||||
|
<UploadOutlined /> 单击上传
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Upload>
|
||||||
|
|
||||||
|
{/* 选择图片 */}
|
||||||
|
<SelectModal
|
||||||
|
rowKey={'materialId'}
|
||||||
|
modalProps={{
|
||||||
|
title: '选择图片',
|
||||||
|
open: open,
|
||||||
|
onOk: () => setOpen(false),
|
||||||
|
onCancel: () => setOpen(false),
|
||||||
|
}}
|
||||||
|
tableProps={{
|
||||||
|
actionRef: actionRef,
|
||||||
|
toolBarRender: toolBarRender,
|
||||||
|
rowKey: 'materialId',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: '素材内容',
|
||||||
|
dataIndex: 'url',
|
||||||
|
valueType: 'image',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '素材名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '素材分类',
|
||||||
|
dataIndex: ['categoryVO', 'name'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
params: {
|
||||||
|
materialPageQry: {
|
||||||
|
type: 'FILE_IMAGE',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request: request,
|
||||||
|
pagination: {
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (total: number) => `共 ${total} 条`,
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
defaultPageSize: 10,
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
position: ['bottomRight'],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onFinish={(materialList) => {
|
||||||
|
const imageList = materialList.map((materialVO) => {
|
||||||
|
return {
|
||||||
|
uid: materialVO?.materialId,
|
||||||
|
name: materialVO?.name,
|
||||||
|
// @ts-ignore
|
||||||
|
status: 'success',
|
||||||
|
thumbUrl: materialVO?.url,
|
||||||
|
url: materialVO?.url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const newFileList = [...fileList, ...imageList];
|
||||||
|
setFileList(newFileList);
|
||||||
|
|
||||||
|
onChange(newFileList);
|
||||||
|
}}
|
||||||
|
num={(maxCount || 1) - fileList.length}
|
||||||
|
type={'checkbox'}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UploadMaterial;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { UserList } from '@/components';
|
import { SelectModal, UserList } from '@/components';
|
||||||
import { business } from '@/services';
|
import { business } from '@/services';
|
||||||
import { formatParam } from '@/utils/formatParam';
|
import { formatParam } from '@/utils/formatParam';
|
||||||
import { pagination } from '@/utils/pagination';
|
import { pagination } from '@/utils/pagination';
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
LightFilter,
|
LightFilter,
|
||||||
ProColumns,
|
ProColumns,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { SelectModal } from '@chageable/components';
|
|
||||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
export * from './Agreement';
|
export * from './Agreement';
|
||||||
export * from './BasicData';
|
export * from './BasicData';
|
||||||
export * from './Biz';
|
export * from './Biz';
|
||||||
|
export { default as Captcha } from './Captcha';
|
||||||
|
export type {
|
||||||
|
CaptchaRes,
|
||||||
|
CheckCaptchaProps,
|
||||||
|
CheckCaptchaRes,
|
||||||
|
} from './Captcha/typing';
|
||||||
export { default as CaptchaModal } from './CaptchaModal';
|
export { default as CaptchaModal } from './CaptchaModal';
|
||||||
export * from './Channel';
|
export * from './Channel';
|
||||||
export * from './Company';
|
export * from './Company';
|
||||||
@ -9,17 +15,22 @@ export * from './Delivery';
|
|||||||
export * from './Editor';
|
export * from './Editor';
|
||||||
export * from './Employee';
|
export * from './Employee';
|
||||||
export * from './Expense';
|
export * from './Expense';
|
||||||
|
export * from './FormItem';
|
||||||
export { default as IconFont } from './Iconfont';
|
export { default as IconFont } from './Iconfont';
|
||||||
export { default as IconPicker } from './Iconfont/IconPicker';
|
export { default as IconPicker } from './Iconfont/IconPicker';
|
||||||
|
export { default as LeftMenu } from './LeftMenu';
|
||||||
export * from './Material';
|
export * from './Material';
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
|
export * from './Modal';
|
||||||
|
export { SelectModal, TMapModal } from './Modal';
|
||||||
export * from './Order';
|
export * from './Order';
|
||||||
export * from './PaymentTask';
|
export * from './PaymentTask';
|
||||||
export * from './Permission';
|
export * from './Permission';
|
||||||
export { default as PhoneContainer } from './PhoneContainer';
|
|
||||||
export * from './Platform';
|
export * from './Platform';
|
||||||
export * from './Remark';
|
export * from './Remark';
|
||||||
export * from './Role';
|
export * from './Role';
|
||||||
export * from './Setting';
|
export * from './Setting';
|
||||||
export * from './Supplier';
|
export * from './Supplier';
|
||||||
|
export { default as UploadMaterial } from './UploadMaterial';
|
||||||
|
export type { UploadMaterialProps } from './UploadMaterial';
|
||||||
export * from './User';
|
export * from './User';
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export function formatAccountDisplay(
|
|||||||
accountType,
|
accountType,
|
||||||
accountName,
|
accountName,
|
||||||
bankName,
|
bankName,
|
||||||
|
branchName,
|
||||||
accountNumber,
|
accountNumber,
|
||||||
} = account;
|
} = account;
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ export function formatAccountDisplay(
|
|||||||
|
|
||||||
// 对公账户:公司账户 + 账户名称 + 银行名称 + 账户号码
|
// 对公账户:公司账户 + 账户名称 + 银行名称 + 账户号码
|
||||||
if (accountCategory === 'COMPANY_ACCOUNT') {
|
if (accountCategory === 'COMPANY_ACCOUNT') {
|
||||||
return `${categoryLabel} | ${accountName} | ${bankName || ''} | ${accountNumber || ''}`;
|
return `${categoryLabel} | ${accountName} | ${bankName || ''} | ${branchName || ''} | ${accountNumber || ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 个人账户(银行):私人账户 + 账户名称 + 银行名称 + 账户号码
|
// 个人账户(银行):私人账户 + 账户名称 + 银行名称 + 账户号码
|
||||||
|
|||||||
105
packages/app-sso-server/src/components/Captcha/index.less
Normal file
105
packages/app-sso-server/src/components/Captcha/index.less
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
.captcha-modal {
|
||||||
|
.ant-modal-close {
|
||||||
|
&:hover {
|
||||||
|
transform: scale(110%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑动验证码 */
|
||||||
|
.verify-bar-area {
|
||||||
|
z-index: 100;
|
||||||
|
position: relative;
|
||||||
|
background: #f5f5f5;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-bar-area .verify-move-block {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-shadow: 0 6px 16px -8px #00000014, 0 9px 28px 0 #0000000d,
|
||||||
|
0 12px 48px 16px #00000008;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-img-out {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-bar-area .verify-left-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background: #f0f0ff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-img-panel {
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-bar-area .verify-move-block .verify-sub-block {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-bar-area .verify-msg {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #999;
|
||||||
|
z-index: 3;
|
||||||
|
padding: 0 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: block;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-very-bottom {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
margin-top: 16px;
|
||||||
|
z-index: 100;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-refresh {
|
||||||
|
width: fit-content;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #666;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-refresh:hover {
|
||||||
|
color: #0754b2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-refresh-text {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
406
packages/app-sso-server/src/components/Captcha/index.tsx
Normal file
406
packages/app-sso-server/src/components/Captcha/index.tsx
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
import {
|
||||||
|
CheckCircleOutlined,
|
||||||
|
CloseCircleOutlined,
|
||||||
|
DoubleRightOutlined,
|
||||||
|
LoadingOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Modal, Skeleton, message } from 'antd';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import './index.less';
|
||||||
|
import { checkCaptchaMock, getPictureMock } from './mock';
|
||||||
|
import { AJCaptchaIconProps, AJCaptchaSliderProps, CaptchaRes } from './typing';
|
||||||
|
import { aesEncrypt, setStyle, uuid } from './utils';
|
||||||
|
|
||||||
|
const Scale = {
|
||||||
|
default: 1,
|
||||||
|
big: 1.4,
|
||||||
|
large: 1.8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AJCaptchaIcon = (props: { icon: AJCaptchaIconProps }) => {
|
||||||
|
const iconStyle: React.CSSProperties = {
|
||||||
|
fontSize: '18px',
|
||||||
|
color: '#999',
|
||||||
|
};
|
||||||
|
switch (props.icon) {
|
||||||
|
case 'right':
|
||||||
|
return <DoubleRightOutlined style={iconStyle} />;
|
||||||
|
case 'fail':
|
||||||
|
return <CloseCircleOutlined style={{ ...iconStyle, color: '#ff4d4f' }} />;
|
||||||
|
case 'loading':
|
||||||
|
return <LoadingOutlined style={iconStyle} />;
|
||||||
|
case 'check':
|
||||||
|
return <CheckCircleOutlined style={{ ...iconStyle, color: '#52c41a' }} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const AJCaptchaSlider: React.FC<AJCaptchaSliderProps> = ({
|
||||||
|
show = false,
|
||||||
|
title,
|
||||||
|
tips,
|
||||||
|
refreshText,
|
||||||
|
size = 'default',
|
||||||
|
vSpace = 18, // 图片与滑块的距离,单位px
|
||||||
|
sliderBlockWidth = 45, // 滑块宽度45,单位px
|
||||||
|
padding = 16, // 弹框内边距 单位px
|
||||||
|
hide,
|
||||||
|
onSuccess,
|
||||||
|
setSize = {
|
||||||
|
imgWidth: 310, // 图片宽度
|
||||||
|
imgHeight: 155, // 图片高度
|
||||||
|
barHeight: 36, // 滑块框高度
|
||||||
|
},
|
||||||
|
getPicture = getPictureMock,
|
||||||
|
checkCaptcha = checkCaptchaMock,
|
||||||
|
}) => {
|
||||||
|
const scale = Scale[size];
|
||||||
|
const blockWidth = sliderBlockWidth * scale;
|
||||||
|
const imgWidth = setSize.imgWidth * scale;
|
||||||
|
const imgHeight = setSize.imgHeight * scale;
|
||||||
|
|
||||||
|
const isSupportTouch = 'ontouchstart' in window;
|
||||||
|
const events = isSupportTouch
|
||||||
|
? {
|
||||||
|
start: 'touchstart',
|
||||||
|
move: 'touchmove',
|
||||||
|
end: 'touchend',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
start: 'mousedown',
|
||||||
|
move: 'mousemove',
|
||||||
|
end: 'mouseup',
|
||||||
|
};
|
||||||
|
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(false); // 是否加载
|
||||||
|
const [response, setResponse] = useState<CaptchaRes | null>(null); // token、密钥、图片等数据
|
||||||
|
const [icon, setIcon] = useState<AJCaptchaIconProps>('loading'); // 滑块icon
|
||||||
|
const [showTips, setTips] = useState<boolean>(true); // 是否展示提示文案
|
||||||
|
const [blockPressed, setPressed] = useState<boolean>(false); // 滑块是否被按下
|
||||||
|
|
||||||
|
const barAreaRef = useRef({
|
||||||
|
barAreaLeft: 0,
|
||||||
|
barAreaOffsetWidth: 0,
|
||||||
|
});
|
||||||
|
const leftBarRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const isEnd = useRef<boolean>(false);
|
||||||
|
const status = useRef<boolean>(false);
|
||||||
|
|
||||||
|
const closeBox = () => {
|
||||||
|
setResponse(null);
|
||||||
|
hide?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
setIcon('right');
|
||||||
|
getPicture()
|
||||||
|
.then((res) => {
|
||||||
|
setResponse(res);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新数据和界面状态的函数
|
||||||
|
* 此函数主要用于在不加载中的情况下,重新获取数据并重置鼠标状态、结束状态、提示信息和布局宽度
|
||||||
|
* 它确保在界面交互过程中,用户界面始终保持一致和响应性
|
||||||
|
*/
|
||||||
|
const refresh = () => {
|
||||||
|
// 检查数据加载状态,如果正在加载,则不执行后续操作
|
||||||
|
if (isLoading) return;
|
||||||
|
|
||||||
|
// 重新获取数据
|
||||||
|
getData();
|
||||||
|
|
||||||
|
// 重置状态,准备下一次交互
|
||||||
|
isEnd.current = false;
|
||||||
|
status.current = false;
|
||||||
|
|
||||||
|
// 设置提示信息,指导用户进行下一步操作
|
||||||
|
setTips(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理滑动事件的方法,用于更新滑动块的位置和相关的状态
|
||||||
|
* @param e React的鼠标点击事件或触摸事件对象
|
||||||
|
*/
|
||||||
|
const move = (
|
||||||
|
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
|
||||||
|
) => {
|
||||||
|
// 如果当前状态不允许滑动或滑动已结束,则直接返回,不执行后续操作
|
||||||
|
if (!status.current || isEnd.current) return;
|
||||||
|
|
||||||
|
// 根据事件类型(触摸或鼠标)获取滑动的x坐标
|
||||||
|
const x = 'touches' in e ? e.touches[0].pageX : e.clientX;
|
||||||
|
|
||||||
|
// 计算滑动块可以移动的最大左边距
|
||||||
|
const maxLeft = barAreaRef.current.barAreaOffsetWidth - blockWidth;
|
||||||
|
|
||||||
|
// 根据滑动位置计算滑动块的实际左边距,确保它在允许的范围内
|
||||||
|
const moveBlockLeft = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(x - barAreaRef.current.barAreaLeft - blockWidth / 2, maxLeft),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 拖动后小方块的left值
|
||||||
|
const left = Math.max(0, moveBlockLeft);
|
||||||
|
|
||||||
|
// 计算对应leftBar的width值
|
||||||
|
const width = `${left + blockWidth}px`;
|
||||||
|
|
||||||
|
// 设置提示信息为空,表示滑动操作正常进行,无错误或额外信息需要展示
|
||||||
|
setTips(false);
|
||||||
|
|
||||||
|
// 设置leftBar的width值
|
||||||
|
setStyle(leftBarRef.current, { width });
|
||||||
|
};
|
||||||
|
|
||||||
|
const end = () => {
|
||||||
|
document.removeEventListener(events.move, move as any);
|
||||||
|
document.removeEventListener(events.end, end);
|
||||||
|
document.removeEventListener('touchcancel', end);
|
||||||
|
// 判断是否重合
|
||||||
|
if (status.current && !isEnd.current) {
|
||||||
|
setIcon('loading');
|
||||||
|
|
||||||
|
const leftBarWidth = parseInt(
|
||||||
|
(leftBarRef.current?.style.width || '').replace('px', ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
const rawPointJson = JSON.stringify({
|
||||||
|
x: (leftBarWidth - blockWidth) / scale, // 计算x轴偏移量
|
||||||
|
y: 5.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
captchaType: 'blockPuzzle',
|
||||||
|
pointJson: response?.secretKey
|
||||||
|
? aesEncrypt(rawPointJson, response?.secretKey)
|
||||||
|
: rawPointJson,
|
||||||
|
token: response?.token || '',
|
||||||
|
clientUid: localStorage.getItem('slider')!,
|
||||||
|
ts: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
checkCaptcha(data)
|
||||||
|
.then((res) => {
|
||||||
|
isEnd.current = true;
|
||||||
|
if (res.token) {
|
||||||
|
setIcon('check');
|
||||||
|
message.success('验证成功!');
|
||||||
|
setTimeout(() => {
|
||||||
|
const params = `${res.token}---${rawPointJson}`;
|
||||||
|
onSuccess(aesEncrypt(params, response?.secretKey));
|
||||||
|
closeBox();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
setIcon('fail');
|
||||||
|
message.error('验证失败!');
|
||||||
|
setTimeout(() => {
|
||||||
|
refresh();
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
isEnd.current = true;
|
||||||
|
setIcon('fail');
|
||||||
|
message.error('验证失败!');
|
||||||
|
setTimeout(() => {
|
||||||
|
refresh();
|
||||||
|
}, 800);
|
||||||
|
});
|
||||||
|
status.current = false;
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!localStorage.getItem('slider'))
|
||||||
|
localStorage.setItem('slider', `slider-${uuid()}`);
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
if (localStorage.getItem('slider')) localStorage.removeItem('slider');
|
||||||
|
document.removeEventListener(events.move, move as any);
|
||||||
|
document.removeEventListener(events.end, end);
|
||||||
|
document.removeEventListener('touchcancel', end);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) refresh();
|
||||||
|
}, [show]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置栏区域的左边界和宽度
|
||||||
|
* 此函数通过计算给定HTML元素的位置和尺寸来更新栏区域的左边界和宽度
|
||||||
|
* @param event HTMLDivElement类型,代表触发事件的HTML元素它用于获取栏区域的位置和宽度信息
|
||||||
|
*/
|
||||||
|
const setBarArea = (event: HTMLDivElement | null) => {
|
||||||
|
if (!event) return;
|
||||||
|
// 获取栏区域左边界的坐标
|
||||||
|
const newBarAreaLeft = event.getBoundingClientRect().left;
|
||||||
|
// 获取栏区域的宽度
|
||||||
|
const newBarAreaOffsetWidth = event.offsetWidth;
|
||||||
|
// 更新状态
|
||||||
|
barAreaRef.current = {
|
||||||
|
barAreaLeft: newBarAreaLeft, // 记录栏区域的左边界
|
||||||
|
barAreaOffsetWidth: newBarAreaOffsetWidth, // 记录栏区域的宽度
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = (
|
||||||
|
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>,
|
||||||
|
) => {
|
||||||
|
if (isEnd.current) return;
|
||||||
|
status.current = true;
|
||||||
|
setPressed(true);
|
||||||
|
|
||||||
|
document.addEventListener(events.move, move as any);
|
||||||
|
document.addEventListener(events.end, end);
|
||||||
|
document.addEventListener('touchcancel', end);
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={title}
|
||||||
|
className="captcha-modal"
|
||||||
|
centered
|
||||||
|
open={show}
|
||||||
|
maskClosable={false}
|
||||||
|
width={imgWidth + 2 * padding}
|
||||||
|
styles={{
|
||||||
|
content: {
|
||||||
|
padding: `16px ${padding}px 10px`,
|
||||||
|
userSelect: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
footer={null}
|
||||||
|
onCancel={closeBox}
|
||||||
|
>
|
||||||
|
<div className="verifybox">
|
||||||
|
{isLoading ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: imgWidth,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="verify-img-out"
|
||||||
|
style={{ height: imgHeight + vSpace }}
|
||||||
|
>
|
||||||
|
<Skeleton.Image
|
||||||
|
active
|
||||||
|
style={{ height: imgHeight, width: imgWidth }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Skeleton.Node
|
||||||
|
active
|
||||||
|
style={{ height: setSize.barHeight, width: imgWidth }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="verify-img-out"
|
||||||
|
style={{ height: imgHeight + vSpace }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="verify-img-panel"
|
||||||
|
style={{
|
||||||
|
width: imgWidth,
|
||||||
|
height: imgHeight,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{response?.originalImageBase64 && (
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
'data:image/png;base64,' + response?.originalImageBase64
|
||||||
|
}
|
||||||
|
alt="captcha-image"
|
||||||
|
draggable={false}
|
||||||
|
className="verify-img"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="verify-bar-area"
|
||||||
|
style={{
|
||||||
|
width: imgWidth,
|
||||||
|
height: setSize.barHeight,
|
||||||
|
}}
|
||||||
|
ref={(bar) => setBarArea(bar)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="verify-msg"
|
||||||
|
style={{
|
||||||
|
lineHeight: setSize.barHeight + 'px',
|
||||||
|
marginLeft: blockWidth + 'px',
|
||||||
|
width: imgWidth - blockWidth + 'px',
|
||||||
|
display: showTips ? 'block' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tips}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="verify-left-bar"
|
||||||
|
ref={leftBarRef}
|
||||||
|
style={{
|
||||||
|
width: blockWidth,
|
||||||
|
height: setSize.barHeight,
|
||||||
|
touchAction: 'pan-y',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="verify-move-block"
|
||||||
|
onMouseDown={start}
|
||||||
|
onTouchStart={start}
|
||||||
|
style={{
|
||||||
|
width: blockWidth,
|
||||||
|
backgroundColor: blockPressed ? '#f2f2f2' : '#fff',
|
||||||
|
cursor: blockPressed ? 'grab' : 'pointer',
|
||||||
|
height: setSize.barHeight - 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{<AJCaptchaIcon icon={icon} />}
|
||||||
|
<div
|
||||||
|
className="verify-sub-block"
|
||||||
|
style={{
|
||||||
|
width: blockWidth,
|
||||||
|
height: imgHeight,
|
||||||
|
top: `-${imgHeight + vSpace}px`,
|
||||||
|
backgroundSize: `${imgWidth} ${imgHeight}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{response?.jigsawImageBase64 && (
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
'data:image/png;base64,' + response?.jigsawImageBase64
|
||||||
|
}
|
||||||
|
alt="blockImage"
|
||||||
|
className="verify-img"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="verify-very-bottom">
|
||||||
|
<div className="verify-refresh" onClick={refresh}>
|
||||||
|
<ReloadOutlined spin={isLoading} />
|
||||||
|
<span className="verify-refresh-text">{refreshText}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AJCaptchaSlider;
|
||||||
76
packages/app-sso-server/src/components/Captcha/mock.ts
Normal file
76
packages/app-sso-server/src/components/Captcha/mock.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import mockCaptchaData from './mockDB';
|
||||||
|
import { CaptchaRes, CheckCaptchaProps, CheckCaptchaRes } from './typing';
|
||||||
|
import { aesDecrypt } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步获取验证码图片
|
||||||
|
*
|
||||||
|
* 本函数通过发送HTTP请求获取验证码图片数据,并返回解析后的数据
|
||||||
|
* 主要用于在用户登录或注册时获取验证码图片,以提高系统安全性
|
||||||
|
*
|
||||||
|
* @returns {Promise<CaptchaRes>} 返回一个Promise,解析后包含验证码图片数据的CaptchaRes对象
|
||||||
|
*/
|
||||||
|
export const getPictureMock = async (): Promise<CaptchaRes> => {
|
||||||
|
const delay = Math.random() * 800;
|
||||||
|
return new Promise<CaptchaRes>((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 模拟异步请求返回的数据
|
||||||
|
const keys = Object.keys(mockCaptchaData);
|
||||||
|
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||||
|
const obj = mockCaptchaData[key];
|
||||||
|
resolve({
|
||||||
|
token: obj.token,
|
||||||
|
secretKey: obj.secretKey,
|
||||||
|
originalImageBase64: obj.originalImageBase64,
|
||||||
|
jigsawImageBase64: obj.jigsawImageBase64,
|
||||||
|
});
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证验证码的正确性
|
||||||
|
*
|
||||||
|
* @param data 包含验证码相关信息的对象
|
||||||
|
* @param data.token 验证码的令牌
|
||||||
|
* @param data.captchaType 验证码的类型
|
||||||
|
* @param data.pointJson 用户点击的位置信息,加密后的内容
|
||||||
|
* @param data.clientUid 客户端的唯一标识
|
||||||
|
* @param data.ts 时间戳
|
||||||
|
* @returns 返回一个Promise,包含验证码验证的结果
|
||||||
|
*/
|
||||||
|
export const checkCaptchaMock = async (
|
||||||
|
data: CheckCaptchaProps,
|
||||||
|
): Promise<CheckCaptchaRes> => {
|
||||||
|
// 生成随机延迟时间,模拟网络延迟
|
||||||
|
const delay = Math.random() * 1500;
|
||||||
|
// 定义误差范围,用于验证点击位置,不要过小或者过大,推荐范围在1到3之间
|
||||||
|
const buffer = 2;
|
||||||
|
|
||||||
|
return new Promise<CheckCaptchaRes>((resolve, reject) => {
|
||||||
|
// 使用setTimeout模拟异步验证过程
|
||||||
|
setTimeout(() => {
|
||||||
|
if (data.token in mockCaptchaData) {
|
||||||
|
const mockCaptchaRes = mockCaptchaData[data.token];
|
||||||
|
const x = mockCaptchaRes.x;
|
||||||
|
|
||||||
|
// 解密用户点击的位置信息
|
||||||
|
const decryptRes = aesDecrypt(data.pointJson, mockCaptchaRes.secretKey);
|
||||||
|
const point = JSON.parse(decryptRes);
|
||||||
|
|
||||||
|
console.log({ captchaX: x, buffer, sliderX: point.x });
|
||||||
|
|
||||||
|
// 验证点击位置是否在有效范围内
|
||||||
|
if (!point.x || point.x > x + buffer || point.x < x - buffer) {
|
||||||
|
reject('point error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟异步请求返回的数据
|
||||||
|
resolve({ token: mockCaptchaRes.token, success: true, msg: 'success' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject('token error');
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
};
|
||||||
41
packages/app-sso-server/src/components/Captcha/mockDB.ts
Normal file
41
packages/app-sso-server/src/components/Captcha/mockDB.ts
Normal file
File diff suppressed because one or more lines are too long
89
packages/app-sso-server/src/components/Captcha/typing.ts
Normal file
89
packages/app-sso-server/src/components/Captcha/typing.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 定义滑动验证码组件的属性类型
|
||||||
|
* 该类型指定了组件可接受的属性及其类型
|
||||||
|
*/
|
||||||
|
export type AJCaptchaSliderProps = {
|
||||||
|
/** 验证码图片的缩放比例,默认为1 */
|
||||||
|
scale?: number;
|
||||||
|
/** 组件的大小,可选值有:'default'、'big'、'large' */
|
||||||
|
size?: 'default' | 'big' | 'large';
|
||||||
|
/** 是否显示验证码组件 */
|
||||||
|
show: boolean;
|
||||||
|
/** 验证码图片之间的垂直间距 */
|
||||||
|
vSpace?: number;
|
||||||
|
/** 滑动块的宽度 */
|
||||||
|
sliderBlockWidth?: number;
|
||||||
|
/** 组件的内边距 */
|
||||||
|
padding?: number;
|
||||||
|
/** 隐藏验证码组件的回调函数 */
|
||||||
|
hide: () => void;
|
||||||
|
/** 验证成功时的回调函数,接收一个秘密字符串作为参数 */
|
||||||
|
onSuccess: (secret: string) => void;
|
||||||
|
/** 自定义验证码图片和滑动条的高度 */
|
||||||
|
setSize?: {
|
||||||
|
imgWidth: number;
|
||||||
|
imgHeight: number;
|
||||||
|
barHeight: number;
|
||||||
|
};
|
||||||
|
/** 验证码组件的标题 */
|
||||||
|
title: string;
|
||||||
|
/** 滑动验证码滑块上的提示文本 */
|
||||||
|
tips?: string;
|
||||||
|
/** 刷新验证码按钮上的文本 */
|
||||||
|
refreshText?: string;
|
||||||
|
/** 获取验证码图片的函数,返回一个Promise,Promise解析为CaptchaRes类型 */
|
||||||
|
getPicture?: () => Promise<CaptchaRes>;
|
||||||
|
/** 检查验证码的函数,接收CheckCaptchaProps类型的数据,返回一个Promise,Promise解析为CheckCaptchaRes类型 */
|
||||||
|
checkCaptcha?: (data: CheckCaptchaProps) => Promise<CheckCaptchaRes>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义验证码图标组件的属性类型
|
||||||
|
* 该类型用于指定组件的验证状态
|
||||||
|
*/
|
||||||
|
export type AJCaptchaIconProps = 'right' | 'fail' | 'loading' | 'check';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义验证码响应类型
|
||||||
|
* 该类型用于表示验证码服务返回的数据结构
|
||||||
|
*/
|
||||||
|
export type CaptchaRes = {
|
||||||
|
/** 用于验证的令牌 */
|
||||||
|
token: string;
|
||||||
|
/** 用于验证用户身份的密钥 */
|
||||||
|
secretKey: string;
|
||||||
|
/** 原始图片的Base64编码 */
|
||||||
|
originalImageBase64: string;
|
||||||
|
/** 滑动验证码图片的Base64编码 */
|
||||||
|
jigsawImageBase64: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义检查验证码的响应类型
|
||||||
|
* 该类型用于表示检查验证码结果的数据结构
|
||||||
|
*/
|
||||||
|
export type CheckCaptchaRes = {
|
||||||
|
/** 检查是否成功 */
|
||||||
|
success: boolean;
|
||||||
|
/** 检查结果的描述信息 */
|
||||||
|
msg: string;
|
||||||
|
/** 验证的令牌 */
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义检查验证码所需的参数类型
|
||||||
|
* 该类型指定了检查验证码时需要提供的参数
|
||||||
|
*/
|
||||||
|
export type CheckCaptchaProps = {
|
||||||
|
/** 验证的令牌 */
|
||||||
|
token: string;
|
||||||
|
/** 验证码类型 */
|
||||||
|
captchaType: string;
|
||||||
|
/** 用户滑动验证码的位置数据,JSON格式 */
|
||||||
|
pointJson: string;
|
||||||
|
/** 客户端的唯一标识符 */
|
||||||
|
clientUid: string;
|
||||||
|
/** 时间戳,用于防止重放攻击 */
|
||||||
|
ts: number;
|
||||||
|
};
|
||||||
96
packages/app-sso-server/src/components/Captcha/utils.ts
Normal file
96
packages/app-sso-server/src/components/Captcha/utils.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
const defaultKeyWord = 'SwKsGlMEcdPMEhQ2B';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用AES算法对字符串进行加密
|
||||||
|
*
|
||||||
|
* @param word {string} 需要加密的字符串
|
||||||
|
* @param keyWord {string} 加密密钥,默认为defaultKeyWord
|
||||||
|
* @returns {string} 加密后的字符串
|
||||||
|
*/
|
||||||
|
export const aesEncrypt = (
|
||||||
|
word: string,
|
||||||
|
keyWord: string = defaultKeyWord,
|
||||||
|
): string => {
|
||||||
|
// 将密钥解析为UTF-8编码的字节序列
|
||||||
|
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||||
|
// 将待加密的字符串解析为UTF-8编码的字节序列
|
||||||
|
const srcs = CryptoJS.enc.Utf8.parse(word);
|
||||||
|
// 使用AES算法在ECB模式和Pkcs7填充下进行加密
|
||||||
|
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
|
||||||
|
mode: CryptoJS.mode.ECB,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
// 返回加密后的字符串表示
|
||||||
|
return encrypted.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用AES算法进行解密
|
||||||
|
*
|
||||||
|
* @param encryptedWord {string} 需要解密的密文
|
||||||
|
* @param keyWord {string} 解密用的密钥,默认值为 defaultKeyWord
|
||||||
|
* @returns {string} 解密后的原文
|
||||||
|
*/
|
||||||
|
export const aesDecrypt = (
|
||||||
|
encryptedWord: string,
|
||||||
|
keyWord: string = defaultKeyWord,
|
||||||
|
): string => {
|
||||||
|
// 将密钥转换为Utf8编码格式
|
||||||
|
const key = CryptoJS.enc.Utf8.parse(keyWord);
|
||||||
|
|
||||||
|
// 使用AES算法解密密文,使用ECB模式和Pkcs7填充方式
|
||||||
|
const decrypted = CryptoJS.AES.decrypt(encryptedWord, key, {
|
||||||
|
mode: CryptoJS.mode.ECB,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将解密后的数据转换为Utf8编码格式的字符串并返回
|
||||||
|
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成一个全局唯一标识符(UUID)
|
||||||
|
*
|
||||||
|
* UUID的格式为xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,其中:
|
||||||
|
* - x表示一个随机的十六进制数字
|
||||||
|
* - y表示一个随机的十六进制数字,但其二进制表示的高两位必须是01(确保UUID的版本为4)
|
||||||
|
*
|
||||||
|
* 此函数使用Math.random生成随机数,并将其转换为十六进制格式以替换UUID模板中的x和y
|
||||||
|
* 通过这种方式,可以生成一个随机的、符合UUID标准的唯一标识符
|
||||||
|
*
|
||||||
|
* @returns {string} 一个随机生成的UUID字符串
|
||||||
|
*/
|
||||||
|
export const uuid = (): string => {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
|
// 生成一个0到15之间的随机整数
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
// 如果当前字符是x,则直接使用随机数;如果是y,则确保随机数的高两位是01
|
||||||
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
// 将生成的随机数转换为十六进制字符串
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置样式
|
||||||
|
/**
|
||||||
|
* 为给定的HTML元素设置样式。
|
||||||
|
* 该函数根据提供的样式对象直接操作元素的样式属性。
|
||||||
|
* 如果元素未定义,则忽略以防止错误。
|
||||||
|
*
|
||||||
|
* @param el 要设置样式的HTML元素。可以为null,此时函数不会执行任何操作。
|
||||||
|
* @param styleObj 定义要应用样式的对象。每个属性对应元素的一个样式属性。
|
||||||
|
*/
|
||||||
|
export const setStyle = (
|
||||||
|
el: HTMLElement | null,
|
||||||
|
styleObj: Record<string, string> = {},
|
||||||
|
) => {
|
||||||
|
if (el) {
|
||||||
|
// 遍历样式对象,将每个样式属性应用到元素上
|
||||||
|
// eslint-disable-next-line guard-for-in
|
||||||
|
for (const prop in styleObj) {
|
||||||
|
el.style[prop as any] = styleObj[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import auth from '@/services/auth';
|
import auth from '@/services/auth';
|
||||||
import { Captcha, CheckCaptchaProps } from '@chageable/components';
|
import { Captcha, CheckCaptchaProps } from '@/components';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface ICaptchaModalProps {
|
interface ICaptchaModalProps {
|
||||||
|
|||||||
@ -1 +1,7 @@
|
|||||||
export { default as CaptchaModal } from './CaptchaModal';
|
export { default as CaptchaModal } from './CaptchaModal';
|
||||||
|
export { default as Captcha } from './Captcha';
|
||||||
|
export type {
|
||||||
|
CaptchaRes,
|
||||||
|
CheckCaptchaProps,
|
||||||
|
CheckCaptchaRes,
|
||||||
|
} from './Captcha/typing';
|
||||||
|
|||||||
3662
pnpm-lock.yaml
3662
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
|||||||
import { defineConfig } from 'dumi';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
outputPath: 'docs-dist',
|
|
||||||
themeConfig: {
|
|
||||||
name: '百变怪',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# http://editorconfig.org
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.md]
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: require.resolve('@umijs/lint/dist/config/eslint'),
|
|
||||||
};
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { defineConfig } from 'father';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
|
||||||
esm: { output: 'dist' },
|
|
||||||
});
|
|
||||||
6
shared/components/.gitignore
vendored
6
shared/components/.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
node_modules
|
|
||||||
/dist
|
|
||||||
.dumi/tmp
|
|
||||||
.dumi/tmp-test
|
|
||||||
.dumi/tmp-production
|
|
||||||
.DS_Store
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
/dist
|
|
||||||
*.yaml
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
pluginSearchDirs: false,
|
|
||||||
plugins: [
|
|
||||||
require.resolve('prettier-plugin-organize-imports'),
|
|
||||||
require.resolve('prettier-plugin-packagejson'),
|
|
||||||
],
|
|
||||||
printWidth: 80,
|
|
||||||
proseWrap: 'never',
|
|
||||||
singleQuote: true,
|
|
||||||
trailingComma: 'all',
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: '*.md',
|
|
||||||
options: {
|
|
||||||
proseWrap: 'preserve',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@umijs/lint/dist/config/stylelint"
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 沈益飞 <m809745357@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
# @chageable/components
|
|
||||||
|
|
||||||
[](https://npmjs.org/package/@chageable/components)
|
|
||||||
[](https://npmjs.org/package/@chageable/components)
|
|
||||||
|
|
||||||
A react library developed with dumi
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# install dependencies
|
|
||||||
$ pnpm install
|
|
||||||
|
|
||||||
# develop library by docs demo
|
|
||||||
$ pnpm start
|
|
||||||
|
|
||||||
# build library source code
|
|
||||||
$ pnpm run build
|
|
||||||
|
|
||||||
# build library source code in watch mode
|
|
||||||
$ pnpm run build:watch
|
|
||||||
|
|
||||||
# build docs
|
|
||||||
$ pnpm run docs:build
|
|
||||||
|
|
||||||
# Locally preview the production build.
|
|
||||||
$ pnpm run docs:preview
|
|
||||||
|
|
||||||
# check your project for potential problems
|
|
||||||
$ pnpm run doctor
|
|
||||||
```
|
|
||||||
|
|
||||||
## LICENSE
|
|
||||||
|
|
||||||
MIT
|
|
||||||
@ -1 +0,0 @@
|
|||||||
This is a guide example.
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
hero:
|
|
||||||
title: library
|
|
||||||
description: A react library developed with dumi
|
|
||||||
actions:
|
|
||||||
- text: Hello
|
|
||||||
link: /
|
|
||||||
- text: World
|
|
||||||
link: /
|
|
||||||
features:
|
|
||||||
- title: Hello
|
|
||||||
emoji: 💎
|
|
||||||
description: Put hello description here
|
|
||||||
- title: World
|
|
||||||
emoji: 🌈
|
|
||||||
description: Put world description here
|
|
||||||
- title: '!'
|
|
||||||
emoji: 🚀
|
|
||||||
description: Put ! description here
|
|
||||||
---
|
|
||||||
|
|
||||||
@chageable/components
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@chageable/components",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "A react library developed with dumi",
|
|
||||||
"module": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"scripts": {
|
|
||||||
"start": "npm run dev",
|
|
||||||
"dev": "dumi dev",
|
|
||||||
"build": "father build",
|
|
||||||
"build:watch": "father dev",
|
|
||||||
"docs:build": "dumi build",
|
|
||||||
"docs:preview": "dumi preview",
|
|
||||||
"prepare": "dumi setup",
|
|
||||||
"doctor": "father doctor",
|
|
||||||
"lint": "npm run lint:es && npm run lint:css",
|
|
||||||
"lint:css": "stylelint \"{src,test}/**/*.{css,less}\"",
|
|
||||||
"lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"",
|
|
||||||
"prepublishOnly": "father doctor && npm run build",
|
|
||||||
"openapi2ts": "openapi2ts && prettier --cache --write ./src/services"
|
|
||||||
},
|
|
||||||
"authors": [
|
|
||||||
"沈益飞 <m809745357@gmail.com>"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"commitlint": {
|
|
||||||
"extends": [
|
|
||||||
"@commitlint/config-conventional"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*.{md,json}": [
|
|
||||||
"prettier --write --no-error-on-unmatched-pattern"
|
|
||||||
],
|
|
||||||
"*.{css,less}": [
|
|
||||||
"stylelint --fix",
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{js,jsx}": [
|
|
||||||
"eslint --fix",
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{ts,tsx}": [
|
|
||||||
"eslint --fix",
|
|
||||||
"prettier --parser=typescript --write"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.9.0",
|
|
||||||
"react-dom": ">=16.9.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@commitlint/cli": "^17.1.2",
|
|
||||||
"@commitlint/config-conventional": "^17.1.0",
|
|
||||||
"@types/react": "^18.0.0",
|
|
||||||
"@types/react-dom": "^18.0.0",
|
|
||||||
"@umijs/lint": "^4.0.0",
|
|
||||||
"dumi": "^2.3.0",
|
|
||||||
"eslint": "^8.23.0",
|
|
||||||
"father": "^4.1.0",
|
|
||||||
"husky": "^8.0.1",
|
|
||||||
"lint-staged": "^13.0.3",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"prettier-plugin-organize-imports": "^3.0.0",
|
|
||||||
"prettier-plugin-packagejson": "^2.2.18",
|
|
||||||
"react": "^18.0.0",
|
|
||||||
"react-dom": "^18.0.0",
|
|
||||||
"stylelint": "^14.9.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
# Captcha
|
|
||||||
|
|
||||||
This is an example component.
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
import { Captcha } from '@chageable/components';
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { Button } from 'antd';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const [showCaptcha, setShow] = useState(false);
|
|
||||||
|
|
||||||
const [size, setSize] = useState('default');
|
|
||||||
|
|
||||||
const resizeUpdate = () => {
|
|
||||||
// 通过事件对象获取浏览器窗口的宽度
|
|
||||||
const w = window.innerWidth;
|
|
||||||
if (w < 480) setSize('default');
|
|
||||||
else if (w < 980) setSize('big');
|
|
||||||
else setSize('large');
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
resizeUpdate();
|
|
||||||
// 页面变化时获取浏览器窗口的大小
|
|
||||||
window.addEventListener('resize', resizeUpdate);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// 组件销毁时移除监听事件
|
|
||||||
window.removeEventListener('resize', resizeUpdate);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onSuccess = (token) => {
|
|
||||||
console.log('token:', token);
|
|
||||||
setShow(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Captcha
|
|
||||||
title="Verify Modal"
|
|
||||||
tips="Slide the slider to the right"
|
|
||||||
refreshText="Refresh"
|
|
||||||
show={showCaptcha}
|
|
||||||
onSuccess={onSuccess}
|
|
||||||
size={size}
|
|
||||||
hide={() => setShow(false)}
|
|
||||||
/>
|
|
||||||
<Button onClick={() => setShow(true)}>图形验证码</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Foo
|
|
||||||
|
|
||||||
This is an example component.
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
import { Foo } from '@chageable/components';
|
|
||||||
|
|
||||||
export default () => <Foo title="Hello dumi!" />
|
|
||||||
```
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import React, { type FC } from 'react';
|
|
||||||
|
|
||||||
const Foo: FC<{ title: string }> = (props) => <h4>{props.title}</h4>;
|
|
||||||
|
|
||||||
export default Foo;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { ProFormItemProps, ProFormText } from '@ant-design/pro-components';
|
|
||||||
import { IAddressProps, TMapModal } from '@chageable/components';
|
|
||||||
import { Button } from 'antd';
|
|
||||||
import useFormInstance from 'antd/es/form/hooks/useFormInstance';
|
|
||||||
import React from 'react';
|
|
||||||
import '../index.less';
|
|
||||||
|
|
||||||
interface ProFormUploadMaterialProps extends ProFormItemProps {
|
|
||||||
fieldProps?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProFormTMap: React.FC<ProFormUploadMaterialProps> = ({ ...rest }) => {
|
|
||||||
const formInstance = useFormInstance();
|
|
||||||
console.log('form', formInstance.getFieldsValue());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProFormText
|
|
||||||
{...rest}
|
|
||||||
addonAfter={
|
|
||||||
<TMapModal
|
|
||||||
trigger={<Button type={'primary'}>选择地址</Button>}
|
|
||||||
onFinish={(address?: IAddressProps) => {
|
|
||||||
formInstance?.setFieldValue('address', address?.poiaddress);
|
|
||||||
formInstance?.setFieldValue('latlng', address?.latlng);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ProFormTMap;
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionType,
|
|
||||||
ProFormDependency,
|
|
||||||
ProFormField,
|
|
||||||
ProFormItemProps,
|
|
||||||
ProTableProps,
|
|
||||||
} from '@ant-design/pro-components';
|
|
||||||
import { UploadMaterial } from '@chageable/components';
|
|
||||||
import React, { MutableRefObject } from 'react';
|
|
||||||
|
|
||||||
interface ProFormUploadMaterialProps extends ProFormItemProps {
|
|
||||||
fieldProps: {
|
|
||||||
maxCount: number;
|
|
||||||
onChange?: (fileList: any[]) => void;
|
|
||||||
fileList?: any[];
|
|
||||||
request: ProTableProps<any, any>['request'];
|
|
||||||
toolBarRender?: ProTableProps<any, any>['toolBarRender'];
|
|
||||||
actionRef?: MutableRefObject<ActionType | undefined>;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProFormUploadMaterial: React.FC<ProFormUploadMaterialProps> = ({
|
|
||||||
fieldProps,
|
|
||||||
...rest
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<ProFormDependency name={[rest.name]}>
|
|
||||||
{(data, form) => (
|
|
||||||
<ProFormField {...rest}>
|
|
||||||
<UploadMaterial
|
|
||||||
onChange={(fileList) => {
|
|
||||||
form.setFieldValue(
|
|
||||||
rest.name,
|
|
||||||
fileList?.map((file: any) => file.url),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
fileList={data[rest.name]?.map((url: string, index: number) => {
|
|
||||||
return {
|
|
||||||
uid: index.toString(),
|
|
||||||
name: url,
|
|
||||||
// @ts-ignore
|
|
||||||
status: 'success',
|
|
||||||
thumbUrl: url,
|
|
||||||
url: url,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
{...fieldProps}
|
|
||||||
/>
|
|
||||||
</ProFormField>
|
|
||||||
)}
|
|
||||||
</ProFormDependency>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ProFormUploadMaterial;
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
# ProForm
|
|
||||||
|
|
||||||
This is an example component.
|
|
||||||
|
|
||||||
## ProFormUploadMaterial
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import {ProFormUploadMaterial} from '@chageable/components';
|
|
||||||
import {ProForm} from "@ant-design/pro-components";
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<ProForm
|
|
||||||
layout={'horizontal'}
|
|
||||||
labelAlign={'right'}
|
|
||||||
labelCol={{span: 4}}
|
|
||||||
wrapperCol={{span: 16}}
|
|
||||||
grid={true}
|
|
||||||
>
|
|
||||||
<ProFormUploadMaterial
|
|
||||||
label="Upload"
|
|
||||||
name="upload"
|
|
||||||
fieldProps={{
|
|
||||||
request: async (params, sorter, filter) => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
materialId: '1',
|
|
||||||
name: 'John Brown',
|
|
||||||
url: 'https://placehold.co/400',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: data || [],
|
|
||||||
total: data.length,
|
|
||||||
success: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
onChange: (fileList) => console.log("fileList", fileList),
|
|
||||||
maxCount: 9
|
|
||||||
}}
|
|
||||||
|
|
||||||
/>
|
|
||||||
</ProForm>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## ProFormTMap
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import {ProFormTMap} from '@chageable/components';
|
|
||||||
import {ProForm} from "@ant-design/pro-components";
|
|
||||||
import {useState} from "react";
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<ProForm
|
|
||||||
layout={'horizontal'}
|
|
||||||
labelAlign={'right'}
|
|
||||||
labelCol={{span: 4}}
|
|
||||||
wrapperCol={{span: 16}}
|
|
||||||
grid={true}
|
|
||||||
>
|
|
||||||
<ProFormTMap
|
|
||||||
label="address"
|
|
||||||
name="address"
|
|
||||||
/>
|
|
||||||
</ProForm>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,142 +0,0 @@
|
|||||||
# Modal
|
|
||||||
|
|
||||||
This is an example component.
|
|
||||||
|
|
||||||
## SelectModal
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import {SelectModal} from '@chageable/components';
|
|
||||||
import {Button, message} from "antd";
|
|
||||||
import {useRef, useState} from "react";
|
|
||||||
|
|
||||||
interface UserVO {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UserPageQry {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const actionRef = useRef<ActionType>();
|
|
||||||
const [keyword, setKeyword] = useState<string>();
|
|
||||||
|
|
||||||
const columns: ProColumns<UserVO>[] = [
|
|
||||||
{
|
|
||||||
title: '用户ID',
|
|
||||||
dataIndex: 'userId',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '头像',
|
|
||||||
dataIndex: 'avatar',
|
|
||||||
valueType: 'avatar',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '微信昵称',
|
|
||||||
dataIndex: 'nickName',
|
|
||||||
valueType: 'text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '姓名',
|
|
||||||
dataIndex: 'name',
|
|
||||||
valueType: 'text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '手机号',
|
|
||||||
dataIndex: 'phone',
|
|
||||||
valueType: 'text',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SelectModal<UserVO, UserPageQry>
|
|
||||||
rowKey={'userId'}
|
|
||||||
modalProps={{
|
|
||||||
title: '派发优惠券',
|
|
||||||
trigger: <Button>派发优惠券</Button>,
|
|
||||||
}}
|
|
||||||
tableProps={{
|
|
||||||
rowKey: 'userId',
|
|
||||||
columns: columns,
|
|
||||||
columnsState: {
|
|
||||||
persistenceType: 'sessionStorage',
|
|
||||||
persistenceKey: 'userModalColumnStateKey',
|
|
||||||
defaultValue: {
|
|
||||||
userId: {show: false},
|
|
||||||
avatar: {show: true},
|
|
||||||
nickName: {show: true},
|
|
||||||
name: {show: true},
|
|
||||||
phone: {show: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
request: async (params) => {
|
|
||||||
return {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
userId: 1,
|
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/8186664?v=4',
|
|
||||||
nickName: 'changeable',
|
|
||||||
name: 'changeable',
|
|
||||||
phone: '12345678901',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/8186664?v=4',
|
|
||||||
nickName: 'changeable',
|
|
||||||
name: 'changeable',
|
|
||||||
phone: '12345678901',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
success: true,
|
|
||||||
total: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actionRef: actionRef,
|
|
||||||
params: {keyword: keyword, isAdmin: false},
|
|
||||||
toolbar: {
|
|
||||||
search: {
|
|
||||||
placeholder: '请输入微信昵称/姓名/手机号',
|
|
||||||
onSearch: async (value: string) => {
|
|
||||||
setKeyword(value);
|
|
||||||
// @ts-ignore
|
|
||||||
await actionRef.current.reloadAndRest();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFinish={async (userVOList) => {
|
|
||||||
message.success('派券成功');
|
|
||||||
}}
|
|
||||||
num={10}
|
|
||||||
type={'checkbox'}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## TMap
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
/**
|
|
||||||
* iframe: true
|
|
||||||
* compact: true
|
|
||||||
*/
|
|
||||||
import {TMapModal, IAddressProps} from '@chageable/components';
|
|
||||||
import {Typography} from "antd";
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<TMapModal
|
|
||||||
trigger={<Typography.Link>
|
|
||||||
选择地址
|
|
||||||
</Typography.Link>}
|
|
||||||
onFinish={(
|
|
||||||
address?: AddressProps,
|
|
||||||
) => {
|
|
||||||
console.log(address)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
# UploadMaterial
|
|
||||||
|
|
||||||
This is an example component.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import {UploadMaterial} from '@chageable/components';
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return (
|
|
||||||
<UploadMaterial
|
|
||||||
request={async (params, sorter, filter) => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
materialId: '1',
|
|
||||||
name: 'John Brown',
|
|
||||||
url: 'https://placehold.co/400',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: data || [],
|
|
||||||
total: data.length,
|
|
||||||
success: true,
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
onChange={(fileList) => console.log("fileList", fileList)}
|
|
||||||
maxCount={9}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
import { UploadOutlined } from '@ant-design/icons';
|
|
||||||
import { ActionType, ProTableProps } from '@ant-design/pro-components';
|
|
||||||
import { SelectModal } from '@chageable/components';
|
|
||||||
import { Upload } from 'antd';
|
|
||||||
import React, { MutableRefObject, useEffect, useState } from 'react';
|
|
||||||
import useStyle from './style.style';
|
|
||||||
|
|
||||||
export interface UploadMaterialProps {
|
|
||||||
maxCount: number;
|
|
||||||
onChange: (fileList: any[]) => void;
|
|
||||||
fileList?: any[];
|
|
||||||
request: ProTableProps<any, any>['request'];
|
|
||||||
toolBarRender?: ProTableProps<any, any>['toolBarRender'];
|
|
||||||
actionRef?: MutableRefObject<ActionType | undefined>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UploadMaterial: React.FC<UploadMaterialProps> = (props) => {
|
|
||||||
const {
|
|
||||||
maxCount,
|
|
||||||
fileList: initialFileList,
|
|
||||||
onChange,
|
|
||||||
request,
|
|
||||||
toolBarRender,
|
|
||||||
actionRef,
|
|
||||||
} = props;
|
|
||||||
const [fileList, setFileList] = useState<any[]>([]);
|
|
||||||
const { styles } = useStyle();
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFileList(initialFileList || []);
|
|
||||||
}, [initialFileList]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Upload
|
|
||||||
listType={'picture-card'}
|
|
||||||
fileList={fileList}
|
|
||||||
onRemove={(file) => {
|
|
||||||
const index = fileList.indexOf(file);
|
|
||||||
const newFileList = fileList.slice();
|
|
||||||
newFileList.splice(index, 1);
|
|
||||||
setFileList(newFileList);
|
|
||||||
onChange(newFileList);
|
|
||||||
}}
|
|
||||||
showUploadList={true}
|
|
||||||
multiple={false}
|
|
||||||
accept={'image/*'}
|
|
||||||
maxCount={maxCount || 1}
|
|
||||||
openFileDialogOnClick={false}
|
|
||||||
>
|
|
||||||
{fileList.length >= (maxCount || 1) ? null : (
|
|
||||||
<div className={styles.uploadImage} onClick={() => setOpen(true)}>
|
|
||||||
<div className={styles.uploadImageIcon}>
|
|
||||||
<UploadOutlined /> 单击上传
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Upload>
|
|
||||||
|
|
||||||
{/* 选择图片 */}
|
|
||||||
<SelectModal
|
|
||||||
rowKey={'materialId'}
|
|
||||||
modalProps={{
|
|
||||||
title: '选择图片',
|
|
||||||
open: open,
|
|
||||||
onOk: () => setOpen(false),
|
|
||||||
onCancel: () => setOpen(false),
|
|
||||||
}}
|
|
||||||
tableProps={{
|
|
||||||
actionRef: actionRef,
|
|
||||||
toolBarRender: toolBarRender,
|
|
||||||
rowKey: 'materialId',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: '素材内容',
|
|
||||||
dataIndex: 'url',
|
|
||||||
valueType: 'image',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '素材名称',
|
|
||||||
dataIndex: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '素材分类',
|
|
||||||
dataIndex: ['categoryVO', 'name'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
params: {
|
|
||||||
materialPageQry: {
|
|
||||||
type: 'FILE_IMAGE',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
request: request,
|
|
||||||
pagination: {
|
|
||||||
showSizeChanger: true,
|
|
||||||
showQuickJumper: true,
|
|
||||||
showTotal: (total: number) => `共 ${total} 条`,
|
|
||||||
pageSizeOptions: ['10', '20', '50', '100'],
|
|
||||||
defaultPageSize: 10,
|
|
||||||
hideOnSinglePage: true,
|
|
||||||
position: ['bottomRight'],
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onFinish={(materialList) => {
|
|
||||||
const imageList = materialList.map((materialVO) => {
|
|
||||||
return {
|
|
||||||
uid: materialVO?.materialId,
|
|
||||||
name: materialVO?.name,
|
|
||||||
// @ts-ignore
|
|
||||||
status: 'success',
|
|
||||||
thumbUrl: materialVO?.url,
|
|
||||||
url: materialVO?.url,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const newFileList = [...fileList, ...imageList];
|
|
||||||
setFileList(newFileList);
|
|
||||||
|
|
||||||
onChange(newFileList);
|
|
||||||
}}
|
|
||||||
num={(maxCount || 1) - fileList.length}
|
|
||||||
type={'checkbox'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default UploadMaterial;
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
export { default as Captcha } from './Captcha';
|
|
||||||
export type { CaptchaRes, CheckCaptchaRes, CheckCaptchaProps } from './Captcha/typing';
|
|
||||||
export { default as Foo } from './Foo';
|
|
||||||
export {
|
|
||||||
ProFormBizSelect,
|
|
||||||
ProFormBizTreeSelect,
|
|
||||||
ProFormTMap,
|
|
||||||
ProFormUploadMaterial,
|
|
||||||
ProFormUploadOss,
|
|
||||||
} from './FormItem';
|
|
||||||
export type {
|
|
||||||
ProFormBizSelectHandles,
|
|
||||||
ProFormBizTreeSelectHandles,
|
|
||||||
} from './FormItem';
|
|
||||||
export { default as LeftMenu } from './LeftMenu';
|
|
||||||
export { SelectModal, TMapModal } from './Modal';
|
|
||||||
export type {
|
|
||||||
IAddressProps,
|
|
||||||
ISelectModalProps,
|
|
||||||
ITMapProps,
|
|
||||||
SelectModalHandle,
|
|
||||||
} from './Modal';
|
|
||||||
export { default as UploadMaterial } from './UploadMaterial';
|
|
||||||
export type { UploadMaterialProps } from './UploadMaterial';
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
"declaration": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"jsx": "react",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"@@/*": [".dumi/tmp/*"],
|
|
||||||
"@chageable/components": ["src"],
|
|
||||||
"@chageable/components/*": ["src/*", "*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"typings": "./typings/index.d.ts",
|
|
||||||
"include": [".dumirc.ts", "src/**/*"]
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
declare module '*.css'
|
|
||||||
declare module '*.less'
|
|
||||||
declare module '*.scss'
|
|
||||||
declare module '*.svg'
|
|
||||||
Loading…
Reference in New Issue
Block a user