详细介绍:react中暴露事件useImperativeHandle
注:本页面模块主要是使用 useImperativeHandle ,
一、概述
1、要点
hooks 中的暴露事情件方法useImperativeHandle,需要和forwardRef、ref 结合一起使用。
1、外层校验的时候会校验里面所有需要校验的验证
2、基础使用
二、demo案例
1、场景
1、弹框打开,调用详情,获取不同的'部门信息'
2、部分信息支持删除数据,至少要保留一条数据
3、弹框保存时需要进行表单校验(每个事业部下form必填、行业信息必填)
2、效果图
(1)页面
(2)校验提示
3、代码
(1)目录
(2)父级弹框
const handleChangeToCustomer = async (record: Record) => {
// console.log('karla:变更为新客户', record.toData());
const { custCode } = record.toData();
// 1、获取'客户信息'
const res = await queryBecomeCustInfo({
custCode,
});
// 2、获取'客户信息'失败
if (res.failed) {
message.error(intl.get(`${modelPrompt}.api.tips.error`).d('程序出错'), 1.5, 'top');
return;
}
// 3、组装数据
const detailInfo = res || {};
// 4、弹框打开
Modal.open({
title: intl.get(`${modelPrompt}.path.button.changeCustomer`).d('渠道-变更为客户'),
style: { width: '80vw' },
className: 'modal_custom_class',
children: ,
onOk: async () => {
// 1、检查 ref 是否存在
if (!modalRef.current) {
message.error('表单组件未加载完成,请稍后重试', 1.5, 'top');
return false;
}
// 执行表单验证
const isValid = await modalRef.current.validate();
console.log('提交校验', isValid);
if (!isValid) {
return false; // 阻止弹框关闭
}
// 2、安全获取表单数据
const formData = modalRef.current.getRecordData();
// 3、提交表单数据
const params = {
custCode,
custManageList: formData.manageList,
};
// 4、调用'保存'接口
const res = await becomeCustomer(params);
if (res.failed) {
message.error(res.message, 1.5, 'top');
return false;
}
// 5、操作成功
// message.success('操作成功', 1.5, 'top');
openDefault.open();
return true;
},
});
};
(3)modal 内容页面
main.tsx
/**
* @author wb01975
* @description 渠道变为客户
*
* @remark 此模块为重构,原逻辑代码未删除
* 1、原先逻辑是有弹框,提示用户是带入'渠道'信息,或者带入'客户'信息;新逻辑:默认带入了'渠道'信息。
* 2、增加了多条事业部信息只,支持删除,最多删除一条信息。
*/
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { message } from 'choerodon-ui/pro';
import { handleTreeReturnData } from '@/utils';
import Header from './components/header/main';
import { Elevator } from './components/elevator/main';
import { General } from './components/general/main';
import { Overseas } from './components/overseas/main';
interface ChannelManageItem {
belongDivision: string;
[key: string]: any;
}
interface DetailInfo {
custChannelManageList?: ChannelManageItem[];
[key: string]: any;
}
interface HandelModalProps {
detailInfo: DetailInfo;
}
interface ModalRef {
getRecordData: () => { manageList: ChannelManageItem[] };
setRecordData: (val: ChannelManageItem[]) => void;
validate: () => Promise;
}
interface DivisionRef {
validate: () => Promise;
}
type ComponentType = 'overseas' | 'general' | 'elevator';
export const ChannelToCustomerModal = forwardRef((props, ref) => {
const { detailInfo } = props;
// console.log('详情', detailInfo);
const [manageList, setManageList] = useState([]); // '事业部'数据
/**
* @description 定义动态 ref 容器,存储所有子组件的 ref
* 键:子组件唯一标识(如 "overseas_0"、"general_1")
* 值:子组件实例(包含 validate 方法)
*/
const componentRefs = useRef({});
// 生成子组件唯一标识(确保类型安全)
const getRefKey = (type: ComponentType, index: number): string => `${type}_${index}`;
/** 获取表单数据 */
const getRecordData = (): { manageList: ChannelManageItem[] } => {
// console.log('获取表单数据:', manageList);
return { manageList: [...manageList] }; // 返回最新数据
};
/** 设置表单数据 */
const setRecordData = (val: ChannelManageItem[]): void => {
// 组装数据,添加行业信息
const updatedList = val.map(item => {
const { belongDivision, belongIndustry, ...other } = item;
return {
belongDivision,
belongIndustry: belongDivision, // 海外事业部值有问题,把belongDivision赋值给belongIndustry(客户中使用的字段是 belongIndustry)
custManageIndustryList: [
{
belongDivision,
belongDivisionName: item?.belongDivisionName,
// 行业
industryLv1: item?.industryLv1,
industryLv2: item?.industryLv2,
industryLv3: item?.industryLv3,
industry: handleTreeReturnData([item.industryLv1, item.industryLv2, item.industryLv3]), // 行业
custBelong: item?.channelBelong || '',
isMainIndustry: 'N', // 行业:默认否', 不可编辑
custCapacityList: [], // 容量
},
],
...other,
};
});
setManageList(updatedList);
};
/** 校验逻辑 */
const validate = async () => {
try {
console.log('开始全局动态表单校验');
// 1、收集所有子组件的校验 Promise
const validationPromises: Promise[] = [];
// 2、遍历动态 ref 容器中的所有子组件
Object.values(componentRefs.current).forEach(refInstance => {
if (refInstance?.validate) {
// 调用子组件的 validate 方法,捕获异常并返回 false
validationPromises.push(
refInstance.validate().catch(error => {
console.error('子组件校验失败:', error);
return false;
}),
);
}
});
// 3、无校验项时默认通过
if (validationPromises.length === 0) {
return true;
}
// 4、执行所有校验并判断结果
const results = await Promise.all(validationPromises);
const allValid = results.every(isValid => isValid);
if (!allValid) {
message.error('请填写完整所有表单信息', 1.5, 'top');
}
return allValid;
} catch (error) {
console.error('全局校验异常:', error);
return false;
}
};
useImperativeHandle(ref, () => ({
getRecordData,
setRecordData,
validate,
}));
useEffect(() => {
if (detailInfo?.custChannelManageList) {
setRecordData(detailInfo.custChannelManageList);
}
}, [detailInfo]);
/** 回调:'事业部'数据变更 */
const handleChangeDate = (val: any, index: number, name: string) => {
console.log(name, '数据变更回调', val);
const updatedList = manageList.map(manageItem => (manageItem.belongDivision === val.belongDivision ? val : manageItem));
setManageList(updatedList);
};
/**
* @description 回调:删除
* @remark 删除后,只做逻辑删除,数据库里能查到,渠道列表页面查不到。
*/
const handleDeleteData = (belongDivision: string) => {
const updatedList = manageList.filter(item => item.belongDivision !== belongDivision);
setManageList(updatedList);
};
return (
<>
{/* 渠道事业部列表 */}
{manageList.map((item, index: number) => {
return (
{/* 海外(发达/新兴) */}
{['200001', 'D000001'].includes(item.belongDivision) && (
{
componentRefs.current[getRefKey('overseas', index)] = el;
}}
detailInfo={item}
list={manageList}
onSelect={val => handleChangeDate(val, index, '海外(发达/新兴)')}
onDelete={(belongDivision: string) => handleDeleteData(belongDivision)}
/>
)}
{/* 通用 */}
{item.belongDivision === '100010' && (
{
componentRefs.current[getRefKey('general', index)] = el;
}}
detailInfo={{
...item,
belongIndustry: item.belongDivision,
}}
list={manageList}
onSelect={val => handleChangeDate(val, index, '通用')}
onDelete={(belongDivision: string) => handleDeleteData(belongDivision)}
/>
)}
{/* 电梯 && 非电梯的事业部字段和'电梯事业部'使用相同字段 */}
{!['200001', 'D000001', '100010'].includes(item.belongDivision) && (
{
componentRefs.current[getRefKey('elevator', index)] = el;
}}
detailInfo={{
...item,
belongIndustry: item.belongDivision,
}}
list={manageList}
onSelect={val => handleChangeDate(val, index, '电梯 && 非电梯的事业部都归类为电梯')}
onDelete={(belongDivision: string) => handleDeleteData(belongDivision)}
/>
)}
);
})}
);
});
ChannelToCustomerModal.displayName = 'ChannelToCustomerModal';
store.ts
import { salesBusinessUnitUrlApi } from '@/api/mcrConfig/salesBusinessUnit';
import { handleTreeResponse } from '@/utils/utils';
import { AxiosRequestConfig } from 'axios';
import DataSet from 'choerodon-ui/dataset';
import { FieldType } from 'choerodon-ui/dataset/data-set/enum';
import { LabelLayout } from 'choerodon-ui/pro/lib/form/enum';
export const modelPrompt = 'mcr.channelToCustomer';
/**
* @description 表单通用配置
*/
export const formConfig = {
columns: 4,
labelLayout: LabelLayout.vertical,
};
/**
* 销售业务单元
*/
export const organizationOptionsDs = (divisionCode?: string) => {
return new DataSet({
autoQuery: true,
parentField: 'parentCode',
idField: 'code',
childrenField: 'dataList',
fields: [
{ name: 'code', type: FieldType.string },
{ name: 'expand', type: FieldType.boolean },
{ name: 'parentCode', type: FieldType.string },
],
transport: {
read: (config: AxiosRequestConfig): AxiosRequestConfig => {
if (!divisionCode) return {};
return {
...config,
...salesBusinessUnitUrlApi('GET', {
code: divisionCode,
}),
transformResponse: data => {
const dataList = JSON.parse(data);
let resultData: any[] = [];
if (Array.isArray(dataList)) {
const handleData = handleTreeResponse(dataList, 'dataList', {
levelField: 'organizationLevel',
disableLevels: [1, 2],
statusField: 'status',
disableStatus: 'FAILURE',
});
handleData.forEach(item => {
if (item.dataList) {
resultData = [...item.dataList];
}
});
}
return resultData;
},
};
},
},
});
};
main.less
.channelToCustomer {
&_header {
width: 100%;
display: flex;
gap: 16px;
&_left {
width: 150px;
height: 103px;
img {
width: 100%;
height: 100%;
}
}
&_right {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: center;
font-size: 14px;
color: rgb(34, 34, 34);
gap: 8px;
&_item {
flex: 1;
}
}
}
&_baseInfo {
margin: 16px 0;
&_title {
font-size: 18px;
font-weight: 700;
color: #222222;
display: flex;
gap: 8px;
align-items: center;
span {
cursor: pointer;
img {
width: 16px;
height: 16px;
}
}
}
}
}
components/general/main.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Cascader, Form, Select, TextField, useDataSet } from 'choerodon-ui/pro';
import { handleTreeReturnData } from '@/utils/utils';
import { MenuMode } from 'choerodon-ui/lib/cascader/enum';
import { LabelLayout } from 'choerodon-ui/pro/lib/form/enum';
import Title from '@/components/Title';
import { languageConfig } from '@/language/mian';
import styles from './../../main.less';
import { generalConfig } from './store';
import { IndustryInfo } from './../industryInfo/main';
interface CurrentData {
organization?: string[];
[key: string]: any;
}
export const General = forwardRef((props: any, ref) => {
const { detailInfo, list, onSelect, onDelete } = props;
console.log('通用:detailInfo', detailInfo);
const industryInfoRef = useRef(null);
const [show, setShow] = useState(true); // 管理收缩状态
const generalInfoDs = useDataSet(() => generalConfig(), []);
useImperativeHandle(ref, () => ({
validate: async () => {
// 1、表单校验
const selfValid = await generalInfoDs.current?.validate(true);
console.log('1、通用触发:generalInfoDs.current', generalInfoDs.current, '校验结果:', selfValid);
// 2、行业信息:校验
let industryValid = true;
if (industryInfoRef.current) {
industryValid = await industryInfoRef.current.validate();
console.log('2、行业信息校验结果:', industryValid);
}
// 合并校验结果
const allValid = selfValid && industryValid;
console.log('3、通用模块整体校验结果:', allValid);
return allValid;
},
}));
useEffect(() => {
if (detailInfo) {
const { organizationLv1, organizationLv2, organizationLv3, belongArea, belongRegion, belongCity, ...other } = detailInfo || {};
const params = {
organization: handleTreeReturnData([organizationLv1, organizationLv2, organizationLv3]), // 销售业务单元
belongZone: [belongArea, belongRegion, belongCity], // 所属战区
...other,
};
params.id = undefined; // 后端要求,此处需置空
generalInfoDs.loadData([params]);
}
}, [detailInfo, generalInfoDs]);
useEffect(() => {
const handleDataChange = () => {
// 1、获取当前数据
const currentData: CurrentData = generalInfoDs.toData()[0] || {};
// 2、获取'销售业务单元'
const organizationArray = Array.isArray(currentData.organization) ? currentData.organization : [];
const [organizationLv1 = '', organizationLv2 = '', organizationLv3 = ''] = organizationArray;
// 3、获取'行业'
const belongArray = Array.isArray(currentData.belongZone) ? currentData.belongZone : [];
const [belongArea = '', belongRegion = '', belongCity = ''] = belongArray;
// 4、构建参数
const params = {
...currentData,
// 销售业务单元
organizationLv1,
organizationLv2,
organizationLv3,
// 行业
belongArea,
belongRegion,
belongCity,
};
// 5、触发回调
if (params && onSelect) {
onSelect(params);
}
};
// 监听 DataSet 的更新事件
generalInfoDs.addEventListener('update', handleDataChange);
// 组件卸载时移除监听
return () => {
generalInfoDs.removeEventListener('update', handleDataChange);
};
}, [generalInfoDs, onSelect]);
return (
<>
{detailInfo.belongDivisionName}
{/* 事情部有多条时:支持删除,至少保留一条 */}
{list.length > 1 && (
{
// TODO: 删除
onDelete(detailInfo.belongDivision);
}}
>
)}
}
isExpanded={show}
onToggle={() => setShow(!show)}
/>
{show && (
<>
{/* 客户类型:不同'事业部',根据值集里面的标记过滤 */}
{
// 1、当前事业部
const division = detailInfo.belongDivision;
if (!division) return true;
// 2、过滤出当前事业部可选的标签
return record.get('tag')?.includes(division);
}}
/>
{/* */}
{languageConfig('channelToCustomer.title.industryInfo', '行业信息')}}
isExpanded={show}
onToggle={() => setShow(!show)}
/>
{
console.log('行业信息:回调', val);
generalInfoDs.current?.set('custManageIndustryList', [val]);
}}
/>
)}
);
});
General.displayName = 'ChannelToCustomerGeneral';
components/general/store.ts
import { languageConfig } from '@/language/mian';
import { renderItemMultipleText } from '@/utils/render';
import { handleAreaOptionDs } from '@/common/commonDs';
import DataSet, { DataSetProps } from 'choerodon-ui/dataset/data-set/DataSet';
import { FieldType } from 'choerodon-ui/dataset/data-set/enum';
import { organizationOptionsDs } from '../../store';
/** 销售业务单元 */
const orgOptionsCache = new Map();
const getOrganizationOptions = (belongDivision: string) => {
if (orgOptionsCache.has(belongDivision)) {
return orgOptionsCache.get(belongDivision);
}
const ds = organizationOptionsDs(belongDivision);
orgOptionsCache.set(belongDivision, ds);
return ds;
};
/** 战区 */
const areaOptionsCache = new Map();
const getAreaOptions = (belongDivision: string) => {
if (areaOptionsCache.has(belongDivision)) {
return areaOptionsCache.get(belongDivision);
}
const ds = handleAreaOptionDs(belongDivision);
areaOptionsCache.set(belongDivision, ds);
return ds;
};
export const generalConfig = (): DataSetProps => {
return {
autoCreate: true,
fields: [
{
name: 'organization',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.organization', '销售业务单元'),
placeholder: languageConfig('channelToCustomer.placeholder.organization', '请选择销售业务单元'),
help: languageConfig('channelToCustomer.label.organization.help', '代表客户经理所在的组织,影响业务流程审批走向,请谨慎选择!'),
required: true,
valueField: 'code',
textField: 'organizationName',
options: new DataSet({}),
dynamicProps: {
options: ({ record }) => {
const { belongDivision } = record.toData();
if (belongDivision) {
return getOrganizationOptions(belongDivision);
}
return new DataSet({});
},
},
},
{
name: 'custType',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custType', '客户类型'),
placeholder: languageConfig('channelToCustomer.placeholder.custType', '请选择客户类型'),
lookupCode: 'LTC_CUST_TYPE',
required: true,
},
{
name: 'custCooperationStatus',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custCooperationStatus', '新客户合作状态'),
placeholder: languageConfig('channelToCustomer.placeholder.custCooperationStatus', '请选择新客户合作状态'),
lookupCode: 'LTC_COLLABORATION_STATUS',
},
{
name: 'custLabel',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custLabel', '客户标签'),
placeholder: languageConfig('channelToCustomer.placeholder.custLabel', '请选择客户标签'),
lookupCode: 'LTC_CUSTOMER_TAGS',
multiple: ',',
},
{
name: 'custDuty',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custDuty', '客户主责方'),
placeholder: languageConfig('channelToCustomer.placeholder.custDuty', '请选择客户主责方'),
lookupCode: 'LTC_RESP_PARTY_CUST',
required: true,
},
{
name: 'saleType',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.saleType', '销售类型'),
placeholder: languageConfig('channelToCustomer.placeholder.saleType', '请选择销售类型'),
lookupCode: 'LTC_SALES_TYPE',
required: true,
},
// {
// name: 'belongRegionAndArea',
// type: FieldType.string,
// label: languageConfig('channelToCustomer.label.custDuty', '战区'),
// transformResponse: (_value, responseObject) => renderItemMultipleText(responseObject, ['belongAreaName', 'belongRegionName', 'belongCityName']),
// },
{
name: 'belongZone',
textField: 'regionName',
valueField: 'code',
label: languageConfig('channelToCustomer.label.belongZone', '所属战区'),
placeholder: languageConfig('channelToCustomer.label.placeholder.belongZone', '请选择所属战区'),
options: new DataSet({}),
dynamicProps: {
options: ({ record }) => {
const belongDivision = record.get('belongDivision');
if (belongDivision) {
if (['-', undefined].includes(belongDivision)) return new DataSet({});
return getAreaOptions(belongDivision);
}
return new DataSet({});
},
},
required: true,
},
],
};
};
components/elevator/main.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Cascader, Form, Select, TextField, useDataSet } from 'choerodon-ui/pro';
import { handleTreeReturnData } from '@/utils/utils';
import { MenuMode } from 'choerodon-ui/lib/cascader/enum';
import { LabelLayout } from 'choerodon-ui/pro/lib/form/enum';
import { languageConfig } from '@/language/mian';
import Title from '@/components/Title';
import styles from './../../main.less';
import { elevatorConfig } from './store';
import { IndustryInfo } from './../industryInfo/main';
interface CurrentData {
organization?: string[];
[key: string]: any;
}
export const Elevator = forwardRef((props: any, ref: any) => {
const { detailInfo, list, onSelect, onDelete } = props;
// console.log('电梯:detailInfo', detailInfo);
const industryInfoRef = useRef(null);
const [show, setShow] = useState(true); // 管理收缩状态
const elevatorInfoDs = useDataSet(() => elevatorConfig(), []);
useImperativeHandle(ref, () => ({
validate: async () => {
// 1、表单校验
const selfValid = await elevatorInfoDs.current?.validate(true);
console.log('1、电梯触发:generalInfoDs.current', elevatorInfoDs.current, '校验结果:', selfValid);
// 2、行业信息:校验
let industryValid = true;
if (industryInfoRef.current) {
industryValid = await industryInfoRef.current.validate();
console.log('2、行业信息校验结果:', industryValid);
}
// 合并校验结果
const allValid = selfValid && industryValid;
console.log('3、电梯模块整体校验结果:', allValid);
return allValid;
},
}));
useEffect(() => {
if (detailInfo) {
const { organizationLv1, organizationLv2, organizationLv3, belongArea, belongRegion, belongCity, ...other } = detailInfo || {};
const params = {
organization: handleTreeReturnData([organizationLv1, organizationLv2, organizationLv3]), // 销售业务单元
belongZone: [belongArea, belongRegion, belongCity], // 所属战区
...other,
};
params.id = undefined;
elevatorInfoDs.loadData([params]);
}
}, [detailInfo, elevatorInfoDs]);
useEffect(() => {
const handleDataChange = () => {
// 1、获取当前数据
const currentData: CurrentData = elevatorInfoDs.toData()[0] || {};
// 2、获取'销售业务单元'
const organizationArray = Array.isArray(currentData.organization) ? currentData.organization : [];
const [organizationLv1 = '', organizationLv2 = '', organizationLv3 = ''] = organizationArray;
// 3、获取'行业'
const belongArray = Array.isArray(currentData.belongZone) ? currentData.belongZone : [];
const [belongArea = '', belongRegion = '', belongCity = ''] = belongArray;
// 4、构建参数
const params = {
...currentData,
// 销售业务单元
organizationLv1,
organizationLv2,
organizationLv3,
// 行业
belongArea,
belongRegion,
belongCity,
};
// 5、触发回调
if (params && onSelect) {
onSelect(params);
}
};
// 监听 DataSet 的更新事件
elevatorInfoDs.addEventListener('update', handleDataChange);
// 组件卸载时移除监听
return () => {
elevatorInfoDs.removeEventListener('update', handleDataChange);
};
}, [elevatorInfoDs, onSelect]);
return (
<>
{detailInfo.belongDivisionName}
{/* 事情部有多条时:支持删除,至少保留一条 */}
{list.length > 1 && (
{
// TODO: 删除
onDelete(detailInfo.belongDivision);
}}
>
)}
}
isExpanded={show}
onToggle={() => setShow(!show)}
/>
{show && (
<>
{/* 客户类型:不同'事业部',根据值集里面的标记过滤 */}
{
// 1、当前事业部
const division = detailInfo.belongDivision;
if (!division) return true;
// 2、过滤出当前事业部可选的标签
return record.get('tag')?.includes(division);
}}
/>
{languageConfig('channelToCustomer.title.industryInfo', '行业信息')}}
isExpanded={show}
onToggle={() => setShow(!show)}
/>
{
console.log('行业信息:回调', val);
elevatorInfoDs.current?.set('custManageIndustryList', [val]);
}}
/>
)}
);
});
Elevator.displayName = 'ChannelToCustomerElevator';
components/elevator/store.ts
import { languageConfig } from '@/language/mian';
import { renderItemMultipleText } from '@/utils/render';
import DataSet, { DataSetProps } from 'choerodon-ui/dataset/data-set/DataSet';
import { FieldType } from 'choerodon-ui/dataset/data-set/enum';
import { organizationOptionsDs } from '../../store';
import { handleAreaOptionDs } from '@/common/commonDs';
/** 销售业务单元 */
const orgOptionsCache = new Map();
const getOrganizationOptions = (belongDivision: string) => {
if (orgOptionsCache.has(belongDivision)) {
return orgOptionsCache.get(belongDivision);
}
const ds = organizationOptionsDs(belongDivision);
orgOptionsCache.set(belongDivision, ds);
return ds;
};
/** 战区 */
const areaOptionsCache = new Map();
const getAreaOptions = (belongDivision: string) => {
if (areaOptionsCache.has(belongDivision)) {
return areaOptionsCache.get(belongDivision);
}
const ds = handleAreaOptionDs(belongDivision);
areaOptionsCache.set(belongDivision, ds);
return ds;
};
export const elevatorConfig = (): DataSetProps => {
return {
autoCreate: true,
fields: [
{
name: 'organization',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.organization', '销售业务单元'),
placeholder: languageConfig('channelToCustomer.placeholder.organization', '请选择销售业务单元'),
help: languageConfig('channelToCustomer.label.organization.help', '代表客户经理所在的组织,影响业务流程审批走向,请谨慎选择!'),
required: true,
valueField: 'code',
textField: 'organizationName',
options: new DataSet({}),
dynamicProps: {
options: ({ record }) => {
const { belongDivision } = record.toData();
if (belongDivision) {
return getOrganizationOptions(belongDivision);
}
return new DataSet({});
},
},
},
{
name: 'custType',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custType', '客户类型'),
placeholder: languageConfig('channelToCustomer.placeholder.custType', '请选择客户类型'),
lookupCode: 'LTC_CUST_TYPE',
required: true,
},
{
name: 'custCooperationStatus',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custCooperationStatus', '新客户合作状态'),
placeholder: languageConfig('channelToCustomer.placeholder.custCooperationStatus', '请选择新客户合作状态'),
lookupCode: 'LTC_COLLABORATION_STATUS',
},
{
name: 'custLabel',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custLabel', '客户标签'),
placeholder: languageConfig('channelToCustomer.placeholder.custLabel', '请选择客户标签'),
lookupCode: 'LTC_CUSTOMER_TAGS',
// multiple: true,
multiple: ',',
},
{
name: 'custDuty',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custDuty', '客户主责方'),
placeholder: languageConfig('channelToCustomer.placeholder.custDuty', '请选择客户主责方'),
lookupCode: 'LTC_RESP_PARTY_CUST',
required: true,
},
{
name: 'saleType',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.saleType', '销售类型'),
placeholder: languageConfig('channelToCustomer.placeholder.saleType', '请选择销售类型'),
lookupCode: 'LTC_SALES_TYPE',
required: true,
},
// {
// name: 'belongRegionAndArea',
// type: FieldType.string,
// label: languageConfig('channelToCustomer.label.custDuty', '战区'),
// transformResponse: (_value, responseObject) => renderItemMultipleText(responseObject, ['belongAreaName', 'belongRegionName', 'belongCityName']),
// },
{
name: 'belongZone',
textField: 'regionName',
valueField: 'code',
label: languageConfig('channelToCustomer.label.belongZone', '所属战区'),
placeholder: languageConfig('channelToCustomer.label.placeholder.belongZone', '请选择所属战区'),
options: new DataSet({}),
dynamicProps: {
options: ({ record }) => {
const belongDivision = record.get('belongDivision');
if (belongDivision) {
if (['-', undefined].includes(belongDivision)) return new DataSet({});
return getAreaOptions(belongDivision);
}
return new DataSet({});
},
},
required: true,
},
],
};
};
components/overseas/main.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { Cascader, Form, Select, TextField, useDataSet } from 'choerodon-ui/pro';
import { MenuMode } from 'choerodon-ui/lib/cascader/enum';
import { languageConfig } from '@/language/mian';
import { LabelLayout } from 'choerodon-ui/pro/lib/form/enum';
import { handleTreeReturnData } from '@/utils/utils';
import Title from '@/components/Title';
import styles from './../../main.less';
import { overseasConfig } from './store';
import { IndustryInfo } from './../industryInfo/main';
interface CurrentData {
organization?: string[];
[key: string]: any;
}
export const Overseas = forwardRef((props: any, ref: any) => {
const { detailInfo, list, onSelect, onDelete } = props;
console.log('海外:detailInfo', detailInfo);
const industryInfoRef = useRef(null);
const [show, setShow] = useState(true); // 管理收缩状态
const overseasInfoDs = useDataSet(() => overseasConfig(), []);
useImperativeHandle(ref, () => ({
validate: async () => {
// 1、表单校验
const selfValid = await overseasInfoDs.current?.validate(true);
console.log('1、海外触发:generalInfoDs.current', overseasInfoDs.current, '校验结果:', selfValid);
// 2、行业信息:校验
let industryValid = true;
if (industryInfoRef.current) {
industryValid = await industryInfoRef.current.validate();
console.log('2、行业信息校验结果:', industryValid);
}
// 合并校验结果
const allValid = selfValid && industryValid;
console.log('3、海外模块整体校验结果:', allValid);
return allValid;
},
}));
useEffect(() => {
if (detailInfo) {
const { organizationLv1, organizationLv2, organizationLv3, belongArea, belongRegion, belongCity, ...other } = detailInfo || {};
const params = {
organization: handleTreeReturnData([organizationLv1, organizationLv2, organizationLv3]), // 销售业务单元
belongZone: [belongArea, belongRegion, belongCity], // 所属战区
...other,
};
params.id = undefined;
overseasInfoDs.loadData([params]);
}
}, [detailInfo, overseasInfoDs]);
useEffect(() => {
const handleDataChange = () => {
// 1、获取当前数据
const currentData: CurrentData = overseasInfoDs.toData()[0] || {};
// 2、获取'销售业务单元'
const organizationArray = Array.isArray(currentData.organization) ? currentData.organization : [];
const [organizationLv1 = '', organizationLv2 = '', organizationLv3 = ''] = organizationArray;
// 3、获取'行业'
const belongArray = Array.isArray(currentData.belongZone) ? currentData.belongZone : [];
const [belongArea = '', belongRegion = '', belongCity = ''] = belongArray;
// 4、构建参数
const params = {
...currentData,
// 销售业务单元
organizationLv1,
organizationLv2,
organizationLv3,
// 行业
belongArea,
belongRegion,
belongCity,
};
// 5、触发回调
if (params && onSelect) {
onSelect(params);
}
};
// 监听 DataSet 的更新事件
overseasInfoDs.addEventListener('update', handleDataChange);
// 组件卸载时移除监听
return () => {
overseasInfoDs.removeEventListener('update', handleDataChange);
};
}, [overseasInfoDs, onSelect]);
return (
<>
{detailInfo.belongDivisionName}
{/* 事情部有多条时:支持删除,至少保留一条 */}
{list.length > 1 && (
{
// TODO: 删除
onDelete(detailInfo.belongDivision);
}}
>
)}
}
isExpanded={show}
onToggle={() => setShow(!show)}
/>
{show && (
<>
{/* 客户类型:不同'事业部',根据值集里面的标记过滤 */}
{
// 1、当前事业部
const division = detailInfo.belongDivision;
if (!division) return true;
// 2、过滤出当前事业部可选的标签
return record.get('tag')?.includes(division);
}}
/>
{languageConfig('channelToCustomer.title.industryInfo', '行业信息')}}
isExpanded={show}
onToggle={() => setShow(!show)}
/>
{
console.log('海外行业信息:回调', val);
overseasInfoDs.current?.set('custManageIndustryList', [val]);
}}
/>
)}
);
});
Overseas.displayName = 'ChannelToCustomerOverseas';
components/overseas/store.ts
import { languageConfig } from '@/language/mian';
import { checkOrganizationCust } from '@/utils/constants';
import { renderItemMultipleText } from '@/utils/render';
import DataSet, { DataSetProps } from 'choerodon-ui/dataset/data-set/DataSet';
import { FieldType } from 'choerodon-ui/dataset/data-set/enum';
import { organizationOptionsDs } from '../../store';
import { handleAreaOptionDs } from '@/common/commonDs';
/** 销售业务单元 */
const orgOptionsCache = new Map();
const getOrganizationOptions = (belongDivision: string) => {
if (orgOptionsCache.has(belongDivision)) {
return orgOptionsCache.get(belongDivision);
}
const ds = organizationOptionsDs(belongDivision);
orgOptionsCache.set(belongDivision, ds);
return ds;
};
/** 战区 */
const areaOptionsCache = new Map();
const getAreaOptions = (belongDivision: string) => {
if (areaOptionsCache.has(belongDivision)) {
return areaOptionsCache.get(belongDivision);
}
const ds = handleAreaOptionDs(belongDivision);
areaOptionsCache.set(belongDivision, ds);
return ds;
};
export const overseasConfig = (): DataSetProps => {
return {
autoCreate: true,
fields: [
{
name: 'organization',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.organization', '销售业务单元'),
placeholder: languageConfig('channelToCustomer.placeholder.organization', '请选择销售业务单元'),
help: languageConfig('channelToCustomer.label.organization.help', '代表客户经理所在的组织,影响业务流程审批走向,请谨慎选择!'),
required: true,
valueField: 'code',
textField: 'organizationName',
options: new DataSet({}),
dynamicProps: {
options: ({ record }) => {
const { belongDivision } = record.toData();
if (belongDivision) {
return getOrganizationOptions(belongDivision);
}
return new DataSet({});
},
},
// dynamicProps: {
// required: ({ record }) => {
// return checkOrganizationCust.includes(record.get('belongDivision'));
// },
// },
},
{
name: 'custType',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custType', '客户类型'),
placeholder: languageConfig('channelToCustomer.placeholder.custType', '请选择客户类型'),
lookupCode: 'LTC_CUST_TYPE',
required: true,
},
{
name: 'custCooperationStatus',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custCooperationStatus', '新客户合作状态'),
placeholder: languageConfig('channelToCustomer.placeholder.custCooperationStatus', '请选择新客户合作状态'),
lookupCode: 'LTC_COLLABORATION_STATUS',
},
{
name: 'custLabel',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custLabel', '客户标签'),
placeholder: languageConfig('channelToCustomer.placeholder.custLabel', '请选择客户标签'),
lookupCode: 'LTC_CUSTOMER_TAGS',
// multiple: true,
multiple: ',',
},
{
name: 'custDuty',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.custDuty', '客户主责方'),
placeholder: languageConfig('channelToCustomer.placeholder.custDuty', '请选择客户主责方'),
lookupCode: 'LTC_RESP_PARTY_CUST',
required: true,
},
{
name: 'saleType',
type: FieldType.string,
label: languageConfig('channelToCustomer.label.saleType', '销售类型'),
placeholder: languageConfig('channelToCustomer.placeholder.saleType', '请选择销售类型'),
lookupCode: 'LTC_SALES_TYPE',
required: true,
},
// {
// name: 'belongRegionAndArea',
// type: FieldType.string,
// label: languageConfig('channelToCustomer.label.custDuty', '战区'),
// transformResponse: (_value, responseObject) => renderItemMultipleText(responseObject, ['belongAreaName', 'belongRegionName', 'belongCityName']),
// },
{
name: 'belongZone',
textField: 'regionName',
valueField: 'code',
label: languageConfig('channelToCustomer.label.belongZone', '所属战区'),
placeholder: languageConfig('channelToCustomer.label.placeholder.belongZone', '请选择所属战区'),
options: new DataSet({}),
dynamicProps: {
options: ({ record }) => {
const belongDivision = record.get('belongDivision');
if (belongDivision) {
if (['-', undefined].includes(belongDivision)) return new DataSet({});
return getAreaOptions(belongDivision);
}
return new DataSet({});
},
},
required: true,
},
],
};
};
components/industryInfo/main.tsx
import React, { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react';
import { Cascader, Table } from 'choerodon-ui/pro';
import DataSet from 'choerodon-ui/dataset';
import { SelectionMode } from 'choerodon-ui/pro/lib/table/enum';
import { ColumnProps } from 'choerodon-ui/pro/lib/table/Column';
import { MenuMode } from 'choerodon-ui/lib/cascader/enum';
import TableHead from '@/components/TableHead';
import { languageConfig } from '@/language/mian';
import { tableList } from './store';
export const IndustryInfo = forwardRef((props: any, ref) => {
const { industryInfo, onSelect } = props;
// console.log('行业信息:industryInfo', props.industryInfo);
// ds Table
const tableDs = useMemo(() => new DataSet(tableList()), []);
const columns: ColumnProps[] = useMemo(() => {
return [
{
name: 'industry',
editor: ,
header: ,
},
{
name: 'custBelong',
editor: true,
header: ,
},
{
name: 'custContactPersonLov',
editor: true,
header: ,
},
{ name: 'isMainIndustry' },
];
}, []);
useImperativeHandle(ref, () => ({
validate: async () => {
const isValid = await tableDs.current?.validate(true);
return isValid;
},
}));
useEffect(() => {
const handler = async () => {
if (!onSelect || !tableDs.current) return;
const currentData = tableDs.current.toData();
const industryArray = Array.isArray(currentData.industry) ? currentData.industry : [];
const [industryLv1 = '', industryLv2 = '', industryLv3 = ''] = industryArray;
const params = {
...currentData,
industryLv1,
industryLv2,
industryLv3,
};
onSelect(params);
};
tableDs.addEventListener('update', handler);
return () => {
tableDs.removeEventListener('update', handler);
};
}, [tableDs, onSelect]);
useEffect(() => {
if (industryInfo) {
tableDs.loadData(industryInfo);
}
}, [industryInfo, tableDs]);
return ;
});
IndustryInfo.displayName = 'IndustryInfo';
components/industryInfo/store.ts
import { handleIndustryOptionDs } from '@/common/commonDs';
import { languageConfig } from '@/language/mian';
import DataSet from 'choerodon-ui/dataset';
import { FieldIgnore, FieldType } from 'choerodon-ui/dataset/data-set/enum';
/** 行业 */
const industryOptionsCache = new Map();
const getIndustryOptions = (belongDivision: string) => {
if (industryOptionsCache.has(belongDivision)) {
return industryOptionsCache.get(belongDivision);
}
const ds = handleIndustryOptionDs(belongDivision);
industryOptionsCache.set(belongDivision, ds);
return ds;
};
/** ds table */
export const tableList = () => {
return {
autoQuery: true,
fields: [
// {
// name: 'industryName',
// type: FieldType.string,labub
// label: languageConfig('channelToCustomer.industryInfo.label.industryName', '行业'),
// required: true,
// },
{
name: 'industry',
type: FieldType.string,
valueField: 'code',
textField: 'industryName',
label: languageConfig('manageInfo.industry', '所属行业'),
placeholder: languageConfig('manageInfo.industry.placeholder', '请选择所属行业'),
// dynamicProps: {
// options: ({ record }) => {
// if (record.get('belongDivision')) {
// if (['-', undefined].includes(record.get('belongDivision'))) return new DataSet({});
// return handleIndustryOptionDs(record.get('belongDivision'));
// }
// return new DataSet({});
// },
// },
dynamicProps: {
options: ({ record }) => {
const { belongDivision } = record.toData();
if (belongDivision) {
return getIndustryOptions(belongDivision);
}
return new DataSet({});
},
},
required: true,
},
{
name: 'custBelong',
type: FieldType.string,
label: languageConfig('channelToCustomer.industryInfo.label.custBelong', '客户归属'),
lookupCode: 'LTC_CUSTOMER_OWNERSHIP',
placeholder: languageConfig('channelToCustomer.industryInfo.placeholder.custBelong', '请选择客户归属'),
required: true,
},
{
name: 'custContactPersonLov',
type: FieldType.object,
ignore: FieldIgnore.always,
label: languageConfig('channelToCustomer.industryInfo.label.custContactPersonLov', '客户接口人'),
placeholder: languageConfig('channelToCustomer.industryInfo.placeholder.custContactPersonLov', '请选择客户接口人'),
lovCode: 'LTC.HPFM.EMPLOYEE',
required: true,
},
{
name: 'custContactPerson',
type: FieldType.string,
bind: 'custContactPersonLov.userInfo',
},
{
name: 'isMainIndustry',
type: FieldType.string,
label: languageConfig('channelToCustomer.industryInfo.label.isMainIndustry', '是否主行业'),
placeholder: languageConfig('channelToCustomer.industryInfo.placeholder.isMainIndustry', '请选择是否主行业'),
lookupCode: 'LTC_YES_OR_NO',
defaultValue: 'N',
},
//
// {
// name: 'industryLv1',
// type: FieldType.string,
// label: languageConfig('channelToCustomer.industryInfo.label.industryLv1', '一级行业'),
// },
// {
// name: 'industryLv2',
// type: FieldType.string,
// label: languageConfig('channelToCustomer.industryInfo.label.industryLv2', '二级行业'),
// },
// {
// name: 'industryLv3',
// type: FieldType.string,
// label: languageConfig('channelToCustomer.industryInfo.label.industryLv3', '三级行业'),
// },
],
};
};
components/header/main.tsx
import React from 'react';
import bgImg from '@/assets/highSeas/bg.png';
import { languageConfig } from '@/language/mian';
import formatterCollections from 'utils/intl/formatterCollections';
import { commonModelPrompt } from '@/common/language';
import { modelPrompt } from '../../store';
import styles from './../../main.less';
interface HeaderProps {
detailInfo: any;
}
export const ChannelToCustomerHeader: React.FC = props => {
const { detailInfo } = props;
const fields = [
{
name: 'custName',
style: { fontSize: '20px', fontWeight: '700' },
},
{
name: 'custCode',
label: languageConfig('channelToCusotmer.label.custCode', '渠道编号'),
style: { fontSize: '12px' },
},
].map(field => ({
...field,
value: detailInfo?.[field.name] ?? '-',
}));
return (
{/* 左侧 */}
{/* 右侧:编辑按钮 */}
{fields.map(item => {
return (
{item.label && {item.label}:}
{item.value}
);
})}
);
};
export default formatterCollections({
code: [modelPrompt, commonModelPrompt],
})(ChannelToCustomerHeader);