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:
shenyifei 2026-01-07 00:12:26 +08:00
parent 4bb4dd0ec4
commit 1429319b01
82 changed files with 1116 additions and 5542 deletions

View File

@ -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",

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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 {

View File

@ -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() {

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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) => {
: []; if (item.orderId === orderVO.orderId) {
orderList.forEach( orderList.splice(index, 1);
(item: BusinessAPI.OrderVO, index: number) => { }
if (item.orderId === orderVO.orderId) { });
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,14 +184,9 @@ 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 {
data: data || [], data: data || [],
@ -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)) {

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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 {

View 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;

View File

@ -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';

View File

@ -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';

View File

@ -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 || ''}`;
} }
// 个人账户(银行):私人账户 + 账户名称 + 银行名称 + 账户号码 // 个人账户(银行):私人账户 + 账户名称 + 银行名称 + 账户号码

View 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;
}

View 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;

View File

@ -0,0 +1,76 @@
import mockCaptchaData from './mockDB';
import { CaptchaRes, CheckCaptchaProps, CheckCaptchaRes } from './typing';
import { aesDecrypt } from './utils';
/**
*
*
* HTTP请求获取验证码图片数据
*
*
* @returns {Promise<CaptchaRes>} PromiseCaptchaRes对象
*/
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);
});
};

File diff suppressed because one or more lines are too long

View 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;
/** 获取验证码图片的函数返回一个PromisePromise解析为CaptchaRes类型 */
getPicture?: () => Promise<CaptchaRes>;
/** 检查验证码的函数接收CheckCaptchaProps类型的数据返回一个PromisePromise解析为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;
};

View 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表示一个随机的十六进制数字01UUID的版本为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];
}
}
};

View File

@ -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 {

View File

@ -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';

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
import { defineConfig } from 'dumi';
export default defineConfig({
outputPath: 'docs-dist',
themeConfig: {
name: '百变怪',
},
});

View File

@ -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

View File

@ -1,3 +0,0 @@
module.exports = {
extends: require.resolve('@umijs/lint/dist/config/eslint'),
};

View File

@ -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' },
});

View File

@ -1,6 +0,0 @@
node_modules
/dist
.dumi/tmp
.dumi/tmp-test
.dumi/tmp-production
.DS_Store

View File

@ -1,2 +0,0 @@
/dist
*.yaml

View File

@ -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',
},
},
],
};

View File

@ -1,3 +0,0 @@
{
"extends": "@umijs/lint/dist/config/stylelint"
}

View File

@ -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.

View File

@ -1,43 +0,0 @@
# @chageable/components
[![NPM version](https://img.shields.io/npm/v/@chageable/components.svg?style=flat)](https://npmjs.org/package/@chageable/components)
[![NPM downloads](http://img.shields.io/npm/dm/@chageable/components.svg?style=flat)](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

View File

@ -1 +0,0 @@
This is a guide example.

View File

@ -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

View File

@ -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"
}
}

View File

@ -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>
</>
);
};
```

View File

@ -1,9 +0,0 @@
# Foo
This is an example component.
```jsx
import { Foo } from '@chageable/components';
export default () => <Foo title="Hello dumi!" />
```

View File

@ -1,5 +0,0 @@
import React, { type FC } from 'react';
const Foo: FC<{ title: string }> = (props) => <h4>{props.title}</h4>;
export default Foo;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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)
}}
/>
)
}
```

View File

@ -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}
/>
)
}
```

View File

@ -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;

View File

@ -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';

View File

@ -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/**/*"]
}

View File

@ -1,4 +0,0 @@
declare module '*.css'
declare module '*.less'
declare module '*.scss'
declare module '*.svg'