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: <ChannelToCustomerModal ref={modalRef} detailInfo={detailInfo} />,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<boolean>;
}interface DivisionRef {validate: () => Promise<boolean>;
}type ComponentType = 'overseas' | 'general' | 'elevator';export const ChannelToCustomerModal = forwardRef<ModalRef, HandelModalProps>((props, ref) => {const { detailInfo } = props;// console.log('详情', detailInfo);const [manageList, setManageList] = useState<ChannelManageItem[]>([]); // '事业部'数据/*** @description 定义动态 ref 容器,存储所有子组件的 ref* 键:子组件唯一标识(如 "overseas_0"、"general_1")* 值:子组件实例(包含 validate 方法)*/const componentRefs = useRef<{ [key: string]: DivisionRef | null }>({});// 生成子组件唯一标识(确保类型安全)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、收集所有子组件的校验 Promiseconst validationPromises: Promise<boolean>[] = [];// 2、遍历动态 ref 容器中的所有子组件Object.values(componentRefs.current).forEach(refInstance => {if (refInstance?.validate) {// 调用子组件的 validate 方法,捕获异常并返回 falsevalidationPromises.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 (<><div className="ltc-c7n-style"><Header detailInfo={detailInfo} />{/* 渠道事业部列表 */}<div>{manageList.map((item, index: number) => {return (<div key={index}>{/* 海外(发达/新兴) */}{['200001', 'D000001'].includes(item.belongDivision) && (<Overseasref={(el: DivisionRef | null) => {componentRefs.current[getRefKey('overseas', index)] = el;}}detailInfo={item}list={manageList}onSelect={val => handleChangeDate(val, index, '海外(发达/新兴)')}onDelete={(belongDivision: string) => handleDeleteData(belongDivision)}/>)}{/* 通用 */}{item.belongDivision === '100010' && (<Generalref={(el: DivisionRef | null) => {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) && (<Elevatorref={(el: DivisionRef | null) => {componentRefs.current[getRefKey('elevator', index)] = el;}}detailInfo={{...item,belongIndustry: item.belongDivision,}}list={manageList}onSelect={val => handleChangeDate(val, index, '电梯 && 非电梯的事业部都归类为电梯')}onDelete={(belongDivision: string) => handleDeleteData(belongDivision)}/>)}</div>);})}</div></div></>);
});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<any>(null);const [show, setShow] = useState<boolean>(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 (<><div className={styles.channelToCustomer_baseInfo}><Titletitle={<><div className={styles.channelToCustomer_baseInfo_title}>{detailInfo.belongDivisionName}{/* 事情部有多条时:支持删除,至少保留一条 */}{list.length > 1 && (<spanonClick={() => {// TODO: 删除onDelete(detailInfo.belongDivision);}}><img src={require('@/assets/imgs/delete.png')} alt="" /></span>)}</div></>}isExpanded={show}onToggle={() => setShow(!show)}/>{show && (<><Form labelLayout={LabelLayout.vertical} dataSet={generalInfoDs} columns={3}><Cascader name="organization" searchable menuMode={MenuMode.single} />{/* 客户类型:不同'事业部',根据值集里面的标记过滤 */}<Selectname="custType"searchableoptionsFilter={record => {// 1、当前事业部const division = detailInfo.belongDivision;if (!division) return true;// 2、过滤出当前事业部可选的标签return record.get('tag')?.includes(division);}}/><Select name="custCooperationStatus" searchable /><Select name="custLabel" searchable colSpan={3} maxTagCount={6} maxTagTextLength={8} /><Select name="custDuty" searchable /><Select name="saleType" searchable />{/* <TextField name="belongRegionAndArea" disabled /> */}<Cascader name="belongZone" searchable menuMode={MenuMode.single} /></Form><Titletype="subTitle"title={<>{languageConfig('channelToCustomer.title.industryInfo', '行业信息')}</>}isExpanded={show}onToggle={() => setShow(!show)}/><IndustryInforef={industryInfoRef}industryInfo={detailInfo?.custManageIndustryList}onSelect={val => {console.log('行业信息:回调', val);generalInfoDs.current?.set('custManageIndustryList', [val]);}}/></>)}</div></>);
});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<any>(null);const [show, setShow] = useState<boolean>(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 (<><div className={styles.channelToCustomer_baseInfo}><Titletitle={<><div className={styles.channelToCustomer_baseInfo_title}>{detailInfo.belongDivisionName}{/* 事情部有多条时:支持删除,至少保留一条 */}{list.length > 1 && (<spanonClick={() => {// TODO: 删除onDelete(detailInfo.belongDivision);}}><img src={require('@/assets/imgs/delete.png')} alt="" /></span>)}</div></>}isExpanded={show}onToggle={() => setShow(!show)}/>{show && (<><Form labelLayout={LabelLayout.vertical} dataSet={elevatorInfoDs} columns={3}><Cascader name="organization" searchable menuMode={MenuMode.single} />{/* 客户类型:不同'事业部',根据值集里面的标记过滤 */}<Selectname="custType"searchableoptionsFilter={record => {// 1、当前事业部const division = detailInfo.belongDivision;if (!division) return true;// 2、过滤出当前事业部可选的标签return record.get('tag')?.includes(division);}}/><Select name="custCooperationStatus" searchable /><Select name="custLabel" searchable colSpan={3} maxTagCount={6} maxTagTextLength={8} /><Select name="custDuty" searchable /><Select name="saleType" searchable /><Cascader name="belongZone" searchable menuMode={MenuMode.single} /></Form><Titletype="subTitle"title={<>{languageConfig('channelToCustomer.title.industryInfo', '行业信息')}</>}isExpanded={show}onToggle={() => setShow(!show)}/><IndustryInforef={industryInfoRef}industryInfo={detailInfo?.custManageIndustryList}onSelect={val => {console.log('行业信息:回调', val);elevatorInfoDs.current?.set('custManageIndustryList', [val]);}}/></>)}</div></>);
});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<any>(null);const [show, setShow] = useState<boolean>(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 (<><div className={styles.channelToCustomer_baseInfo}><Titletitle={<><div className={styles.channelToCustomer_baseInfo_title}>{detailInfo.belongDivisionName}{/* 事情部有多条时:支持删除,至少保留一条 */}{list.length > 1 && (<spanonClick={() => {// TODO: 删除onDelete(detailInfo.belongDivision);}}><img src={require('@/assets/imgs/delete.png')} alt="" /></span>)}</div></>}isExpanded={show}onToggle={() => setShow(!show)}/>{show && (<><Form labelLayout={LabelLayout.vertical} dataSet={overseasInfoDs} columns={3}><Cascader name="organization" searchable menuMode={MenuMode.single} />{/* 客户类型:不同'事业部',根据值集里面的标记过滤 */}<Selectname="custType"searchableoptionsFilter={record => {// 1、当前事业部const division = detailInfo.belongDivision;if (!division) return true;// 2、过滤出当前事业部可选的标签return record.get('tag')?.includes(division);}}/><Select name="custCooperationStatus" searchable /><Select name="custLabel" searchable colSpan={3} maxTagCount={6} maxTagTextLength={8} /><Select name="custDuty" searchable /><Select name="saleType" searchable /><Cascader name="belongZone" searchable menuMode={MenuMode.single} /></Form><Titletype="subTitle"title={<>{languageConfig('channelToCustomer.title.industryInfo', '行业信息')}</>}isExpanded={show}onToggle={() => setShow(!show)}/><IndustryInforef={industryInfoRef}industryInfo={detailInfo?.custManageIndustryList}onSelect={val => {console.log('海外行业信息:回调', val);overseasInfoDs.current?.set('custManageIndustryList', [val]);}}/></>)}</div></>);
});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 Tableconst tableDs = useMemo(() => new DataSet(tableList()), []);const columns: ColumnProps[] = useMemo(() => {return [{name: 'industry',editor: <Cascader searchable menuMode={MenuMode.single} />,header: <TableHead title={languageConfig('channelToCustomer.industryInfo.label.industryName', '行业')} />,},{name: 'custBelong',editor: true,header: <TableHead title={languageConfig('channelToCustomer.industryInfo.label.custBelong', '客户归属')} />,},{name: 'custContactPersonLov',editor: true,header: <TableHead title={languageConfig('channelToCustomer.industryInfo.label.custContactPersonLov', '客户接口人')} />,},{ 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 <Table dataSet={tableDs} columns={columns} selectionMode={SelectionMode.click} pagination={false} style={{ marginBottom: '16px' }} />;
});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<HeaderProps> = 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 (<div className={`ltc-c7n-style ${styles.channelToCustomer_header}`}>{/* 左侧 */}<div className={styles.channelToCustomer_header_left}><img src={bgImg} alt="" /></div>{/* 右侧:编辑按钮 */}<div className={styles.channelToCustomer_header_right}>{fields.map(item => {return (<div className={styles.channelDetails_header_left_second_item} key={item.name}>{item.label && <span>{item.label}:</span>}<span style={item.style}>{item.value}</span></div>);})}</div></div>);
};export default formatterCollections({code: [modelPrompt, commonModelPrompt],
})(ChannelToCustomerHeader);