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-editor": "^1.3.0",
|
||||
"@ant-design/x": "^1.1.0",
|
||||
"@chageable/components": "workspace:^",
|
||||
"@formily/antd-v5": "^1.2.3",
|
||||
"@formily/core": "^2.3.2",
|
||||
"@formily/react": "^2.3.2",
|
||||
|
||||
@ -2,14 +2,13 @@
|
||||
|
||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||
import { VersionChecker } from '@/components';
|
||||
import { VersionChecker, LeftMenu } from '@/components';
|
||||
import Avatar from '@/layout/guide/Avatar';
|
||||
import { auth } from '@/services';
|
||||
import { Navigate } from '@@/exports';
|
||||
import { RunTimeLayoutConfig } from '@@/plugin-layout/types';
|
||||
import { RequestConfig } from '@@/plugin-request/request';
|
||||
import { InfoCircleFilled } from '@ant-design/icons';
|
||||
import { LeftMenu } from '@chageable/components';
|
||||
import '@nutui/nutui-react/dist/style.scss';
|
||||
import { history } from '@umijs/max';
|
||||
import { Alert, Button, message, notification, Spin } from 'antd';
|
||||
|
||||
@ -4,6 +4,9 @@ import {
|
||||
BoxSpecList,
|
||||
MaterialList,
|
||||
ModeType,
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
ProFormUploadMaterial,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
@ -15,11 +18,6 @@ import {
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProListMetas } from '@ant-design/pro-list';
|
||||
import {
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
ProFormUploadMaterial,
|
||||
} from '@chageable/components';
|
||||
import { Image } from 'antd';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ import {
|
||||
BizValueType,
|
||||
CostItemList,
|
||||
ModeType,
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import groupby from '@/utils/groupby';
|
||||
@ -16,10 +18,6 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import {
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@chageable/components';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
interface ICostListProps {
|
||||
|
||||
@ -4,6 +4,8 @@ import {
|
||||
BizValueType,
|
||||
CostList,
|
||||
ModeType,
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import groupby from '@/utils/groupby';
|
||||
@ -14,10 +16,6 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import {
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@chageable/components';
|
||||
import { Space, Typography } from 'antd';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import auth from '@/services/auth';
|
||||
import { Captcha, CheckCaptchaProps } from '@chageable/components';
|
||||
import { Captcha, CheckCaptchaProps } from '@/components';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface ICaptchaModalProps {
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { BizContainer, BizValueType, MaterialList } from '@/components';
|
||||
import {
|
||||
BizContainer,
|
||||
BizValueType,
|
||||
MaterialList,
|
||||
ProFormUploadMaterial,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
import { useIntl } from '@@/exports';
|
||||
@ -9,7 +14,6 @@ import {
|
||||
ProFormTextArea,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import { useRef } from 'react';
|
||||
|
||||
export default function ChannelList() {
|
||||
|
||||
@ -3,13 +3,13 @@ import {
|
||||
BizValueType,
|
||||
CompanyPaymentAccountList,
|
||||
ModeType,
|
||||
ProFormUploadMaterial,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
import { useIntl } from '@@/exports';
|
||||
import { ProColumns, ProFormText } from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import React from 'react';
|
||||
|
||||
interface ICompanyListProps {
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
LightFilter,
|
||||
ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import { SelectModal } from '@chageable/components';
|
||||
import { SelectModal } from '@/components';
|
||||
import { Alert, ModalProps, Row, Tag } from 'antd';
|
||||
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 { formatParam } from '@/utils/formatParam';
|
||||
import { pagination } from '@/utils/pagination';
|
||||
@ -7,7 +7,6 @@ import {
|
||||
LightFilter,
|
||||
ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import { SelectModal } from '@chageable/components';
|
||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useIntl } from '@@/exports';
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
EmployeeRoleUpdate,
|
||||
MaterialList,
|
||||
ModeType,
|
||||
ProFormUploadMaterial,
|
||||
RestPassword,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
@ -18,7 +19,6 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
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,
|
||||
MaterialCategoryList,
|
||||
ModeType,
|
||||
ProFormBizTreeSelect,
|
||||
ProFormBizTreeSelectHandles,
|
||||
ProFormUploadOss,
|
||||
Remark,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
@ -12,11 +15,6 @@ import {
|
||||
ProFormDependency,
|
||||
ProFormRadio,
|
||||
} from '@ant-design/pro-components';
|
||||
import {
|
||||
ProFormBizTreeSelect,
|
||||
ProFormBizTreeSelectHandles,
|
||||
ProFormUploadOss,
|
||||
} from '@chageable/components';
|
||||
import { useModel } from '@umijs/max';
|
||||
import { TreeSelect } from 'antd';
|
||||
import moment from 'moment';
|
||||
@ -305,7 +303,7 @@ export default function MaterialList(props: IMaterialListProps) {
|
||||
const ossToken = await getOssToken();
|
||||
const client = new OSS({
|
||||
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
|
||||
region: "oss-" + ossToken?.region,
|
||||
region: 'oss-' + ossToken?.region,
|
||||
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
||||
accessKeyId: ossToken?.accessKeyId,
|
||||
accessKeySecret: ossToken?.accessKeySecret,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Remark } from '@/components';
|
||||
import { Remark, SelectModal } from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
import { pagination } from '@/utils/pagination';
|
||||
@ -8,7 +8,6 @@ import {
|
||||
ProColumns,
|
||||
ProFormTreeSelect,
|
||||
} from '@ant-design/pro-components';
|
||||
import { SelectModal } from '@chageable/components';
|
||||
import { ModalProps, TreeSelect } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ import {
|
||||
IconPicker,
|
||||
ModeType,
|
||||
PermissionList,
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@/components';
|
||||
import useSearch from '@/models/useSearch';
|
||||
import { business } from '@/services';
|
||||
@ -18,10 +20,6 @@ import {
|
||||
ProFormTreeSelect,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import {
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@chageable/components';
|
||||
import { TreeSelect } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import {
|
||||
CostList,
|
||||
ModeType,
|
||||
OrderSelect,
|
||||
ProFormBizSelect,
|
||||
ProFormBizSelectHandles,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import groupby from '@/utils/groupby';
|
||||
@ -16,8 +18,6 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProFormBizSelect } from '@chageable/components';
|
||||
import { ProFormBizSelectHandles } from '@chageable/components/src';
|
||||
import { Button, Space } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -8,10 +8,9 @@ import {
|
||||
ProColumns,
|
||||
ProFormSelect,
|
||||
} 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 React, { useEffect, useRef, useState } from 'react';
|
||||
import { OrderFormItem, OrderList } from '@/components';
|
||||
|
||||
export interface IOrderModalProps extends ModalProps {
|
||||
title: string;
|
||||
@ -142,16 +141,12 @@ export default function OrderModal(props: IOrderModalProps) {
|
||||
|
||||
function setDealerVOStorage(orderVO: BusinessAPI.OrderVO) {
|
||||
const localOrderList = localStorage.getItem(sessionKey);
|
||||
const orderList = localOrderList
|
||||
? JSON.parse(localOrderList)
|
||||
: [];
|
||||
orderList.forEach(
|
||||
(item: BusinessAPI.OrderVO, index: number) => {
|
||||
if (item.orderId === orderVO.orderId) {
|
||||
orderList.splice(index, 1);
|
||||
}
|
||||
},
|
||||
);
|
||||
const orderList = localOrderList ? JSON.parse(localOrderList) : [];
|
||||
orderList.forEach((item: BusinessAPI.OrderVO, index: number) => {
|
||||
if (item.orderId === orderVO.orderId) {
|
||||
orderList.splice(index, 1);
|
||||
}
|
||||
});
|
||||
if (orderList.length < 5) {
|
||||
orderList.unshift(orderVO);
|
||||
localStorage.setItem(sessionKey, JSON.stringify(orderList));
|
||||
@ -189,14 +184,9 @@ export default function OrderModal(props: IOrderModalProps) {
|
||||
...params,
|
||||
},
|
||||
request: async (params, sorter, filter) => {
|
||||
const { data, success, totalCount } =
|
||||
await business.order.pageOrder({
|
||||
orderPageQry: formatParam<typeof params>(
|
||||
params,
|
||||
sorter,
|
||||
filter,
|
||||
),
|
||||
});
|
||||
const { data, success, totalCount } = await business.order.pageOrder({
|
||||
orderPageQry: formatParam<typeof params>(params, sorter, filter),
|
||||
});
|
||||
|
||||
return {
|
||||
data: data || [],
|
||||
@ -210,10 +200,7 @@ export default function OrderModal(props: IOrderModalProps) {
|
||||
},
|
||||
tableAlertRender: ({ selectedRowKeys, selectedRows }) => {
|
||||
// selectedRows 和 selectedList 组合在一起,去重,
|
||||
const selectedRowsMap = new Map<
|
||||
string,
|
||||
BusinessAPI.OrderVO
|
||||
>();
|
||||
const selectedRowsMap = new Map<string, BusinessAPI.OrderVO>();
|
||||
selectedRows.forEach((item: BusinessAPI.OrderVO) => {
|
||||
if (item) {
|
||||
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 { formatBankCard, formatIdCard, formatPhone } from '@/utils/format';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
@ -11,7 +11,6 @@ import {
|
||||
ProColumns,
|
||||
ProFormSelect,
|
||||
} from '@ant-design/pro-components';
|
||||
import { SelectModal } from '@chageable/components';
|
||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
InsertPosition,
|
||||
MaterialList,
|
||||
OrderSupplierInvoiceList,
|
||||
ProFormUploadMaterial,
|
||||
SupplierFarmerList,
|
||||
SupplierInvoiceList,
|
||||
SupplierSelect,
|
||||
@ -24,7 +25,6 @@ import {
|
||||
RouteContext,
|
||||
RouteContextType,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import { Col, Row, Space, Table } from 'antd';
|
||||
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 { formatParam } from '@/utils/formatParam';
|
||||
import { formLayout } from '@/utils/formLayout';
|
||||
@ -11,7 +16,6 @@ import {
|
||||
RouteContext,
|
||||
RouteContextType,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import { message, Space, Typography } from 'antd';
|
||||
|
||||
interface IChargingPilePurchaseConfigProps {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MaterialList } from '@/components';
|
||||
import { MaterialList, ProFormUploadMaterial } from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
import { formLayout } from '@/utils/formLayout';
|
||||
@ -8,7 +8,6 @@ import {
|
||||
ProFormText,
|
||||
ProFormUploadDragger,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import { Col, message, Row, Space } from 'antd';
|
||||
import { useRef } from 'react';
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { MaterialList } from '@/components';
|
||||
import { MaterialList, ProFormUploadMaterial } from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatParam } from '@/utils/formatParam';
|
||||
import { formLayout } from '@/utils/formLayout';
|
||||
import { ActionType, ProForm, ProFormText } from '@ant-design/pro-components';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import { Col, message, Row, Space } from 'antd';
|
||||
import { useRef } from 'react';
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
BizValueType,
|
||||
MaterialList,
|
||||
ModeType,
|
||||
ProFormUploadMaterial,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatBankCard, formatIdCard, formatPhone } from '@/utils/format';
|
||||
@ -15,7 +16,6 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
interface ISupplierFarmerListProps {
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
ProColumns,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { SelectModal } from '@chageable/components';
|
||||
import { SelectModal } from '@/components';
|
||||
import { Alert, ModalProps, Row, Tag } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
BizValueType,
|
||||
MaterialList,
|
||||
ModeType,
|
||||
ProFormUploadMaterial,
|
||||
} from '@/components';
|
||||
import { business } from '@/services';
|
||||
import { formatBankCard, formatPhone } from '@/utils/format';
|
||||
@ -15,7 +16,6 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
|
||||
import { ProFormUploadMaterial } from '@chageable/components';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
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 { formatParam } from '@/utils/formatParam';
|
||||
import { pagination } from '@/utils/pagination';
|
||||
@ -7,7 +7,6 @@ import {
|
||||
LightFilter,
|
||||
ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import { SelectModal } from '@chageable/components';
|
||||
import { Alert, ModalProps, Row, Space, Tag } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
export * from './Agreement';
|
||||
export * from './BasicData';
|
||||
export * from './Biz';
|
||||
export { default as Captcha } from './Captcha';
|
||||
export type {
|
||||
CaptchaRes,
|
||||
CheckCaptchaProps,
|
||||
CheckCaptchaRes,
|
||||
} from './Captcha/typing';
|
||||
export { default as CaptchaModal } from './CaptchaModal';
|
||||
export * from './Channel';
|
||||
export * from './Company';
|
||||
@ -9,17 +15,22 @@ export * from './Delivery';
|
||||
export * from './Editor';
|
||||
export * from './Employee';
|
||||
export * from './Expense';
|
||||
export * from './FormItem';
|
||||
export { default as IconFont } from './Iconfont';
|
||||
export { default as IconPicker } from './Iconfont/IconPicker';
|
||||
export { default as LeftMenu } from './LeftMenu';
|
||||
export * from './Material';
|
||||
export * from './Menu';
|
||||
export * from './Modal';
|
||||
export { SelectModal, TMapModal } from './Modal';
|
||||
export * from './Order';
|
||||
export * from './PaymentTask';
|
||||
export * from './Permission';
|
||||
export { default as PhoneContainer } from './PhoneContainer';
|
||||
export * from './Platform';
|
||||
export * from './Remark';
|
||||
export * from './Role';
|
||||
export * from './Setting';
|
||||
export * from './Supplier';
|
||||
export { default as UploadMaterial } from './UploadMaterial';
|
||||
export type { UploadMaterialProps } from './UploadMaterial';
|
||||
export * from './User';
|
||||
|
||||
@ -16,6 +16,7 @@ export function formatAccountDisplay(
|
||||
accountType,
|
||||
accountName,
|
||||
bankName,
|
||||
branchName,
|
||||
accountNumber,
|
||||
} = account;
|
||||
|
||||
@ -56,7 +57,7 @@ export function formatAccountDisplay(
|
||||
|
||||
// 对公账户:公司账户 + 账户名称 + 银行名称 + 账户号码
|
||||
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 { Captcha, CheckCaptchaProps } from '@chageable/components';
|
||||
import { Captcha, CheckCaptchaProps } from '@/components';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface ICaptchaModalProps {
|
||||
|
||||
@ -1 +1,7 @@
|
||||
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