Commit c03bc644 authored by XieZhiXiong's avatar XieZhiXiong

feat: 添加 地址选择+抽屉组件

parent 5ee2bd8c
@import '~antd/es/style/themes/default.less';
@addressList-prefix: addressList;
.@{addressList-prefix} {
display: block;
&-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: @padding-sm - 2 @padding-md;
background-color: @background-color-base;
border-radius: 4px;
cursor: pointer;
box-sizing: border-box;
border: 1px solid transparent;
transition: all .3s;
&:not(:last-child) {
margin-bottom: @margin-md;
}
&:hover {
background-color: @white;
border-color: @primary-color;
.@{addressList-prefix}-item-actions {
visibility: visible;
}
}
&-left {
display: flex;
align-items: center;
}
&-right {
flex-shrink: 0;
}
&-default {
padding: @padding-xss - 2 @padding-xss;
font-size: 12px;
color: #5C626A;
background-color: #EBECF0;
border-radius: 2px;
}
&-actions {
visibility: hidden;
transition: all .3s;
}
}
}
\ No newline at end of file
/*
* @Author: XieZhiXiong
* @Date: 2021-08-05 14:23:40
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-08-05 18:12:24
* @Description: 地址单选框组
*/
import React, { useState, useEffect, useMemo } from 'react';
import { Radio, Button, Modal, message } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { PublicApi } from '@/services/api';
import { GetLogisticsShipperAddressGetResponse, GetLogisticsReceiverAddressGetResponse } from '@/services/LogisticsApi';
import { IRequestSuccess } from '@/index';
import styles from './index.less';
const { confirm } = Modal;
export type AddressItemType = {
/**
* 主键id
*/
id: number,
/**
* 发货人名称
*/
shipperName: string,
/**
* 收件人名称
*/
receiverName: string,
/**
* 发货地址
*/
fullAddress: string,
/**
* 手机号码
*/
phone: string,
/**
* 是否默认0-否1-是
*/
isDefault: number,
}
export type AddressValueType = Omit<AddressItemType, 'shipperName' | 'receiverName'> & {
/**
* 寄件人 或者 收件人名称
*/
name: string,
}
interface IProps {
/**
* 类型:1 收货地址 2 发货地址,默认 2
*/
addressType: 1 | 2,
/**
* 值
*/
value?: AddressValueType,
/**
* 选择触发改变
*/
onChange?: (value: AddressValueType) => void,
/**
* 是否默认选择 默认地址,是的话会触发 onChange value为默认地址,默认为false
*/
isDefaultAddress?: boolean,
}
const AddressRadioGroup: React.FC<IProps> = (props) => {
const {
addressType = 2,
value,
onChange,
isDefaultAddress = false,
} = props;
const [list, setList] = useState<AddressValueType[]>([]);
const [internalValue, setInternalValue] = useState<AddressValueType | undefined>(undefined);
const triggerChange = (value: AddressValueType) => {
if (onChange) {
onChange(value);
}
};
const getAddressList = () => {
const fetchAction = addressType === 2 ? PublicApi.getLogisticsSelectListShipperAddress() : PublicApi.getLogisticsSelectListReceiverAddress();
fetchAction.then((res: IRequestSuccess<AddressItemType[]>) => {
if (res.code === 1000) {
const defaultItem = res.data?.find((item) => item.isDefault);
setList(res.data?.map(({ shipperName, receiverName, ...rest }) => ({
name: shipperName || receiverName,
...rest,
})));
if (isDefaultAddress && defaultItem) {
const { shipperName, receiverName, ...rest } = defaultItem;
triggerChange({
name: shipperName || receiverName,
...rest,
});
}
}
}).catch((err) => {
console.warn(err);
});
};
useEffect(() => {
if ('value' in props) {
setInternalValue(value);
}
}, [value]);
useEffect(() => {
getAddressList();
}, []);
const handleSelectItem = (id: number) => {
const current = list.find((item) => item.id === id);
if (!('value' in props)) {
setInternalValue(current);
}
if (current) {
triggerChange(current);
}
};
const handleRadioClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.stopPropagation();
};
const handleEdit = (e: React.MouseEvent<HTMLElement, MouseEvent>, id: number) => {
e.stopPropagation();
};
const handleDelete = (e: React.MouseEvent<HTMLElement, MouseEvent>, id: number) => {
e.stopPropagation();
confirm({
title: '提示',
icon: <ExclamationCircleOutlined />,
content: `是否需要删除该地址信息?`,
onOk() {
return (
addressType === 2
? PublicApi.postLogisticsShipperAddressDelete({ id })
: PublicApi.postLogisticsReceiverAddressDelete({ id })
);
},
});
};
// 设置默认地址,这里直接调用修改地址接口
const handleSetDefaultItem = async (e: React.MouseEvent<HTMLElement, MouseEvent>, id: number) => {
e.stopPropagation();
const mesInstance = message.loading({
content: '正在设置',
duration: 0,
});
try {
const res = addressType === 2 ? await PublicApi.getLogisticsShipperAddressGet({ id: `${id}`}) : await PublicApi.getLogisticsReceiverAddressGet({ id: `${id}`});
if (res.code === 1000) {
addressType === 2
? await PublicApi.postLogisticsShipperAddressUpdate({
...(res.data as GetLogisticsShipperAddressGetResponse),
isDefault: 1,
})
: await PublicApi.postLogisticsReceiverAddressUpdate({
...(res.data as GetLogisticsReceiverAddressGetResponse),
isDefault: 1,
});
}
} catch (error) {
console.warn(error);
}
mesInstance();
};
const options = useMemo(() => {
return list.map((item) => ({
value: item.id,
label: `${item.name} ${item.fullAddress} ${item.phone}`,
isDefault: !!item.isDefault,
}));
}, [list]);
return (
<div className={styles.addressList}>
<Radio.Group value={internalValue?.id} style={{ display: 'block' }}>
{options.map((item) => (
<div
key={item.value}
className={styles['addressList-item']}
onClick={() => handleSelectItem(item.value)}
>
<div className={styles['addressList-item-left']}>
<Radio value={item.value} onClick={handleRadioClick}>{item.label}</Radio>
{item.isDefault ? (
<span className={styles['addressList-item-default']}>默认地址</span>
) : (
<div className={styles['addressList-item-actions']}>
<Button
type="text"
size="small"
onClick={(e) => handleSetDefaultItem(e, item.value)}
>
设为默认地址
</Button>
</div>
)}
</div>
<div className={styles['addressList-item-right']}>
<div className={styles['addressList-item-actions']}>
<Button
type="text"
size="small"
onClick={(e) => handleEdit(e, item.value)}
>
编辑
</Button>
<Button
type="text"
size="small"
onClick={(e) => handleDelete(e, item.value)}
>
删除
</Button>
</div>
</div>
</div>
))}
</Radio.Group>
</div>
);
};
export default AddressRadioGroup;
/*
* @Author: XieZhiXiong
* @Date: 2021-08-05 14:54:18
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-08-05 14:54:19
* @Description:
*/
import React from 'react';
import { connect } from '@formily/antd';
import AddressRadioGroup from '../AddressRadioGroup';
const AddressRadioGroupFormilyItem = connect()((props) => {
const {
dataSource,
value,
onChange,
addressType,
...rest
} = props;
return (
<div style={{ flex: 1 }}>
<AddressRadioGroup
addressType={addressType}
value={value}
onChange={onChange}
{...rest}
/>
</div>
);
});
export default AddressRadioGroupFormilyItem;
@import '~antd/es/style/themes/default.less';
.address-select {
display: flex;
align-items: center;
&-input {
flex: 1;
}
&-action {
margin-left: @margin-md;
}
}
.label-required {
font-size: 12px;
color: #909399;
&::after {
margin-left: 8px;
font-size: 12px;
font-family: SimSun, sans-serif;
color: #ff4d4f;
content: '*';
}
}
\ No newline at end of file
/*
* @Author: XieZhiXiong
* @Date: 2021-08-05 10:28:06
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-08-05 18:37:33
* @Description: 地址选择 FormItem
*/
import React, { useState, useEffect, useMemo } from 'react';
import { Select, Button, Drawer, Divider } from 'antd';
import { PublicApi } from '@/services/api';
import {
createAsyncFormActions,
FormEffectHooks,
} from '@formily/antd';
import { DatePicker } from '@formily/antd-components';
import { IRequestSuccess } from '@/index';
import { useLinkEnumEffect } from '@/components/NiceForm/linkages/linkEnum';
import { useAsyncSelect } from '@/formSchema/effects/useAsyncSelect';
import NiceForm from '@/components/NiceForm';
import { schema } from './schema';
import { AddressItemType, AddressValueType } from './components/AddressRadioGroup';
import AddressRadioGroup from './components/AddressRadioGroupFormilyItem';
import styles from './index.less';
const formActions = createAsyncFormActions();
const {
onFormMount$,
onFieldValueChange$,
} = FormEffectHooks;
interface IProps {
/**
* 类型:1 收货地址 2 发货地址,默认 2
*/
addressType?: 1 | 2,
/**
* 值
*/
value?: AddressValueType,
/**
* 选择触发改变
*/
onChange?: (value: AddressValueType) => void,
/**
* 是否默认选择 默认地址,是的话会触发 onChange value为默认地址,默认为false
*/
isDefaultAddress?: boolean,
}
export type SubmitValuesType = {
/**
* 地址单选选中的值
*/
address?: AddressValueType,
/**
* 收件人名称 或 发货人名称
*/
name: string,
/**
* 省级id
*/
provinceId: number,
/**
* 市级id
*/
cityId: number,
/**
* 区级id
*/
areaId: number,
/**
* 详细地址
*/
detailed: string,
/**
* 邮编
*/
postalCode: string,
/**
* 区号
*/
telCode: string,
/**
* 手机号码
*/
phone: string,
/**
* 电话号码
*/
tel: string,
/**
* 是否是默认
*/
isDefault: boolean,
}
const AddressSelect: React.FC<IProps> = (props) => {
const {
addressType = 2,
value,
onChange,
isDefaultAddress = false,
} = props;
const [options, setOptions] = useState<{ label, value }[]>([]);
const [internalValue, setInternalValue] = useState<AddressValueType>();
const [visibleDrawer, setVisibleDrawer] = useState(false);
const [submitLoading, setSubmitLoading] = useState(false);
const triggerChange = (value: AddressValueType) => {
if (onChange) {
onChange(value);
}
};
const getAddressList = () => {
const fetchAction = addressType === 2 ? PublicApi.getLogisticsSelectListShipperAddress() : PublicApi.getLogisticsSelectListReceiverAddress();
fetchAction.then((res: IRequestSuccess<AddressItemType[]>) => {
if (res.code === 1000) {
const defaultItem = res.data?.find((item) => item.isDefault);
setOptions(res.data?.map((item) => ({
value: item.id,
label: `${item.shipperName || item.receiverName} ${item.fullAddress} ${item.phone}`
})));
if (isDefaultAddress && defaultItem) {
const { shipperName, receiverName, ...rest } = defaultItem;
triggerChange({
name: shipperName || receiverName,
...rest,
});
}
}
}).catch((err) => {
console.warn(err);
});
};
// 获取手机code
const fetchTelCode = async () => {
const { data, code } = await PublicApi.getManageCountryAreaGetTelCode();
if (code === 1000) {
return data;
}
return [];
};
useEffect(() => {
getAddressList();
}, []);
const handleVisibleDrawer = (flag?: boolean) => {
setVisibleDrawer(!!flag);
};
const handleSubmit = (values: SubmitValuesType) => {
console.log('values', values)
};
const useFields = (): any => (
useMemo(() => ({
AddressRadioGroup,
}), [])
);
const AddressLabel = (
<div className={styles['label-required']}>
收件地区
</div>
);
const handleAddAddress = () => {
formActions.setFieldState('ADDRESS_NEW', state => {
state.visible = !state.visible;
});
};
const AddButton = useMemo(() => {
return () => (
<div>
<Button onClick={handleAddAddress}>新增收货地址</Button>
<Divider style={{ marginBottom: 4 }} />
</div>
);
}, []);
return (
<>
<div className={styles['address-select']}>
<Select
options={options}
value={value?.id}
/>
<Button
onClick={() => handleVisibleDrawer(true)}
className={styles['address-select-action']}
>
管理
</Button>
</div>
<Drawer
title="更改收货地址信息"
width={800}
onClose={() => handleVisibleDrawer(false)}
visible={visibleDrawer}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={() => handleVisibleDrawer(false)} style={{ marginRight: 16 }}>
取 消
</Button>
<Button
onClick={() => formActions.submit()}
type="primary"
loading={submitLoading}
>
确 定
</Button>
</div>
}
destroyOnClose
>
<NiceForm
previewPlaceholder="' '"
components={{
AddButton,
}}
expressionScope={{
AddressLabel,
}}
fields={useFields()}
effects={($, { setFieldState }) => {
onFormMount$().subscribe(async () => {
const areaRes = await PublicApi.getManageAreaAll();
if (areaRes.code === 1000) {
const { data } = areaRes;
formActions.setFieldState('provinceId', targetState => {
targetState.originData = data;
targetState.props.enum = data.map(v => ({
label: v.name,
value: v.id,
}));
});
}
});
useLinkEnumEffect('areaResponses', result => result.map(v => ({
label: v.name,
value: v.id,
})));
useAsyncSelect('telCode', fetchTelCode);
}}
actions={formActions}
schema={schema}
onSubmit={values => handleSubmit(values)}
/>
</Drawer>
</>
);
};
export default AddressSelect;
/*
* @Author: XieZhiXiong
* @Date: 2021-08-05 14:02:46
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-08-05 18:34:01
* @Description:
*/
import { ISchema } from '@formily/antd';
import { PATTERN_MAPS } from '@/constants/regExp';
export const schema: ISchema = {
type: 'object',
properties: {
ADDRESS_LIST: {
type: 'object',
'x-component': 'FlagBox',
'x-component-props': {
title: '选择收货地址',
border: false,
},
properties: {
MEGA_LAYOUT_1: {
type: 'object',
'x-component': 'Mega-Layout',
'x-component-props': {
wrapperCol: 24,
},
properties: {
address: {
type: 'string',
'x-component': 'AddressRadioGroup',
'x-component-props': {},
'x-rules': [
{
required: true,
message: '请选择地址',
},
],
},
},
},
},
},
ADD_ACTION: {
type: 'object',
'x-component': 'AddButton',
},
ADDRESS_NEW: {
type: 'object',
'x-component': 'FlagBox',
'x-component-props': {
title: '填写收货信息',
border: false,
},
visible: false,
properties: {
MEGA_LAYOUT_2: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
labelCol: 4,
wrapperCol: 20,
labelAlign: 'left',
},
properties: {
name: {
type: 'string',
title: '收件人',
'x-component-props': {
placeholder: '请输入',
},
'x-rules': [
{
required: true,
message: '请输入收件人',
},
{
limitByte: true, // 自定义校验规则
maxByte: 10,
}
],
required: true,
},
MEGA_LAYOUT_2_1: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
label: '{{AddressLabel}}',
wrapperCol: 24,
},
required: true,
properties: {
MEGA_LAYOUT_2_1_1: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
grid: true,
full: true,
autoRow: true,
columns: 3,
},
properties: {
provinceId: {
type: 'string',
enum: [],
'x-component-props': {
placeholder: '- 省 -',
},
'x-linkages': [
{
type: 'value:linkage',
condition: '{{ !!$self.value }}', // $value,不触发不知道咋回事
origin: 'provinceId',
target: 'cityId',
},
],
required: true,
},
cityId: {
type: 'string',
enum: [],
'x-component-props': {
placeholder: '- 市 -',
},
'x-linkages': [
{
type: 'value:linkage',
condition: '{{ !!$self.value }}', // $value,不触发不知道咋回事
origin: 'cityId',
target: 'areaId',
},
],
required: true,
},
areaId: {
type: 'string',
enum: [],
'x-component-props': {
placeholder: '- 县 / 区 -',
},
required: true,
},
},
},
},
},
detailed: {
type: 'string',
title: '详细地址',
'x-component-props': {
placeholder: '请输入详细地址(最长50个字符,25个汉字)',
},
'x-rules': [
{
required: true,
message: '请输入详细地址',
},
{
limitByte: true, // 自定义校验规则
maxByte: 50,
}
],
},
postalCode: {
type: 'string',
title: '邮编',
'x-component-props': {},
'x-rules': [
{
limitByte: true, // 自定义校验规则
maxByte: 16,
}
],
},
MEGA_LAYOUT_2_2: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
label: '手机号码',
wrapperCol: 24,
},
properties: {
MEGA_LAYOUT2_1: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
grid: true,
full: true,
},
properties: {
telCode: {
type: 'string',
enum: [],
'x-component-props': {
placeholder: '请选择',
},
required: true,
},
phone: {
type: 'string',
required: true,
'x-mega-props': {
span: 3,
},
'x-component-props': {
placeholder: '请输入你的手机号码',
maxLength: 11,
},
'x-rules': [
{
pattern: PATTERN_MAPS.phone,
message: '请输入正确格式的手机号',
},
],
},
},
},
},
},
tel: {
type: 'string',
title: '电话号码',
'x-component-props': {},
'x-rules': [
{
limitByte: true, // 自定义校验规则
maxByte: 16,
}
],
},
isDefault: {
type: 'boolean',
title: '是否默认',
'x-component': 'Switch',
},
},
},
},
},
},
};
\ No newline at end of file
/*
* @Author: XieZhiXiong
* @Date: 2021-08-05 11:26:43
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-08-05 11:30:21
* @Description:
*/
import React from 'react';
import { connect } from '@formily/antd';
import AddressSelect from '@/components/AddressSelect';
const CustomAddressSelect = connect()((props) => {
const {
dataSource,
value,
onChange,
addressType,
...rest
} = props;
return (
<div style={{ flex: 1 }}>
<AddressSelect
addressType={addressType}
value={value}
onChange={onChange}
{...rest}
/>
</div>
);
});
export default CustomAddressSelect;
...@@ -36,6 +36,7 @@ import AreaSelect from './components/AreaSelect'; ...@@ -36,6 +36,7 @@ import AreaSelect from './components/AreaSelect';
import CustomSelect from './components/CustomSelect'; import CustomSelect from './components/CustomSelect';
import CheckboxGroup from './components/CheckboxGroup'; import CheckboxGroup from './components/CheckboxGroup';
import CustomRadioGroup from './components/CustomRadioGroup'; import CustomRadioGroup from './components/CustomRadioGroup';
import CustomAddressSelect from './components/CustomAddressSelect';
import { useLinkComponentProps } from './linkages/linkComponentProps'; import { useLinkComponentProps } from './linkages/linkComponentProps';
import Loading from '../Loading'; import Loading from '../Loading';
import MultAddress from './components/MultAddress'; import MultAddress from './components/MultAddress';
...@@ -129,6 +130,7 @@ export const componentExport = { ...@@ -129,6 +130,7 @@ export const componentExport = {
CustomSelect, CustomSelect,
CheckboxGroup, CheckboxGroup,
CustomRadioGroup, CustomRadioGroup,
CustomAddressSelect,
Switch, Switch,
} }
const NiceForm: React.FC<NiceFormProps> = props => { const NiceForm: React.FC<NiceFormProps> = props => {
......
...@@ -463,14 +463,30 @@ export const addSchema = (orderType: number): ISchema => { ...@@ -463,14 +463,30 @@ export const addSchema = (orderType: number): ISchema => {
}, },
], ],
}, },
// // 退货发货地址
// shippingAddress: {
// title: '退货发货地址',
// type: 'string',
// visible: false,
// 'x-component': 'AddressFormItem',
// 'x-component-props': {
// dataSource: [],
// },
// 'x-rules': [
// {
// required: true,
// message: '请选择退货发货地址',
// },
// ],
// },
// 退货发货地址 // 退货发货地址
shippingAddress: { shippingAddress: {
title: '退货发货地址', title: '退货发货地址',
type: 'string', type: 'string',
visible: false, visible: false,
'x-component': 'AddressFormItem', 'x-component': 'CustomAddressSelect',
'x-component-props': { 'x-component-props': {
dataSource: [], isDefaultAddress: true,
}, },
'x-rules': [ 'x-rules': [
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment