Commit 8ab34711 authored by guang.wu's avatar guang.wu

feat: 添加 SchemaForm

parent 58886dd3
......@@ -463,12 +463,7 @@ const CustomerInfo = props => {
onOk={validateForm}
onCancel={() => closeModal(0)}
>
<Form
name="basicInfo"
initialValues={{ mealTimePeriod: [{}, {}, {}] }}
{...layout}
form={form}
>
<Form name="basicInfo" initialValues={{ mealTimePeriod: [] }} {...layout} form={form}>
{formItem.map((item, index) => {
if (item.type === 'Divider' && !item.disabled) {
return (
......
......@@ -8,6 +8,8 @@ const MealLimit = props => (
label={`${props.label}限额`}
name={props.name}
value={props.value}
labelCol={{ span: 10 }}
wrapperCol={{ span: 14 }}
rules={[
{ validator: validateRequired, message: `请输入${props.label}限额` },
{ validator: isCheckPriceTwoDecimal, message: '请输入正确的价格' },
......
import React from 'react';
import { Form, Space, TimePicker } from 'antd';
import { Form, Row, Col, TimePicker, Space } from 'antd';
import { mealSections } from '../staticData/index';
import MealCheckbox from './MealCheckbox';
......@@ -8,24 +8,32 @@ const MealSection = props => (
{fields => (
<>
{Object.keys(mealSections).map((field, i) => (
<Space key={field} align="baseline">
<Form.Item label="" name={[i, 'mealPeriodType']}>
<MealCheckbox changeType={props.onChange} meals={props.meals} field={field} />
</Form.Item>
<Form.Item
name={[i, 'time']}
rules={
props.meals[field]
? [
{ type: 'array', required: true, message: '请选择!' },
{ validator: props.validateMeals, message: '时间段不能交叉!' },
]
: []
}
>
<TimePicker.RangePicker format="HH:mm" minuteStep={30} />
</Form.Item>
</Space>
<Row key={field} align="baseline">
<Col span={4}>
<Form.Item label="" name={[i, 'mealPeriodType']}>
<MealCheckbox
changeType={e => props.onChangeSection(e, props)}
meals={props.meals}
field={field}
/>
</Form.Item>
</Col>
<Col span={18}>
<Form.Item
name={[i, 'time']}
rules={
props.meals[field]
? [
{ type: 'array', required: true, message: '请选择!' },
{ validator: props.validateMeals, message: '时间段不能交叉!' },
]
: []
}
>
<TimePicker.RangePicker format="HH:mm" minuteStep={30} />
</Form.Item>
</Col>
</Row>
))}
</>
)}
......
import React, { useEffect, useRef, useState } from 'react';
import { notification } from 'antd';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { layout } from '../../staticData/index';
import { apiEnterpriseInfo, apiNewEnterprise, apiEditEnterprise } from '../../service';
import { getBaseFormItem } from './staticData';
import { getPickSelf, checkConfirm, transformToApiParams, transformToFormData } from './bll';
// 企业客户信息
const CustomerInfo = props => {
const [meals, setMeals] = useState([]); // 选中的餐段
const [selectedMealTypes, setSelectedMealTypes] = useState([]); // 选中的餐品类型
const [pickSelfList, setPickSelfList] = useState({}); // 自提点列表
const [visible, setVisible] = useState(false);
const refForm = useRef();
/**
* 7. 表单关闭
* @param {boolean} isRefresh 是否刷新列表
*/
const closeModal = isRefresh => {
if (typeof props.onClose === 'function') {
props.onClose(!!isRefresh);
}
setSelectedMealTypes([]);
setMeals({});
refForm.current.setFieldsValue({
mealLimit: [],
});
};
const onOpenChange = v => {
if (!v) {
closeModal(!1);
}
};
/*
* 6. 表单提交
*/
const submitForm = async () => {
// 校验表单 并 转换成接口需要的数据
const formData = refForm.current.validateFields();
const data = await transformToApiParams(formData, selectedMealTypes, meals);
let api = apiNewEnterprise;
// 如果有客户ID 则为编辑
if (props.id) {
data.id = props.id;
api = apiEditEnterprise;
}
const resp = await api(data);
if (resp && resp.data) {
// 保存成功后刷新列表
closeModal(!0);
notification.success({ message: '保存成功!' });
}
};
// 改变餐品类型 (选自助餐必选外卖)
const onChangeMealType = async ms => {
try {
// 编辑时,取消餐段需提示用户确认风险
if (props.id && ms.length < selectedMealTypes.length) {
await checkConfirm();
}
// 取消外卖,必须取消自助餐
if (selectedMealTypes.includes('1') && !ms.includes('1')) {
ms = ms.filter(item => item !== '2');
}
// 选择自助餐,必须选择外卖
if (!selectedMealTypes.includes('1') && ms.includes('2')) {
ms.push('1');
}
refForm.current.setFieldsValue({
mealType: ms,
});
setSelectedMealTypes(ms);
} catch {
refForm.current.setFieldsValue({
mealType: selectedMealTypes,
});
}
};
/**
* 改变餐段
* @param {object} e 事件对象
* meals: { 1: '早餐', 2: '午餐', 3: '晚餐' }
*/
const onChangeMealSection = e => {
const { id, checked, label } = e?.target || {};
const values = Object.assign({}, meals);
// 选中则添加到选中餐段中,否则删除
if (checked) {
values[id] = label;
} else {
delete values[id];
}
// 设置选中餐段
setMeals(values);
// 餐段都没选 则设置为空数组
if (Object.keys(values).length === 0) {
refForm.current.setFieldsValue({
mealTimePeriod: null,
});
}
// 触发验证当前自段 是否显示表单提示
refForm.current.validateFields(['mealTimePeriod']);
};
// 获取表单信息
const getInfo = async () => {
const res = await apiEnterpriseInfo(props.id);
if (res && res.data) {
// 转换成表单需要的数据
const formData = transformToFormData(res.data);
setSelectedMealTypes(formData.mealType);
setMeals(formData.mealTimePeriodMap);
refForm.current.setFieldsValue(formData);
}
};
useEffect(() => {
if (props.visible) {
// 如果有客户ID 则为编辑 需获取表单信息
if (props.id) {
getInfo();
} else {
// 新增的时候才获取自提点列表
getPickSelf(setPickSelfList, setVisible);
}
setVisible(true);
} else {
setVisible(false);
}
}, [props.visible]);
const formItem = getBaseFormItem({
onChangeMealType,
onChangeMealSection,
id: props.id,
pickSelfList,
meals,
selectedMealTypes,
form: refForm.current,
});
return (
<BetaSchemaForm
layoutType="ModalForm"
title="企业客户信息"
open={visible}
width="900px"
modalProps={{
maskClosable: false,
destroyOnClose: true,
}}
formRef={refForm}
onOpenChange={onOpenChange}
layout="horizontal"
{...layout}
steps={[
{
title: '企业客户信息',
},
]}
onFinish={submitForm}
columns={formItem}
/>
);
};
export default CustomerInfo;
import React from 'react';
import { Form, Row, Col } from 'antd';
import { mealType, mealSections } from '../../staticData/index';
import MealLimit from '../MealLimit';
/**
* 渲染 企业单笔消费限额 二维表单项目
*/
const MealLimitsFormList = (meals, selectedMealTypes) => (
<Form.List name="mealLimit" key="mealLimit">
{mealLimitsFields => (
<>
{Object.keys(meals).map(meal => (
<Form.Item
key={`${mealSections[meal]}`}
label={`${mealSections[meal]}订单`}
required
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
>
<Form.List
name={`limit${meal}`}
key={`${meal}limit`}
required
wrapperCol={{ span: 21 }}
>
{mealLimitsFieldsList => (
<Row key={`row${meal}`}>
{selectedMealTypes.map((t, i) => (
<Col span={7} offset={i ? 1 : 0} key={t}>
<MealLimit value={t} label={mealType[t]} name={`${t}`} />
</Col>
))}
</Row>
)}
</Form.List>
</Form.Item>
))}
</>
)}
</Form.List>
);
export default MealLimitsFormList;
/**
* 业务逻辑处理
*/
import React from 'react';
import { Modal } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import moment from 'moment';
import { apiEnterprisePickSelf } from '../../service';
import { mealSections } from '../../staticData/index';
// 获取自提点列表
export const getPickSelf = async (setPickSelfList, setVisible) => {
const res = await apiEnterprisePickSelf({});
if (res && res.data && res.data.records) {
const data = res.data.records;
const json = {};
data.forEach(item => {
json[item.id] = item.pickselfName;
});
setPickSelfList(json);
setVisible(true);
}
};
// 风险提示
const { confirm } = Modal;
export const checkConfirm = () => {
const mt = '';
return new Promise((resolve, reject) => {
confirm({
title: '风险提示',
icon: <ExclamationCircleOutlined />,
content: `确定关闭${mt}餐品类型?此餐品类型下关联的商户及商品将一并删除,不可逆请谨慎操作!`,
onOk() {
resolve(1);
},
onCancel() {
reject(new Error());
},
});
});
};
/**
* 接口获取数据转换为表单数据 转换餐段 mealTimePeriod
* 接口来源数据: [{mealPeriodType: '1', beginTime: '08:00', endTime: '09:00'}]
* 转换后表单结构:[{mealPeriodType: '1', time: [moment('08:00'), moment('09:00')]}]
*/
const transformMealTimePeriod = mealTimePeriod => {
const mealTimePeriodMap = {};
const mealTimePeriodArr = Object.keys(mealSections).map(() => ({}));
mealTimePeriod.forEach(item => {
const { mealPeriodType, beginTime, endTime } = item;
// 数据模型转换-餐段配置,转为 {餐段:餐段名称}
// 把mealTimePeriod按mealPeriodType转为map
const mealPeriodName = mealSections[mealPeriodType];
mealTimePeriodMap[mealPeriodType] = mealPeriodName;
// 数据模型转换-餐段和时间配置, [{餐段, time}, {}, {}]
const index = Object.keys(mealSections).indexOf(`${mealPeriodType}`);
if (index > -1) {
mealTimePeriodArr[index] = {
mealPeriodType: `${mealPeriodType}`,
time: [
moment(`${moment().format('YYYY-MM-DD')} ${beginTime}`),
moment(`${moment().format('YYYY-MM-DD')} ${endTime}`),
],
};
}
});
return { mealTimePeriodMap, mealTimePeriodArr };
};
/**
* 接口获取数据转换为表单数据 消费限额 mealLimit
* 接口来源数据: [{mealPeriodType: '1', limit: [{mealType: '1', limit: 100},{mealType: '2', limit: 200}]}]
* 转换后表单结构:mealLimit: { limit1: {1: 100, 2: 200}, limit2: {1: 100, 2: 200}}
*/
const transformMealLimit = mealLimit => {
const mealLimitMap = {};
mealLimit.forEach(item => {
const { mealPeriodType, limit } = item;
mealLimitMap[`limit${mealPeriodType}`] = limit.reduce(
(acc, t) => ({
...acc,
[t.mealType]: t.limit,
}),
{},
);
});
return mealLimitMap;
// return mealLimit;
};
/**
* 3. 数据模型转换-接口获取数据转换为表单数据
*/
export const transformToFormData = data => {
const {
hideImage,
hidePrice,
id,
name,
mealLimit,
mealTimePeriod = [],
mealType: type,
weekPreview,
endOrderTime,
} = data;
const formData = {
id,
name,
weekPreview,
mealType: type?.map(item => `${item}`) ?? [],
hideInfo: [],
endOrderTime,
};
if (+hidePrice) {
formData.hideInfo.push('hidePrice');
}
if (+hideImage) {
formData.hideInfo.push('hideImage');
}
const { mealTimePeriodMap, mealTimePeriodArr } = transformMealTimePeriod(mealTimePeriod);
const mealLimitMap = transformMealLimit(mealLimit);
formData.mealTimePeriod = mealTimePeriodArr;
formData.mealLimit = mealLimitMap;
formData.mealTimePeriodMap = mealTimePeriodMap;
console.log('formData :>> ', formData);
return formData;
};
/*
* 5. 表单数据转换-表单数据转换为接口数据
*/
export const transformToApiParams = async (res, selectedMealTypes, meals) => {
console.log('res :>> ', res);
const params = {
hideImage: 0, // 隐藏图片:默认0 不隐藏 必传
hidePrice: 0, // 隐藏价格:默认0 不隐藏 必传
...res,
};
if (params.pickSelfIds) {
params.pickselfIds = params.pickSelfIds;
}
/**
* 处理餐段 mealTimePeriod
* 表单来源数据: [{mealPeriodType: '1', time: [moment('08:00'), moment('09:00')]}]
* 转换后数据结构:[{mealPeriodType: '1', beginTime: '08:00', endTime: '09:00'}]
*/
const arr = [];
res.mealTimePeriod &&
res.mealTimePeriod.forEach(item => {
if (item && meals[item.mealPeriodType]) {
const obj = { ...item };
obj.beginTime = moment(obj.time[0]).format('HH:mm');
obj.endTime = moment(obj.time[1]).format('HH:mm');
delete obj.time;
arr.push(obj);
}
});
params.mealTimePeriod = arr;
/**
* hidePrice 隐藏价格:0不隐藏;1隐藏
* hideImage 隐藏图片:0不隐藏;1隐藏
*/
if (res.hideInfo?.length) {
params.hidePrice = res.hideInfo.includes('hidePrice') ? 1 : 0;
params.hideImage = res.hideInfo.includes('hideImage') ? 1 : 0;
}
delete params.hideInfo;
/**
* 处理限额数据
* 表单来源数据 mealLimit: { limit1: {1: 100, 2: 200}, limit2: {1: 100, 2: 200}}
* mealLimit: { limit餐品类型: {餐段类型: 限额}}
* 转换后数据结构:[{mealPeriodType: '1', limit: [{mealType: '1', limit: 100}]}]
* mealPeriodType 餐段类型:(1早餐,2午餐,4晚餐)
* mealType 餐品类型:(1外卖 2 自助餐 4到店)
*/
console.log('res.mealLimit :>> ', res.mealLimit);
console.log('selectedMealTypes :>> ', selectedMealTypes);
const limits = [];
res.mealLimit &&
Object.keys(res.mealLimit).forEach(item => {
const mealPeriodType = item.replace('limit', '');
if (meals[mealPeriodType]) {
const json = {
mealPeriodType,
limit: [],
};
Object.keys(res.mealLimit[item]).forEach(t => {
if (selectedMealTypes.includes(t)) {
json.limit.push({
mealType: t,
limit: res.mealLimit[item][t],
});
}
});
limits.push(json);
}
});
params.mealLimit = limits;
console.log(params, '.....');
return params;
};
import React from 'react';
import { Input, InputNumber, Select, Checkbox, Radio } from 'antd';
import { jsonToArray } from '@/utils/utils';
import moment from 'moment';
import MealSection from '../MealSection';
import { mealType, boolOptions, hideOptions } from '../../staticData/index';
import MealFormListLimit from './MealFormListLimit';
const CheckboxGroup = Checkbox.Group;
const RadioGroup = Radio.Group;
const hideEnums = { hidePrice: '隐藏商品价格', hideImage: '隐藏商品图片' };
const weekEnums = { 1: '', 0: '' };
// 校验时间
const checkTime = (arr, current) => {
let valid = false;
arr.forEach(item => {
if (current < item.endTime) {
valid = true;
}
});
return valid;
};
// 验证餐段
const validateMeals = (form, meals) => {
const { mealTimePeriod = [] } = form.getFieldValue();
const arr = [];
let validTime = false;
mealTimePeriod.forEach(item => {
if (item && meals[item.mealPeriodType]) {
const obj = { ...item };
if (obj.time?.length === 2) {
obj.beginTime = moment(obj.time[0]).format('HH:mm');
obj.endTime = moment(obj.time[1]).format('HH:mm');
if (checkTime(arr, obj.beginTime, meals[item.mealPeriodType])) {
validTime = true;
}
arr.push(obj);
}
}
});
if (validTime) {
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject('时间段不能交叉!');
}
return Promise.resolve();
};
export const getFormItem = options => {
const { id, pickSelfList, onChangeMealType, onChangeMealSection, meals, form } = options;
return [
{
type: 'Form.Item',
label: '企业名称',
name: 'name',
wrapperCol: { span: 20 },
rules: [{ required: true, message: '请输入企业名称' }],
component: <Input placeholder="请输入企业名称" />,
},
{
type: 'Form.Item',
disabled: id,
label: '企业取餐点',
wrapperCol: { span: 20 },
name: 'pickSelfIds',
rules: [{ required: true, message: '请选择企业取餐点' }],
component: (
<Select
options={pickSelfList}
mode="multiple"
showSearch
filterOption={(v, option) =>
(option?.label ?? '').toLowerCase().includes(v.toLowerCase())
}
/>
),
},
{
type: 'Form.Item',
label: '企业截止时间',
wrapperCol: { span: 20 },
name: 'endOrderTime',
rules: [{ required: true, message: '请输入企业截止时间' }],
component: <InputNumber min={0} max={600} addonAfter="分钟" />,
extra: <span>企业员工下单的截至时间,仅支持正整数,单位为分钟。</span>,
},
{
type: 'Form.Item',
label: '餐品类型',
wrapperCol: { span: 20 },
name: 'mealType',
rules: [{ required: true, message: '请选择餐品类型' }],
component: <CheckboxGroup options={jsonToArray(mealType)} onChange={onChangeMealType} />,
},
{
type: 'Form.Item',
label: '餐段配置',
wrapperCol: { span: 12 },
name: 'mealTimePeriod',
rules: [{ required: true, message: '请选择餐段配置' }],
component: (
<MealSection
meals={meals}
validateMeals={() => validateMeals(form, meals)}
onChange={onChangeMealSection}
/>
),
},
{
name: 'Divider',
type: 'Divider',
component: '企业单笔消费限额',
},
{
name: 'Form.List',
type: 'Form.List',
component: '',
},
{
type: 'Form.Item',
label: '商品隐藏信息',
wrapperCol: { span: 20 },
name: 'hideInfo',
component: <CheckboxGroup options={hideOptions} />,
},
{
type: 'Form.Item',
label: '是否周预览',
wrapperCol: { span: 20 },
name: 'weekPreview',
rules: [{ required: true, message: '请选择是否周预览' }],
component: <RadioGroup options={boolOptions} />,
},
];
};
// 获取 schemaForm 的 columns
export const getBaseFormItem = options => {
const {
id,
pickSelfList,
selectedMealTypes,
onChangeMealType,
onChangeMealSection,
meals,
form,
} = options;
const baseColumn = [
{
title: '企业名称',
dataIndex: 'name',
formItemProps: {
rules: [{ required: true, message: '请输入企业名称' }],
},
},
{
hideInForm: !!id,
title: '企业取餐点',
dataIndex: 'pickSelfIds',
valueType: 'select',
valueEnum: pickSelfList,
fieldProps: {
mode: 'multiple',
},
formItemProps: {
rules: [{ required: true, message: '请选择企业取餐点' }],
},
},
{
title: '企业截止时间',
dataIndex: 'endOrderTime',
valueType: 'digit',
formItemProps: {
rules: [{ required: true, message: '请输入企业截止时间' }],
addonAfter: '分钟',
extra: <span>企业员工下单的截至时间,仅支持正整数,单位为分钟。</span>,
},
},
{
title: '餐品类型',
dataIndex: 'mealType',
valueType: 'checkbox',
valueEnum: mealType,
formItemProps: {
rules: [{ required: true, message: '请选择餐品类型' }],
},
fieldProps: {
onChange: onChangeMealType,
name: 'mealType',
},
},
{
title: '餐段配置',
dataIndex: 'mealTimePeriod',
formItemProps: {
rules: [{ required: true, message: '请选择餐段配置' }],
},
fieldProps: {
copyIconProps: false, // 隐藏复制行按钮
deleteIconProps: false, // 隐藏删除行按钮
creatorButtonProps: false, // 隐藏添加行按钮
},
renderFormItem: () => (
<MealSection
meals={meals}
validateMeals={() => validateMeals(form, meals)}
onChangeSection={onChangeMealSection}
/>
),
},
{
title: '',
dataIndex: '',
valueType: 'divider',
formItemProps: {
wrapperCol: { span: 22 },
},
fieldProps: {
children: '企业单笔消费限额',
orientation: 'left',
},
},
{
title: '',
dataIndex: 'mealLimit',
formItemProps: {
labelCol: { span: 0 },
wrapperCol: { span: 24 },
},
renderFormItem: () => MealFormListLimit(meals, selectedMealTypes),
},
{
title: '商品隐藏信息',
dataIndex: 'hideInfo',
valueType: 'checkbox',
valueEnum: hideEnums,
},
{
title: '是否周预览',
dataIndex: 'weekPreview',
rules: [{ required: true, message: '请选择是否周预览' }],
valueType: 'radio',
valueEnum: weekEnums,
convertValue: v => `${v}`,
},
];
return baseColumn;
};
......@@ -3,8 +3,9 @@ import ProTable from '@ant-design/pro-table';
import { Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { customerColumn } from './staticData/index';
import CustomerInfo from './components/CustomerInfoCopy';
// import CustomerInfo from './components/CustomerInfoCopy';
// import CustomerInfo from './components/CustomerInfo';
import CustomerInfo from './components/SchemaForm/CustomerBase';
import utilStyle from '@/utils/utils.less';
import { stringOrObjectTrim } from '@/utils/utils';
import { apiEnterpriseList } from './service';
......@@ -47,7 +48,6 @@ const BusinessCustomer = () => {
columns={customerColumn({ onEdit })}
request={params => query({ ...params })}
rowKey={r => r.id}
expandIconColumnIndex={10}
bordered
options={false}
toolBarRender={() => [
......
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