antd is a React framework we commonly use (it is equal to not said, haha)
What is ProComponents?
For a rookie who has used this component to develop for half a year, what is ProComponents,
It is the enhanced integrated version of antd, which has a high degree of integration and is very convenient to use (for me as a rookie, it is easy to step on the pit), whether it is elementUi vant antd..., the usage of the components is roughly similar, take a time to record, also To deepen the impression, it is easy to come across new components in the future.
ProFormDigit
This code, why should I put form.item in ProFormDigit?
That's because ProFormDigit has a bug,
Because if I click submit directly, I will skip the ProFormDigit restrictions on the input content, including (number, digits, maximum value, minimum value) will not have time to verify, submit it~~
<Form.Item
style={{width:'300px'}}
name="percent"
rules={[
{
required: true,
message: '请输入调整比例',
},
{
pattern:/^([1-9][0-9]{0,1}|100)$/,
message:'请输入1到100之间的整数',
},
]}
>
<ProFormDigit
fieldProps={{ precision: 0 }}
label=""
width={150}
placeholder="请输入调整比例"
min={0}
max={100}
/>
</Form.Item>
This component is sauce purple~
Time component ProFormDateRangePicker
generally use
import {
ProFormDateRangePicker,
} from '@ant-design/pro-form'
<ProFormDateRangePicker width="md" name={['contract', 'createTime']} label="合同生效时间" />
use
const columns = defineProTableColumn<MaintenanceListVo>([
{
title: '投诉时间',
dataIndex: 'createTime',
key: 'createTime',
hideInSearch: true,
},
{
title:'',
dataIndex: 'createTime',
key: 'createTime',
valueType: 'dateRange',
hideInTable: true,
fieldProps: {
placeholder: ['投诉时间','投诉时间'],
},
search: {
transform: (value) => {
return {
startTime: value[0],
endTime: value[1],
};
},
},
},
]);
/** 处理表格列 */
export function useColumns() {
return { columns };
}
ProFormSelect selection box
<ProFormSelect
width={364}
rules={[
{
required: true,
message: '请选择要转让的员工',
},
]}
placeholder="请选择人员"
fieldProps={{
onChange: (e) => {
setData(staffList.find((item) => item.employeeId == e));
},
}}
help={currentStore?.storeType === 'community' && '转让后你就不是该小区的负责人,请慎用!'}
name="employeeId"
options={staffList.map((item) => ({
label: item.employeeName,
value: item.employeeId,
}))}
label="转让到"
/>
ProFormDependency Whether to display hidden form items
<ProFormSelect
width="md"
placeholder="请选择设备类型"
options={[
{
value: 'UNIT',
label: '单元设备',
},
{
value: 'AREA',
label: '小区设备',
},
]}
rules={[
{
required: true,
message: '请选择设备类型',
},
]}
name="deviceType"
label="设备类型"
/>
<ProFormDependency name={['deviceType', 'buildingId']}>
{({ deviceType, buildingId }) => {
return deviceType === 'UNIT' ? (
<div style={{ gap: '0px' }}>
<ProFormSelect
width="md"
required
placeholder="请选择楼栋"
options={buildingList}
rules={[
{
required: true,
message: '请选择楼栋',
},
]}
name="buildingId"
label="绑定位置"
/>
<ProFormSelect
width="md"
required
name="unitId"
placeholder="请选择单元"
options={buildingId ? unitObj[`${buildingId}`] : []}
rules={[
{
required: true,
message: '请输入单元',
},
]}
/>
</div>
) : (
<ProFormSelect
width="md"
placeholder="请选择区域"
options={areaList}
// fieldNames={{ label: 'areaName', value: 'areaId' }
rules={[
{
required: true,
message: '请选择区域',
},
]}
name="areaId"
label="绑定位置"
/>
);
}}
</ProFormDependency>
ProFormTextArea same as textArea
<ProFormTextArea width="md" name="content" label="问题描述" placeholder="请输入您的问题描述" />
ProFormUploadButton upload picture
<ProFormUploadButton
name="images"
label="上传图片"
tooltip="仅支持.jpg"
max={2}
fieldProps={{
listType: 'picture',
accept: 'jpg',
name: 'file',
data: { fileType: 'IMAGE' },
headers: { 'User-Token': localStorage.getItem('User-Token') || '' },
className: 'upload-list-inline',
}}
action="http://67.104.133.180:8092/api/exterior/upload/file"
/>
ProFormText
<ProFormText
fieldProps={{
size: 'large',
prefix: <CTIcon type="mima" className={styles.prefixIcon} />,
suffix:
state.timeCount > -1 ? (
`${state.timeCount}s`
) : (
<a
style={{ zIndex: 99 }}
onClick={() => sendCheckPicture({ cellphone: formRef?.current?.getFieldsValue().cellphone })}
>
获取验证码
</a>
),
}}
name="checkCode"
allowClear={false}
placeholder={'请输入验证码'}
rules={[
{
required: true,
message: '请输入短信验证码!',
},
]}
/>
<ProFormText
name="newPassword"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon} />,
autoComplete: 'off',
className: readOnly ? styles.pwd : '',
allowClear: false,
suffix: readOnly ? (
<EyeInvisibleOutlined onClick={() => setReadOnly(false)} />
) : (
<EyeTwoTone onClick={() => setReadOnly(true)} />
),
}}
placeholder={'设置新密码'}
rules={[
{
required: true,
message: '请填写新密码!',
},
{
pattern:
/^[\u4E00-\u9FA5A-Za-z0-9`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘',。、]{8,20}$/,
message: '请输入8-20位字母/数字/标点符号',
},
]}
/>
<ProFormText
name="surePassword"
dependencies={['newPassword']}
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon} />,
autoComplete: 'off',
className: sureReadOnly ? styles.pwd : '',
allowClear: false,
suffix: sureReadOnly ? (
<EyeInvisibleOutlined onClick={() => setSureReadOnly(false)} />
) : (
<EyeTwoTone onClick={() => setSureReadOnly(true)} />
),
}}
placeholder={'再次确认新密码'}
rules={[
{
required: true,
message: '请再次填写新密码',
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject('与新密码不同,请重新确认新密码');
},
}),
]}
/>
Component rules verification writing
rules={[
{
validator: (_, value) => {
if (!value) {
return Promise.resolve();
}
if (value && value.length < 6) {
return Promise.resolve();
}
if (value.length > 5) {
return Promise.reject('标签最大数量不能超过5个');
}
},
},
]}
rules={[
{
required: true,
message: '请输入联系方式',
},
{
pattern: /^1\d{10}$/,
message: '请输入正确的手机号格式',
},
{
pattern: /^[\s\S]*.*[^\s][\s\S]*$/,
message: '联系方式不可为空',
},
]}
ProFormUploadButton upload
Remove duplicate pictures in oncChange
limits the size of the picture in beforeUpload. Returning true means it can be uploaded. If it is too large, it returns Upload.LIST_IGNORE, which means it cannot be uploaded and will not appear on the page
import { Upload } from 'antd';
const handleChange = (pic: UploadChangeParam) => {
const filterRes = pic.fileList.filter((f) => f.name === pic.file.name && f.size === pic.file.size);
filterRes.length > 1 && pic.fileList.pop();
};
const beforeUpload = (file: UploadFile) => {
const isLt2M = (file.size || 0) < 1024 * 1024 * 2;
if (!isLt2M) {
message.error('图片不能超过2MB!');
}
console.log('Upload.LIST_IGNORE',Upload.LIST_IGNORE)
return isLt2M ? true : Upload.LIST_IGNORE;
};
<ProFormUploadButton
name="images"
label="上传图片"
tooltip="仅支持jpeg、jpg、png"
max={6}
onChange={handleChange}
fieldProps={{
listType: 'picture',
accept: '.jpeg,.jpg,.png',
name: 'file',
data: { fileType: 'IMAGE' },
headers: { 'User-Token': localStorage.getItem('User-Token') || '' },
className: 'upload-list-inline',
beforeUpload: beforeUpload,
}}
action={`${HOST_BASE}/api/exterior/upload/file`}
/>
Display pictures of all aspects of the product with a magnifying glass
import { CTIcon, CTImage } from '@/components';
import cm from 'classnames';
import { FC, useEffect, useMemo, useState } from 'react';
import ReactImageZoom from 'react-image-zoom';
import { store } from '../../store';
import './index.less';
const MAX_COUNT = 3;
/**
* 多规格: 零售价显示区间价
* 多单位: 展示换算关系
*/
const GoodsImageInfo: FC = () => {
const { unibuyGoodsDeatil, currentObj } = store.useState();
const imageList = useMemo(() => {
return currentObj.skuPic || [];
}, [currentObj]);
const [index, setIndex] = useState(0);
const [selectImage, setSelectImage] = useState<string>(imageList[0]);
const count = imageList.length;
const isStart = index <= 0;
const isEnd = index >= imageList.length - MAX_COUNT;
useEffect(() => {
setIndex(0);
setSelectImage(imageList[0]);
}, [imageList]);
return (
<div className="goods-image">
{selectImage ? (
<div className="goods-image-detail">
<ReactImageZoom width={238} height={238} offset={{ vertical: 0, horizontal: 10 }} img={selectImage} />
</div>
) : (
<CTImage className="goods-image-detail" src={selectImage} width={338} />
)}
{count > 0 && (
<div className="goods-image-btn-groups">
<div
className={cm('btn-prev', { disable: isStart })}
onClick={() => {
if (isStart) return;
setIndex((index) => index - 1);
}}
>
<CTIcon type="fanhui" size={16} color="#999" />
</div>
<div
className="goods-image-btn-container"
style={{
/* stylelint-disable value-keyword-case */
maxWidth: 56 * MAX_COUNT,
}}
>
<div
className="goods-image-btn-container-wrapper"
style={{
width: count * 56 * MAX_COUNT,
transform: `translate3d(-${index * 56}px, 0, 0)`,
}}
>
{imageList.map((item, index) => (
<CTImage
key={index}
src={item}
className={cm('goods-image-btn', {
active: item === selectImage,
})}
width={48}
onClick={() => setSelectImage(item)}
/>
))}
</div>
</div>
<div
className={cm('btn-next', { disable: isEnd })}
onClick={() => {
if (isEnd) return;
setIndex((index) => index + 1);
}}
>
<CTIcon type="xiaji" size={16} color="#999" />
</div>
</div>
)}
</div>
);
};
export default GoodsImageInfo;
index.less
.goods-image {
.goods-image-detail {
width: 240px;
height: 240px;
border: 1px solid #d9d9d9;
border-radius: 8px;
img {
object-fit: contain;
border-radius: 8px;
}
}
.goods-image-btn-groups {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
.btn-prev,
.btn-next {
flex: none;
padding: 4px;
cursor: pointer;
&.disable {
cursor: not-allowed;
}
.anticon {
vertical-align: middle;
}
}
.goods-image-btn-container {
position: relative;
flex: 1;
height: 48px;
overflow: hidden;
}
.goods-image-btn-container-wrapper {
position: absolute;
top: 0;
left: 0;
transform: translate3d(0, 0, 0);
transition: transform 0.3s;
}
.goods-image-btn {
width: 48px;
height: 48px;
margin-left: 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
&.active {
border-color: #1b9aee;
}
}
}
}
ProTable (1)
- Skip the general and enter the advanced level directly. The effect of the following picture is divided into: classification on the left, batch operation, sub-table display sku (color, specification)*
<ProTable<GoodsInfoVo>
options={false}
接口数据
dataSource={list}
展示隐藏子table组件(GoodsSkuList组件)
expandedRowRender={(record) => <GoodsSkuList goods={record} btnKeys={btnKeys}/>}
列表字段
columns={columns}
多选框
rowSelection={rowSelection}
toolbar={{
列表左侧控制分类行的展示隐藏
title: !cateVisible && (
<div className={styles.cate_title}>
商品分类
<MenuUnfoldOutlined className={styles.icon} onClick={() => store.onChangeCateVisible(true)} />
</div>
),
右侧的批量按钮
actions: btns,
}}
rowKey="goodsId"
tableRender={(_, dom) => (
单独渲染 分类组件与原table拼接 dom是原有的结构
<div style={{ display: 'flex', width: '100%' }}>
<GoodsCate />
<div style={{ flex: 1 }}>{dom}</div>
</div>
)}
搜索框的配置
search={{
labelWidth: 0,
collapsed: false,
collapseRender: false,
}}
查询触发的方法
onSubmit={(params) => {
const { pageSize } = queryParams;
loadGoodsList({ ...params, pageSize, pageNum: 1 });
}}
重置按钮触发的方法
onReset={() => {
loadGoodsList({ pageSize: 10, pageNum: 1 });
}}
分页配置
pagination={{
showQuickJumper: true,
size: 'default',
pageSize: queryParams.pageSize,
current: queryParams.pageNum,
total,
}}
点击分页触发方法
onChange={(pagination, _) => {
const { current = 1, pageSize } = pagination;
loadGoodsList({ ...queryParams, pageSize: pageSize || 10, pageNum: current });
}}
dateFormatter="string"
/>
index.hook.tsx
Set list items, search boxes and set permissions for buttons
import { message } from '@/components';
import { defineProTableColumn } from '@/utils/defineProTableColumn';
import { Button, Select, TreeSelect, Typography } from 'antd';
const { Text } = Typography;
import { history } from 'umi';
import { downUsing, putUsing, store, toSetReferPrice } from './store';
const loadColumns = (unibuyBrandList: API.TOption[], categoryList: Array<UnibuyCategoryVo>, btnKeys: string[]) =>
defineProTableColumn<GoodsInfoVo>([
{
title: '',
dataIndex: 'keyWords',
key: 'keyWords',
hideInTable: true,
fieldProps: {
placeholder: '商品名称/商品编号/商品货号',
},
},
{
title: '商品名称/规格',
dataIndex: 'goodsName',
key: 'goodsName',
width: 200,
renderText: (text, record) =>
text ? (
<a onClick={() => history.push(`/merchant/unibuy/goods/detail?goodsId=${record.goodsId}`)}>{text}</a>
) : (
'-'
),
hideInSearch: true,
},
{
title: '商品编号/商品货号',
dataIndex: 'goodsId',
key: 'goodsId',
width: 100,
hideInSearch: true,
},
{
title: '零售价(元)',
key: 'storePrice',
width: 80,
hideInSearch: true,
render: (_,record) => {
return <Text>{record.storePrice || 0}</Text>;
}
},
{
title: '成本价(元)',
key: 'skuCostPrice',
hideInSearch: true,
width: 80,
render: (_,record) => {
return <Text>{record.skuCostPrice || 0}</Text>;
},
},
{
title: '商品分类',
dataIndex: 'categoryName',
key: 'categoryName',
width: 80,
hideInSearch: true,
},
{
title: '库存',
key: 'stock',
width: 80,
hideInSearch: true,
render: (_,record) => {
return <Text>{record.stock || 0}</Text>;
},
},
{
title: '销量',
dataIndex: 'saleCount',
key: 'saleCount',
hideInSearch: true,
render: (_,record) => {
return <Text>{record.saleCount || 0}</Text>;
},
width: 80,
},
{
title: '',
hideInTable: true,
dataIndex: 'brandId',
key: 'brandId',
renderFormItem: (_, { type, defaultRender }, form) => {
return <Select options={[{ label: '全部', value: '' }].concat(unibuyBrandList)} placeholder="请选择品牌" />;
},
},
{
title: (_, type) => (type === 'table' ? '销售类型' : ''),
dataIndex: 'sellType',
key: 'sellType',
width: 80,
valueEnum: {
'': { text: '全部' },
1: { text: '香港现货' },
2: { text: '期货' },
3: { text: '欧洲现货' },
4: { text: '国内现货' },
},
fieldProps: {
placeholder: '请选择销售类型',
},
},
{
title: '',
hideInTable: true,
dataIndex: 'categoryId',
key: 'categoryId',
renderFormItem: (_, { type, defaultRender }, form) => {
return <TreeSelect options={[{ title: '全部', value: '' }].concat(categoryList)} placeholder="全部分类" />;
},
},
{
title: '',
dataIndex: 'priceChangeTime',
key: 'priceChangeTime',
valueEnum: {
ONE_DAY: { text: '最近24小时' },
TWO_DAY: { text: '最近2天' },
THREE_DAY: { text: '最近3天' },
},
fieldProps: {
placeholder: '最近改价',
},
hideInTable: true,
},
{
title: '操作',
key: 'option',
width: 150,
valueType: 'option',
render: (_, record) => {
return [
btnKeys.includes('btn_merchant_unibuy_goods_price') && (
<a key="setPrice" onClick={() => toSetReferPrice(record)}>
设价
</a>
),
record?.skuStatus === 0 && record.goodsId && btnKeys.includes('btn_merchant_unibuy_goods_on') && (
<a key="on" onClick={() => record.goodsId && putUsing({ goodsIds: [record.goodsId] })}>
上架
</a>
),
record?.skuStatus === 1 && btnKeys.includes('btn_merchant_unibuy_goods_off') && (
<a key="off" onClick={() => record.goodsId && downUsing({ goodsIds: [record.goodsId] })}>
下架
</a>
),
];
},
},
]);
/** 处理表格列 */
export function useColumns(btnKeys: string[]) {
const { unibuyBrandList, categoryList } = store.getState();
return {
columns: loadColumns(unibuyBrandList, categoryList, btnKeys),
};
}
/** 处理按钮组 */
export function useToolBtn(btnKeys: string[]) {
const { selectedRowKeys } = store.getState();
return [
btnKeys.includes('btn_merchant_unibuy_goods_batchOn') && (
<Button
key="on"
onClick={() => {
if (selectedRowKeys.length === 0) {
message.error('请选择至少一个商品');
return;
}
putUsing({ goodsIds: selectedRowKeys });
}}
>
批量上架
</Button>
),
btnKeys.includes('btn_merchant_unibuy_goods_batchOff') && (
<Button
key="off"
onClick={() => {
if (selectedRowKeys.length === 0) {
message.error('请选择至少一个商品');
return;
}
downUsing({ goodsIds: selectedRowKeys });
}}
>
批量下架
</Button>
),
];
}
Classification component
import { Tree } from 'antd';
import cn from 'classnames';
import { FC } from 'react';
import { loadGoodsList, store } from '../../store';
import styles from './index.less';
const GoodsCate: FC = () => {
const { cateVisible, categoryList, queryParams } = store.useState();
const onSelect = (selectedKeys: React.Key[], info: any) => {
loadGoodsList({ ...queryParams, pageNum: 1, categoryId: selectedKeys[0] as string });
};
return (
<div className={cn(styles.goods_cate_box, styles[`${cateVisible ? 'show' : 'hidden'}`])}>
<div className={styles.title} onClick={() => store.onChangeCateVisible(false)}>
商品分类
<MenuFoldOutlined className={styles.icon} />
</div>
<Tree selectedKeys={[queryParams.categoryId as React.Key]} onSelect={onSelect} treeData={categoryList} />
</div>
);
};
export default GoodsCate;
index.less
.goods_cate_box {
background: #fff;
&.show {
width: 158px;
margin-right: 10px;
}
&.hidden {
width: 0;
margin-right: 0;
.title {
display: none;
}
}
.title {
margin-bottom: 16px;
padding: 0 16px;
color: #262626;
font-size: 14px;
line-height: 56px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
.icon {
margin-left: 10px;
}
}
:global .ant-menu-inline {
border-right: 0;
}
}
Sub Table GoodsSkuList
import ProTable from '@ant-design/pro-table';
import { FC } from 'react';
import { useColumns } from './index.hooks';
import styles from './index.less';
interface IGoodsSkuListProps {
goods: GoodsInfoVo;
}
const GoodsSkuList: FC<IGoodsSkuListProps> = ({ goods,btnKeys }) => {
const { columns } = useColumns(goods,btnKeys);
return (
<ProTable<GoodsVo>
className={styles.sku_table}
showHeader={false}
columns={columns}
headerTitle={false}
rowKey="goodsId"
search={false}
options={false}
dataSource={goods.skusList || []}
pagination={false}
/>
);
};
export default GoodsSkuList;
index.less
.sku_table {
background: #fff;
:global .ant-card-body {
padding: 0;
}
table tbody tr:last-child {
td {
border-bottom: 0;
}
}
}
subindex.hook
Set the list structure of the layer in the table, but because there is no multi-select batch box in the inner layer, you need to set one more empty column
import { defineProTableColumn } from '@/utils/defineProTableColumn';
import { Typography } from 'antd';
const { Text } = Typography;
import { history } from 'umi';
import { downUsing, putUsing, toSetReferPrice } from '../../store';
const loadColumns = (goods: GoodsInfoVo,btnKeys:string[]) =>
defineProTableColumn<GoodsVo>([
{
key: 'index',
width: 80,
},
{
key: 'attr',
width: 200,
renderText: (text, record) => (
<a onClick={() => history.push(`/merchant/unibuy/goods/detail?goodsId=${record.goodsId}`)}>
{record?.attr?.map((item) => item.vidName).join('') || '-'}
</a>
),
},
{
title: '商品编号/商品货号',
dataIndex: 'goodsId',
key: 'goodsId',
width: 100,
},
{
title: '零售价(元)',
dataIndex: 'storePrice',
key: 'storePrice',
width: 80,
render: (_, record) => {
return <Text>{record.storePrice || 0}</Text>;
},
},
{
title: '成本价(元)',
dataIndex: 'skuCostPrice',
key: 'skuCostPrice',
width: 80,
render: (_, record) => {
return <Text>{record.skuCostPrice || 0}</Text>;
},
},
{
title: '商品分类',
dataIndex: 'categoryName',
key: 'categoryName',
width: 80,
},
{
title: '库存',
dataIndex: 'stock',
key: 'stock',
width: 80,
render: (_, record) => {
return <Text>{record.stock || 0}</Text>;
},
},
{
title: '销量',
dataIndex: 'saleCount',
key: 'saleCount',
width: 80,
render: (_, record) => {
return <Text>{record.saleCount || 0}</Text>;
},
},
{
dataIndex: 'sellType',
key: 'sellType',
width: 80,
valueEnum: {
1: { text: '香港现货' },
2: { text: '期货' },
3: { text: '欧洲现货' },
4: { text: '国内现货' },
},
fieldProps: {
placeholder: '请选择销售类型',
},
},
{
title: '操作',
key: 'option',
width: 150,
valueType: 'option',
render: (_, record) => {
return [
btnKeys.includes('btn_merchant_unibuy_goods_price')&&<a key="setPrice" onClick={() => toSetReferPrice(goods)}>
设价
</a>,
btnKeys.includes('btn_merchant_unibuy_goods_on')&&record.skuStatus === 0 && (
<a key="on" onClick={() => record?.goodsId && putUsing({ goodsIds: [record?.goodsId] })}>
上架
</a>
),
btnKeys.includes('btn_merchant_unibuy_goods_off')&&record.skuStatus === 1 && (
<a key="on" onClick={() => record?.goodsId && downUsing({ goodsIds: [record?.goodsId] })}>
下架
</a>
),
];
},
},
]);
/** 处理表格列 */
export function useColumns(goods: GoodsInfoVo,btnKeys:string[]) {
return {
columns: loadColumns(goods,btnKeys),
};
}
table, but I still want to talk about the logic of setting the price
Set price
Batch pricing and processing of status data
import ProTable from '@ant-design/pro-table';
import { Button, Drawer, Image, Space } from 'antd';
import { FC } from 'react';
import { doSetReferPrice, store } from '../../store';
import { useColumns } from './index.hook';
import styles from './index.less';
const SetPriceDrawer: FC = () => {
const { drawerVisible, goodsItem } = store.useState();
const { columns } = useColumns(goodsItem);
return (
<Drawer
onClose={() => {
store.changeDrawerVisible(false);
}}
title="商品设价"
width="78%"
visible={drawerVisible}
footer={
<Space>
<Button
key="cancel"
onClick={() => {
store.changeDrawerVisible(false);
}}
>
取消
</Button>
<Button key="sure" type="primary" onClick={() => {doSetReferPrice(),store.changeDrawerVisible(false)}}>
保存
</Button>
</Space>
}
footerStyle={{ textAlign: 'center' }}
>
<div className={styles.setPrice}>
<Image src={goodsItem.spuPic?.[0] || ''} preview={false} width={64} height={64} />
<div className={styles.detailBox}>
<div className={styles.goodDetail}>{goodsItem.goodsName}</div>
<div className={styles.category}>{goodsItem.categoryName}</div>
</div>
</div>
<ProTable<GoodsVo>
bordered
options={false}
columns={columns}
rowKey="goodsId"
dataSource={goodsItem?.skusList || []}
dateFormatter="string"
search={false}
pagination={false}
toolbar={{
menu: {
type: 'tab',
activeKey: 'w',
items: [{ key: 'w', label: '基础价格' }],
},
}}
/>
</Drawer>
);
};
export default SetPriceDrawer;
index.hook.tsx
dynamically renders the list rows of the table, according to the return value size, or color, spliced to the list column field settings
import { defineProTableColumn } from '@/utils/defineProTableColumn';
import { Input, InputNumber } from 'antd';
import { store } from '../../store';
const columnsMeta = (attrs: GoodsAttrVo[], goodsPriceObj: Record<string, number>) =>
defineProTableColumn<GoodsVo>(
// @ts-ignore
attrs
.map((item) => ({
title: item.pidName,
key: item.pid,
isEditable: false,
render: (_: any, record: any) => {
// @ts-ignore
return <>{record.attr.find((attr) => attr.pid === item.pid).vidName}</>;
},
}))
.concat([
{
title: '零售价(元)',
dataIndex: 'price',
key: 'price',
render: (_, record) => (
<InputNumber
min={0}
max={1000000000}
precision={2}
style={{ width: '150px' }}
value={goodsPriceObj[record.goodsId]}
onChange={(val) => store.onChangeGoodsPriceObj({ ...goodsPriceObj, [record.goodsId]: val })}
/>
),
},
{
title: '成本价(元)',
dataIndex: 'skuCostPrice',
key: 'skuCostPrice',
render: (_, record) => <Input style={{ width: '150px' }} value={record.skuCostPrice} disabled />,
},
]),
);
/** 处理表格列 */
export function useColumns(goodsItem: GoodsInfoVo) {
const { goodsPriceObj } = store.useState();
return { columns: columnsMeta(goodsItem.attrs || [], goodsPriceObj) };
}
store.ts
Data status processing
import { message } from '@/components';
import { reduxStore } from '@/createStore';
import {
downUsingPOST,
getUnibuyBrandUsingGET,
getUnibuyCategoryUsingGET,
getUnibuyGoodsListUsingPOST,
putUsingPOST,
setReferPriceUsingPOST,
} from '@/services/UnibuyGoodsServices';
type TGoodsPriceObj = Record<string, number>;
export const store = reduxStore.defineLeaf({
namespace: 'unibuy_goods',
initialState: {
queryParams: {
pageNum: 1,
pageSize: 10,
} as UnibuyGoodsForm,
total: 0,
list: [] as GoodsInfoVo[],
cateVisible: true,
unibuyBrandList: [] as API.TOption[],
categoryList: [] as Array<UnibuyCategoryVo>,
drawerVisible: false,
goodsItem: {} as GoodsInfoVo,
selectedRowKeys: [] as string[],
goodsPriceObj: {} as TGoodsPriceObj,
},
reducers: {
onChangeSelectedRowKeys(state, payload: string[]) {
state.selectedRowKeys = payload;
},
onChangeTotal(state, payload: number) {
state.total = payload;
},
onChangeQueryParams(state, payload: UnibuyGoodsForm) {
state.queryParams = payload;
},
onChangeList(state, payload: GoodsInfoVo[]) {
state.list = payload;
},
onChangeCateVisible(state, payload: boolean) {
state.cateVisible = payload;
},
onChangeUnibuyBrandList(state, unibuyBrandList: API.TOption[]) {
state.unibuyBrandList = unibuyBrandList;
},
onChangeCategoryList(state, payload: Array<UnibuyCategoryVo>) {
state.categoryList = payload;
},
changeDrawerVisible(state, payload: boolean) {
state.drawerVisible = payload;
},
onChangeGoodsItem(state, payload: GoodsInfoVo) {
state.goodsItem = payload;
},
onChangeGoodsPriceObj(state, payload: TGoodsPriceObj) {
state.goodsPriceObj = payload;
},
},
});
/**查询unibuy品牌列表 */
export const loadGoodsList = async (queryParams: UnibuyGoodsForm) => {
const { success, errMessage, data, total } = await getUnibuyGoodsListUsingPOST({ body: queryParams });
if (!success || !data) {
message.error(errMessage || '查询失败!');
return;
}
store.onChangeQueryParams(queryParams);
store.onChangeTotal(total || 0);
store.onChangeList(data);
};
/**unibuy品牌接口 */
export const getUnibuyBrand = async () => {
const { success, errMessage, data } = await getUnibuyBrandUsingGET();
if (!success || !data) {
message.error(errMessage || '查询失败!');
return;
}
const unibuyBrandList = data.map((item) => ({ label: item.brandName, value: item.brandId } as API.TOption));
store.onChangeUnibuyBrandList(unibuyBrandList);
};
/**unibuy目录接口 */
export const loadCategoryList = async () => {
const { success, errMessage, data } = await getUnibuyCategoryUsingGET();
if (!success || !data) {
message.error(errMessage || '查询失败!');
return;
}
store.onChangeCategoryList(data);
};
/**设置成本价 */
export const toSetReferPrice = (goodsItem: GoodsInfoVo) => {
store.onChangeGoodsItem(goodsItem);
const _goodsItem = goodsItem.skusList?.reduce((pre, cur) => {
if (cur.goodsId) {
pre[`${cur.goodsId}`] = cur.storePrice || 0;
}
return pre;
}, {} as TGoodsPriceObj);
store.onChangeGoodsPriceObj(_goodsItem || {});
store.changeDrawerVisible(true);
};
// uniBuy下架
export const downUsing = async (queryParams: UniBuyGoodsStatusForm) => {
const { success, errMessage, data } = await downUsingPOST({ body: queryParams });
if (!success || !data) {
message.error(errMessage || '查询失败!');
return;
}
message.success('下架成功');
store.onChangeSelectedRowKeys([])
loadGoodsList(store.getState().queryParams);
};
// uniBuy上架
export const putUsing = async (queryParams: UniBuyGoodsStatusForm) => {
const { success, errMessage, data } = await putUsingPOST({ body: queryParams });
if (!success || !data) {
message.error(errMessage || '查询失败!');
return;
}
message.success('上架成功');
store.onChangeSelectedRowKeys([])
loadGoodsList(store.getState().queryParams);
};
设价
export const doSetReferPrice = async () => {
const { goodsPriceObj } = store.getState();
const _list = Object.entries(goodsPriceObj).map((item) => ({
goodsId: item[0],
storePrice: item[1],
})) as UniBuySetReferPriceForm[];
const { data, success, errMessage } = await setReferPriceUsingPOST({ body: { goodsInfos: _list } });
if (!success || !data) {
message.error(errMessage || '设价失败!');
return;
}
message.success('设价成功');
loadGoodsList(store.getState().queryParams);
};
ProList and ProTable (two)
This is a structure combining proList and ProTable
The whole can be regarded as a list, and the list can be divided into the form of each list line and the text of the form
Its search, form and the text on the top of the form are all separate components
The outermost parent element index.tsx
import { PageContainer } from '@ant-design/pro-layout';
import ProList from '@ant-design/pro-list';
import { useMount } from 'ahooks';
import { Col, Row } from 'antd';
import { E_ORDER_TYPE, ORDER_TYPE } from '../constant';
import styles from '../index.less';
import OrderGoods from './components/OrderGoods';
import OrderInfoRow from './components/OrderInfoRow';
import SalesDetail from './components/SalesDetail';
import SearchForm from './components/SearchForm';
import RefundDialog from './components/RefundDialog';
import { loadRefundList, loadRefundOrderStatusList, loadStatusList, store, queryAppRefundCount } from './store';
export default () => {
store.useMount();
const { queryParams, total, list, loading, refundCount } = store.useState();
useMount(() => {
loadRefundList(queryParams);
loadRefundOrderStatusList();
loadStatusList();
// queryAppRefundCount()
});
return (
<PageContainer className={styles.orderListContainer}>
搜索组件
<SearchForm />
详情组件
<SalesDetail />
拒绝弹框
<RefundDialog />
<ProList<QueryOrderListVo>
table栏切换
toolbar={{
menu: {
type: 'tab',
activeKey: queryParams.state,
items: E_ORDER_TYPE,
onChange: (key) => {
let arr = [];
if (key === 'RETURN_APPLYING') {
arr = ['RETURN_APPLYING', 'REFUND_APPLYING'];
} else if (key === 'RETURN_AGREE') {
arr = ['RETURN_AGREE', 'CANCEL', 'REFUND_AGREE'];
} else {
arr = [key];
}
loadRefundList({ ...queryParams, states: arr, pageNum: 1 });
},
},
}}
loading={loading}
rowKey="orderId"
itemLayout="vertical"
dataSource={list}
设置这个列表的列表项
metas={{
title: {
render: (_, record, index) => {
if (index !== 0) return null;
return (
<Row>
<Col style={{ width: '20%' }}>商品</Col>
<Col style={{ width: '15%' }}>成交单价/退货数量</Col>
<Col style={{ width: '10%' }}>申请退款金额</Col>
<Col style={{ width: '10%' }}>实退金额</Col>
<Col style={{ width: '10%' }}>订单来源</Col>
<Col style={{ width: '10%' }}>售后类型</Col>
<Col style={{ width: '10%' }}>订单状态</Col>
<Col style={{ width: '15%' }}>操作</Col>
</Row>
);
},
},
每一行列表的头部描述文字
description: {
render: (_, record) => {
return <OrderInfoRow order={record} />;
},
},
每一列列表的内容 这里是一个表单组件
content: {
render: (_, record) => {
return <OrderGoods record={record} />;
},
},
}}
分页
pagination={{
showQuickJumper: true,
size: 'default',
pageSize: queryParams.pageSize,
current: queryParams.pageNum,
total,
onChange: (current, pageSize) => {
loadRefundList({ ...queryParams, pageSize: pageSize || 10, pageNum: current });
},
}}
/>
</PageContainer>
);
};
index.less
.orderListContainer {
font-size: 14px;
// :global .ant-pro-list .ant-pro-list-row-title .ant-col {
// text-align: center !important;
// }
:global .ant-pro-list {
.ant-spin-container {
overflow-x: auto;
}
.ant-list-items {
min-width: 1280px;
}
.ant-pro-table-list-toolbar-left {
gap: 0;
}
.ant-pro-list-row-title {
width: 100%;
height: 46px;
margin-right: 0;
line-height: 46px;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
&:hover {
color: rgba(0, 0, 0, 0.85);
}
.ant-col {
padding: 0 8px;
}
}
.ant-list-vertical {
.ant-pro-list-row-description {
margin-top: 0;
}
.ant-list-item-meta {
margin-bottom: 0;
}
.ant-pro-list-row {
padding: 0;
&:hover {
background-color: transparent;
transition: none;
}
.ant-pro-table .ant-card-body {
padding: 0;
}
}
}
}
}
The search component of the head SearchForm/index.tsx
import { Button, Col, DatePicker, Form, FormProps, Input, Row, Select } from 'antd';
import type { Moment } from 'moment';
import { FC } from 'react';
import { loadRefundList, store } from '../../store';
import styles from './index.less';
const Option = Select.Option;
const RangePicker = DatePicker.RangePicker;
type IFormValues = QueryOrderListForm & { startEndDate?: Moment[]; payStartEndDate?: Moment[] };
const SearchForm: FC = () => {
const [form] = Form.useForm<QueryOrderListForm>();
const { queryParams, statusList, refundStatusList } = store.useState();
const handleValues = (values: IFormValues) => {
if (values.startEndDate) {
values = {
...values,
beginTime: values.startEndDate[0].format('YYYY-MM-DD') + ' 00:00:00',
endTime: values.startEndDate[1].format('YYYY-MM-DD') + ' 23:59:59',
};
delete values['startEndDate'];
}
return values;
};
const onFinish: FormProps<QueryOrderListForm>['onFinish'] = (values) => {
loadRefundList({ ...queryParams, ...handleValues(values), pageNum: 1 });
};
const onReset = () => {
form.resetFields();
loadRefundList({ pageSize: 10, pageNum: 1, states: queryParams.states });
};
return (
<div className={styles.searchFrom}>
<Form<QueryOrderListForm> form={form} onFinish={onFinish}>
<Row gutter={20}>
<Col span={6}>
<Form.Item name="searchContent">
<Input placeholder="请输入订单编号/归属店铺/商品名称" />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="orderState" rules={[{ required: false }]}>
<Select placeholder="请选择订单状态">
<Option value={''}>全部</Option>
{statusList.map((item, index) => (
<Option value={item.code || ''} key={index}>
{item.name}
</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="refundType" rules={[{ required: false }]}>
<Select placeholder="请选择售后类型">
<Option value={''}>全部</Option>
{refundStatusList.map((item, index) => (
<Option value={item.code || ''} key={index}>
{item.name}
</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="startEndDate">
<RangePicker
placeholder={['下单:开始时间', '结束时间']}
style={{ width: '100%' }}
inputReadOnly={true}
allowClear={false}
/>
</Form.Item>
</Col>
<Col span={24} style={{ textAlign: 'right' }}>
<Button style={{ margin: '0 10px' }} onClick={onReset}>
重置
</Button>
<Button type="primary" htmlType="submit">
搜索
</Button>
</Col>
</Row>
</Form>
</div>
);
};
export default SearchForm;
Description text component of each column OrderInfoRow/index.tsx
import { FC } from 'react';
import styles from './styles.less';
const OrderInfoRow: FC<{ order: QueryOrderVo }> = ({ order }) => {
return (
<div className={styles.orderInfoRow}>
<span>退货单号:{order.qmApplyId || '-'}</span>
<span>申请时间:{order.createTime}</span>
<span>买家:{order.refundOrderVo?.buyerName}</span>
<span>联系电话:{order.refundOrderVo?.buyerPhone}</span>
</div>
);
};
export default OrderInfoRow;
The table component of each column OrderGoods/index.tsx
The width of the inner table item should be the same as the width of the outer list item
The table component here can be combined with table table items and can be regarded as a table component that must be rendered for each row of the list
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import ProTable from '@ant-design/pro-table';
import { FC,useState } from 'react';
import { useColumns } from './goods.hooks';
import styles from './styles.less';
import { useModel } from 'umi';
const OrderGoods: FC<{ record: QueryOrderListVo }> = ({ record }) => {
const { initialState } = useModel('@@initialState');
const btnKeys = initialState?.currentPageBtnKeys || [];
const { columns } = useColumns(record,btnKeys);
const [status, setStatus] = useState(false);
return (
<div className={styles.after_goods}>
<ProTable<OrderItem>
options={false}
每一列有多个商品可以收起的数据处理
dataSource={status ? record.orderItemVos : record.orderItemVos?.slice(0, 1)}
columns={columns}
rowKey="goodsId"
search={false}
dateFormatter="string"
showHeader={false}
pagination={false}
bordered
/>
每一列有多个商品可以收起
{
(record.orderItemVos?.length||1)>1&&(
<a className={styles.btn_arrow} onClick={() => setStatus(!status)}>
{status? <UpOutlined /> : <DownOutlined />}
</a>
)
}
</div>
);
};
export default OrderGoods;
of the table component of each column
import { defineProTableColumn } from '@/utils/defineProTableColumn';
import { Image, Space, Typography, Tooltip, Popconfirm } from 'antd';
import { loadAfterShopDetail, refuseUsing, store } from '../../store';
import styles from './styles.less';
const { Paragraph, Text } = Typography;
const loadColumns = (order: QueryOrderVo,keyStatus: string[],btnKeys:string[]) => {
const _length = order.orderItemVos?.length || 1;
return defineProTableColumn<OrderItem>([
{
title: '商品',
key: 'goods',
width: '20%',
hideInSearch: true,
render: (_, record) => {
return (
<div className={styles.goods}>
<Image src={record.skuPic} width={54} height={54} />
<Paragraph className={styles.name} ellipsis={{ rows: 2 }}>
<Tooltip title={record.skuName}>
<div className={styles.nameTitle}>{record.skuName}</div>
</Tooltip>
<div className={styles.norms}>规格: {record.specification || '暂无'}</div>
</Paragraph>
</div>
);
},
},
{
title: '成交单价/数量',
align: 'center',
key: 'returnedNum',
width: '15%',
render: (_, record) => {
return (
<Space direction="vertical">
<Text>¥{record.singleRefundableAmount}</Text>
{record.retailPrice && (
<Text type="secondary" delete>
¥{record.retailPrice}
</Text>
)}
{!record.retailPrice && <Text>×{record.buyNum}</Text>}
</Space>
);
},
},
{
title: '退款金额',
align: 'center',
key: 'totalPrice',
width: '10%',
render: (_, __, index) => {
合并表格行
const obj = {
children: <Text>¥{order.totalRefundPrice}</Text>,
props: { rowSpan: index === 0 ? _length : 0 },
};
return obj;
},
},
{
title: '实退金额',
align: 'center',
key: 'actualRefundPrice',
width: '10%',
render: (_, __, index) => {
const obj = {
children: order.actualRefundPrice ? (
<Text type={'warning'}>¥{order.actualRefundPrice}</Text>
) : (
<Text>无</Text>
),
props: { rowSpan: index === 0 ? _length : 0 },
};
return obj;
},
},
{
title: '订单来源',
align: 'center',
key: 'platform',
width: '10%',
render: (_, __, index) => {
const obj = {
children: <Text>{order.refundOrderVo?.orderSourceText}</Text>,
props: { rowSpan: index === 0 ? _length : 0 },
};
return obj;
},
},
{
title: '售后类型',
align: 'center',
key: 'distribution',
width: '10%',
render: (_, __, index) => {
合并表格行
const obj = {
children: <Text>{order.refundTypeText}</Text>,
props: { rowSpan: index === 0 ? _length : 0 },
};
return obj;
},
},
{
title: '订单状态',
align: 'center',
key: 'refundStatus',
width: '10%',
render: (_, __, index) => {
合并表格行
const obj = {
children: <Text>{order.refundStatusText}</Text>,
props: { rowSpan: index === 0 ? _length : 0 },
};
return obj;
},
},
{
title: '操作',
align: 'center',
key: 'option',
valueType: 'option',
width: '15%',
render: (_, __, index) => {
const obj = {
children: (
<div>
{btnKeys.includes('btn_merchant_unibuy_after_refund')&&keyStatus.includes('RETURN_APPLYING')&&<a
onClick={() => {
store.onChangeDialogVisible(true);
store.onChangeAfterItem(order);
}}
>
退款
</a>}
{btnKeys.includes('btn_merchant_unibuy_after_refuse')&&keyStatus.includes('RETURN_APPLYING')&&<Popconfirm
key="delete"
title="确定要拒绝该申请?"
placement="topRight"
onConfirm={() =>
refuseUsing({
applyId: order.qmApplyId,
})
}
okText="确定"
cancelText="取消"
>
<a>拒绝</a>
</Popconfirm>}
<a
onClick={() => {
loadAfterShopDetail(order);
}}
>
查看详情
</a>
</div>
),
props: { rowSpan: index === 0 ? _length : 0 },
};
return obj;
},
},
]);
};
/** 处理表格列 */
export function useColumns(record: QueryOrderListVo,btnKeys:string[]) {
const {queryParams} = store.useState()
return {
columns: loadColumns(record,queryParams?.states,btnKeys),
};
}
Antd components
Map map editing and echo
Synchronize to the map according to the entered address
map component CTMap
import { FC } from 'react';
import { Map, Marker } from 'react-amap';
const mapKey = 'b15fb269af7d8d61042e208f2cb3ef68';
export interface TCTMapValue {
address?: string[];
addressDetail?: string;
longitude?: number;
latitude?: number;
}
interface IMapComponentProps {
onChange?: (value: TCTMapValue) => void;
value?: TCTMapValue;
height?: string;
readonly?: boolean;
}
const CTMap: FC<IMapComponentProps> = ({ onChange, value, height = '300px', readonly = false }) => {
const selectAddress = {
created: (e: any) => {
let auto;
let geocoder;
window.AMap.plugin('AMap.Autocomplete', () => {
auto = new window.AMap.Autocomplete({ input: 'searchInput' });
});
window.AMap.plugin(['AMap.Geocoder'], function () {
geocoder = new AMap.Geocoder({
radius: 1000, //以已知坐标为中心点,radius为半径,返回范围内兴趣点和道路信息
extensions: 'all', //返回地址描述以及附近兴趣点和道路信息,默认"base"
});
});
window.AMap.plugin('AMap.PlaceSearch', () => {
let place = new window.AMap.PlaceSearch({});
window.AMap.event.addListener(auto, 'select', (e) => {
place.search(e.poi.name);
geocoder.getAddress(e.poi.location, function (status, result) {
if (status === 'complete' && result.regeocode) {
const data = result.regeocode.addressComponent;
const name = data.township + data.street + data.streetNumber;
onChange &&
onChange({
address: [data.province, data.city, data.district],
addressDetail: name,
longitude: e.poi.location.lng,
latitude: e.poi.location.lat,
});
}
});
});
});
},
click: (e) => {
let geocoder;
window.AMap.plugin(['AMap.Geocoder'], function () {
geocoder = new AMap.Geocoder({
radius: 1000, //以已知坐标为中心点,radius为半径,返回范围内兴趣点和道路信息
extensions: 'all', //返回地址描述以及附近兴趣点和道路信息,默认"base"
});
geocoder.getAddress(e.lnglat, function (status, result) {
if (status === 'complete' && result.regeocode) {
const data = result.regeocode.addressComponent;
const name = data.township + data.street + data.streetNumber;
if (readonly) return;
onChange &&
onChange({
address: [data.province, data.city, data.district],
addressDetail: name,
longitude: e.lnglat.lng,
latitude: e.lnglat.lat,
});
}
});
});
},
};
return (
<div style={{ width: '100%', height }}>
{value?.longitude ? (
<Map
amapkey={mapKey}
plugins={['ToolBar']}
events={selectAddress}
center={[value?.longitude || 0, value?.latitude || 0]}
zoom={13}
>
<Marker position={[value?.longitude || 0, value?.latitude || 0]} />
</Map>
) : (
<Map amapkey={mapKey} plugins={['ToolBar']} events={selectAddress} zoom={13}>
<Marker position={[value?.longitude || 0, value?.latitude || 0]} />
</Map>
)}
</div>
);
};
export default CTMap;
uses this component
import { CTMap } from '@/components';
import type { TCTMapValue } from '@/components/CTMap';
import ProCard from '@ant-design/pro-card';
import ProForm, { ProFormText } from '@ant-design/pro-form';
import CHINA_REGION from '@province-city-china/level';
import { usePersistFn } from 'ahooks';
import type { FormInstance } from 'antd';
import { Button, Cascader, Space } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { useModel } from 'umi';
import { getDetails, store, updateCommunity } from '../../store';
import styles from './index.less';
interface IUpdateCommunityForm extends UpdateCommunityForm {
address: string[];
}
export default () => {
const { refresh } = useModel('@@initialState');
const { dataObj, actionStatus } = store.useState();
const formRef = useRef<FormInstance>();
const [mapValues, setMapValues] = useState<TCTMapValue>({});
useEffect(() => {
if (!dataObj.communityId) return;
const { storeName, province, city, area, addressDetail, telePhone, createTime, latitude, longitude } = dataObj;
formRef?.current?.setFieldsValue({
storeName,
telePhone,
createTime,
});
onMapChange({
latitude,
longitude,
address: [province || '', city || '', area || ''],
addressDetail: addressDetail || '',
});
}, [dataObj.communityId, actionStatus]);
const onMapChange = usePersistFn((values: TCTMapValue) => {
setMapValues(values);
formRef?.current?.setFieldsValue({ address: values.address, addressDetail: values.addressDetail });
});
const onFinish = async (values: IUpdateCommunityForm) => {
const address = values.address;
updateCommunity({
...values,
province: address[0],
city: address.length === 2 ? address[0] : address[1],
area: address.length === 2 ? address[1] : address[2],
latitude: mapValues.latitude || 0,
longitude: mapValues.longitude || 0,
});
};
return (
<ProCard bordered title="小区信息" headerBordered style={{ paddingBottom: '50px' }}>
<ProForm<IUpdateCommunityForm>
className={actionStatus ? styles.readonlyForm : ''}
style={{ width: 400, padding: '0 16px' }}
formRef={formRef}
onFinish={onFinish}
submitter={{
render: () => {
return [];
},
}}
>
<ProFormText
rules={[
{
required: true,
message: '请输入小区名称',
},
{
pattern:
/^[\u4E00-\u9FA5A-Za-z0-9`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘',。、]{2,20}$/,
message: '请输入非空的2-20字小区名称',
},
]}
name="storeName"
label="小区名称"
placeholder="请输入"
readonly={actionStatus}
/>
{actionStatus ? (
<ProFormText label="小区地址" readonly name="address" className="were" />
) : (
<ProForm.Item
name="address"
label="小区地址"
rules={[
{
required: true,
message: '请选择小区地址',
},
]}
>
<Cascader
fieldNames={{ label: 'name', value: 'name' }}
options={CHINA_REGION}
placeholder="请选择省/市/区"
/>
</ProForm.Item>
)}
{actionStatus && <ProFormText name="addressDetail" label="" readonly />}
<ProFormText
hidden={actionStatus}
rules={[
{
required: true,
message: '请输入详细地址',
},
{
pattern:
/^[\u4E00-\u9FA5A-Za-z0-9`~!@#$%^&*()_\-+=<>?:"{}|,.\/;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘',。、]{2,40}$/,
message: '请输入非空的2-40字详细地址',
},
]}
name="addressDetail"
label=""
placeholder="请输入详细地址"
fieldProps={{ id: 'searchInput' }}
/>
<ProForm.Item label="地图定位">
<CTMap onChange={onMapChange} value={mapValues} readonly={actionStatus} />
</ProForm.Item>
<ProFormText
rules={[
{
pattern: /^[0-9-]*$/,
message: '请输入正确的电话号格式',
},
]}
name="telePhone"
label="物业电话"
placeholder="请输入"
readonly={actionStatus}
/>
<ProFormText name="createTime" label="创建时间" readonly />
<div className={styles.bottomBox}>
<Space>
{!actionStatus && (
<Button
onClick={() => {
store.onChangeActionStatus(true);
getDetail
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。