注:本頁面模塊主要是使用?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);