import React, { useState, useEffect, useMemo, useCallback, memo } from 'react'; import { bitable, CurrencyCode, FieldType, ICurrencyField, ICurrencyFieldMeta } from '@lark-base-open/js-sdk'; import { Card, Modal, Checkbox, message } from 'antd'; import './App.css'; // 客户配置类型定义 interface CustomerOptionGroup { title: string; // 客户名称 options: CustomerTagGroup[]; // 客户专属标签组 isDefault?: boolean; // 是否为默认客户 } // 客户标签组类型定义 interface CustomerTagGroup { title: string; // 标签组标题 options: string[]; // 标签选项 required: boolean; // 是否必填 level?: number; // 层级 parentOption?: string; // 父选项 condition?: (checkedList: string[]) => boolean; // 显示条件 resetOn?: string[]; // 重置条件 multiple?: boolean; // 是否支持多选 } // 客户配置 - 按客户分组管理标签 const CUSTOMER_CONFIG: CustomerOptionGroup[] = [ // PDS 客户配置 { title: 'PDS', options: [ { title: '单据类型', options: ['首单', '翻单'], required: true, level: 2 }, { title: '是否要打板', options: ['需要打板', '不需要打板'], required: false, level: 3, parentOption: '首单', condition: (checkedList) => checkedList.includes('首单'), resetOn: ['翻单'] }, { title: '批色样', options: ['要批色样', '不要批色样'], required: true, level: 4, parentOption: '不需要打板', condition: (checkedList) => checkedList.includes('首单') && checkedList.includes('不需要打板'), resetOn: ['翻单', '有变动需要修改', '无变动不需要修改'] }, { title: '翻单变动', options: ['无变动不需要修改', '有变动需要修改'], required: false, level: 3, parentOption: '翻单', condition: (checkedList) => checkedList.includes('翻单'), resetOn: ['首单'] }, { title: '特殊订单', options: ['换料寄面料样', '换料重新打板', '改尺寸重新打板','改尺寸不打版','加色'], required: false, level: 4, parentOption: '有变动需要修改', condition: (checkedList) => checkedList.includes('有变动需要修改'), resetOn: ['首单', '无变动不需要修改'] }, { title: '批色样', options: ['需要打板','要批色样','不要批色样'], required: true, level: 5, parentOption: '加色', condition: (checkedList) => checkedList.includes('加色'), resetOn: ['首单', '无变动不需要修改', '需要打板', '不需要打板'] }, { title: '品类', options: ['牛仔', '时装'], required: true }, { title: '复杂度', options: ['简单款', '基础款', '复杂款'], required: true }, { title: '二次工艺', options: ['绣花', '印花'], required: false }, { title: '是否需要批船样', options: ['不需要批船样', '需要批船样'], required: true }, { title: '运输方式', options: ['美国', '澳大利亚', '英国'], required: false, resetOn: ['PLT', 'RBE'] }, { title: '英国运输方式', options: [ '英国-海运', '英国-空运 (直飞)', '英国-空运 (转机)', '英国-铁路(中欧班列)', '英国-卡航', '英国-卡空', '英国-卡车联运', '英国-海空联运' ], required: false, level: 2, parentOption: '英国', condition: (checkedList) => checkedList.includes('英国'), resetOn: ['美国', '澳大利亚', 'PLT', 'RBE'] }, { title: '美国运输方式', options: [ '美国-海运慢船', '美国-海运快船', '美国-空运(直飞)' ], required: true, level: 2, parentOption: '美国', condition: (checkedList) => checkedList.includes('美国'), resetOn: ['英国', '澳大利亚', 'PLT', 'RBE'] }, { title: '澳大利亚运输方式', options: [ '澳大利亚-海运', '澳大利亚-空运(直飞)' ], required: true, level: 2, parentOption: '澳大利亚', condition: (checkedList) => checkedList.includes('澳大利亚'), resetOn: ['美国', '英国', 'PLT', 'RBE'] }, { title: '面料特性', options: [ '普通面料(纯棉、常规化纤)', '特殊面料(真丝、皮革、功能性面料)', '易损面料(薄纱、蕾丝)' ], required: false } ] }, // LWH 客户配置 { title: 'LWH', options: [ { title: '单据类型', options: ['首单', '翻单'], required: true, level: 2 }, { title: '打板类型(多选)', options: ['复版', 'PP版','不用打版'], required: false, level: 3, parentOption: '首单', multiple: true, condition: (checkedList) => checkedList.includes('首单'), resetOn: ['翻单'] }, { title: '翻单变动', options: ['无变动不需要修改', '有变动需要修改'], required: false, level: 3, parentOption: '翻单', condition: (checkedList) => checkedList.includes('翻单'), resetOn: ['首单'] }, { title: '特殊订单', options: ['换料寄面料样', '换料重新打板', '加色', '改尺寸重新打板','改尺寸不打版'], required: false, level: 4, parentOption: '有变动需要修改', condition: (checkedList) => checkedList.includes('有变动需要修改'), resetOn: ['首单', '无变动不需要修改'] }, { title: '特殊订单', options: ['批大货布'], required: false, level: 4, parentOption: '无变动不需要修改', condition: (checkedList) => checkedList.includes('无变动不需要修改'), resetOn: ['首单', '有变动需要修改'] }, { title: '批色样', options: ['寄成衣','寄色样'], required: true, level: 5, parentOption: '加色', condition: (checkedList) => checkedList.includes('加色'), resetOn: ['首单', '无变动不需要修改', '需要打板', '不需要打板'] }, { title: '品类', options: ['牛仔', '时装'], required: true }, { title: '复杂度', options: ['简单款', '基础款', '复杂款'], required: true }, { title: '二次工艺', options: ['绣花', '印花'], required: false }, { title: '是否需要批船样', options: ['不需要批船样', '需要批船样'], required: true }, { title: '运输方式', options: ['美国', '澳大利亚', '英国'], required: false, resetOn: ['PLT', 'RBE'] }, { title: '英国运输方式', options: [ '英国-海运', '英国-空运 (直飞)', '英国-空运 (转机)', '英国-铁路(中欧班列)', '英国-卡航', '英国-卡空', '英国-卡车联运', '英国-海空联运' ], required: false, level: 2, parentOption: '英国', condition: (checkedList) => checkedList.includes('英国'), resetOn: ['美国', '澳大利亚', 'PLT', 'RBE'] }, { title: '美国运输方式', options: [ '美国-海运慢船', '美国-海运快船', '美国-空运(直飞)' ], required: true, level: 2, parentOption: '美国', condition: (checkedList) => checkedList.includes('美国'), resetOn: ['英国', '澳大利亚', 'PLT', 'RBE'] }, { title: '澳大利亚运输方式', options: [ '澳大利亚-海运', '澳大利亚-空运(直飞)' ], required: true, level: 2, parentOption: '澳大利亚', condition: (checkedList) => checkedList.includes('澳大利亚'), resetOn: ['美国', '英国', 'PLT', 'RBE'] }, { title: '面料特性', options: [ '普通面料(纯棉、常规化纤)', '特殊面料(真丝、皮革、功能性面料)', '易损面料(薄纱、蕾丝)' ], required: false } ] }, // PLT 客户配置 { title: 'PLT', options: [ { title: '单据类型', options: ['首单', '翻单'], required: true, level: 2 }, { title: '打板类型(多选)', options: ['复版', '拍照版','开货版需打版','不用打版'], required: false, level: 3, parentOption: '首单', multiple: true, condition: (checkedList) => checkedList.includes('首单'), resetOn: ['翻单'] }, { title: '寄样方式', options: ['寄裤筒', '寄成衣'], required: false, level: 4, parentOption: '不用打版', condition: (checkedList) => checkedList.includes('首单') && checkedList.includes('不用打版'), resetOn: ['翻单'] }, { title: '翻单变动', options: ['无变动不需要修改', '有变动需要修改'], required: false, level: 3, parentOption: '翻单', condition: (checkedList) => checkedList.includes('翻单'), resetOn: ['首单'] }, { title: '特殊订单', options: ['换料寄面料样', '换料重新打板', '改尺寸重新打板','改尺寸不打版'], required: false, level: 4, parentOption: '有变动需要修改', condition: (checkedList) => checkedList.includes('有变动需要修改'), resetOn: ['首单', '无变动不需要修改'] }, { title: '打版类型', options: ['复版','拍照版'], required: false, level: 5, parentOption: '换料重新打板', condition: (checkedList) => checkedList.includes('换料重新打板'), resetOn: ['首单', '无变动不需要修改','换料重新打板'] }, { title: '打版类型', options: ['复版','拍照版'], required: false, level: 5, parentOption: '改尺寸重新打板', condition: (checkedList) => checkedList.includes('改尺寸重新打板'), resetOn: ['首单', '无变动不需要修改','换料重新打板'] }, { title: '特殊订单', options: ['批大货布'], required: false, level: 4, parentOption: '无变动不需要修改', condition: (checkedList) => checkedList.includes('无变动不需要修改'), resetOn: ['首单', '有变动需要修改'] }, { title: '品类', options: ['牛仔', '时装'], required: true }, { title: '复杂度', options: ['简单款', '基础款', '复杂款'], required: true }, { title: '二次工艺', options: ['绣花', '印花'], required: false }, { title: '是否需要批船样', options: ['不需要批船样', '需要批船样'], required: true }, { title: '运输方式', options: ['美国', '澳大利亚', '英国'], required: false, resetOn: ['RBE'] }, { title: '英国运输方式', options: [ '英国-海运', '英国-空运 (直飞)', '英国-空运 (转机)', '英国-铁路(中欧班列)', '英国-卡航', '英国-卡空', '英国-卡车联运', '英国-海空联运' ], required: false, level: 2, parentOption: '英国', condition: (checkedList) => checkedList.includes('英国'), resetOn: ['美国', '澳大利亚', 'RBE'] }, { title: '美国运输方式', options: [ '美国-海运慢船', '美国-海运快船', '美国-空运(直飞)' ], required: true, level: 2, parentOption: '美国', condition: (checkedList) => checkedList.includes('美国'), resetOn: ['英国', '澳大利亚', 'RBE'] }, { title: '澳大利亚运输方式', options: [ '澳大利亚-海运', '澳大利亚-空运(直飞)' ], required: true, level: 2, parentOption: '澳大利亚', condition: (checkedList) => checkedList.includes('澳大利亚'), resetOn: ['美国', '英国', 'RBE'] }, { title: '面料特性', options: [ '普通面料(纯棉、常规化纤)', '特殊面料(真丝、皮革、功能性面料)', '易损面料(薄纱、蕾丝)' ], required: false } ] }, // RBE 客户配置 - 简化示例 { title: 'RBE', options: [ { title: '特殊订单类型', options: ['紧急订单', '常规订单'], required: true, level: 2 }, { title: '运输优先级', options: ['标准运输', '加急运输', '特快运输'], required: true, level: 3 } ] } ]; const FABRIC_TEST_OPTIONS = ['需要面料测试', '不需要面料测试']; // 优化:预计算选项集合,避免重复includes操作 const createOptionSets = (checkedList: string[]) => { const checkedSet = new Set(checkedList); return { checkedSet, hasFirstOrder: checkedSet.has('首单'), hasReorder: checkedSet.has('翻单'), hasNoChange: checkedSet.has('无变动不需要修改'), hasChange: checkedSet.has('有变动需要修改'), hasAddColor: checkedSet.has('加色'), hasNoPlate: checkedSet.has('不需要打板') }; }; // 优化:使用memo包装OptionGroup组件 interface OptionGroupProps { group: CustomerTagGroup; checkedList: string[]; customer: string; onChange: (newList: string[]) => void; lockedOptions?: string[]; level?: number; } const OptionGroup = memo(({ group, checkedList, customer, onChange, lockedOptions = [], level = 1 }) => { // 使用配置中的 multiple 属性,默认为 false(单选) const isMulti = group.multiple || false; // 优化:使用useMemo缓存计算结果 const groupChecked = useMemo(() => checkedList.filter(v => group.options.includes(v)), [checkedList, group.options] ); const indentStyle = useMemo(() => ({ marginLeft: level > 1 ? (level - 1) * 24 : 0, marginTop: level > 1 ? 8 : 0, borderLeft: level > 1 ? `${level > 2 ? 'dashed' : 'solid'} 2px #eee` : 'none', paddingLeft: level > 1 ? 12 : 0 }), [level]); const titleColor = useMemo(() => { switch(level) { case 1: return '#000'; case 2: return '#888'; case 3: return '#b36d00'; default: return '#888'; } }, [level]); const options = useMemo(() => group.options.map(opt => ({ label: opt, value: opt, disabled: lockedOptions.includes(opt) })), [group.options, lockedOptions] ); const handleChange = useCallback((list: string[]) => { const others = checkedList.filter(v => !group.options.includes(v)); let newList; if (isMulti) { // 多选模式:保留所有选中项 newList = [...others, ...list]; } else { // 单选模式:只保留最后选中的一项 newList = [...others, list.slice(-1)[0]].filter(Boolean); } onChange(newList); }, [checkedList, group.options, isMulti, onChange]); return (
{group.title} {group.required && *}
); }); // 客户选择器组件 - 添加变更时清空选项的功能 const CustomerSelector = memo(({ customers, selectedCustomer, onChange, onCustomerChange }) => { return (
选择客户:
{customers.map(customer => ( ))}
); }); // 优化:使用memo包装OrderConfigSelector组件 interface OrderConfigSelectorProps { selectedRecordId: string | null; checkedList: string[]; setCheckedList: (list: string[]) => void; selectedCustomer: string; setSelectedCustomer: (customer: string) => void; onSubmit: (options: string[]) => void; onCancel: () => void; loading: boolean; } const OrderConfigSelector = memo(({ selectedRecordId, checkedList, setCheckedList, selectedCustomer, setSelectedCustomer, onSubmit, onCancel, loading }) => { // 查找当前选中客户的配置 const customerConfig = useMemo(() => CUSTOMER_CONFIG.find(c => c.title === selectedCustomer) || CUSTOMER_CONFIG[0], [selectedCustomer] ); // 优化:使用useMemo缓存可见的选项组 const visibleGroups = useMemo(() => { if (!customerConfig) return []; // 筛选出可见的选项组 return customerConfig.options.filter(group => { // 如果没有定义显示条件,默认为可见 if (!group.condition) { return true; } // 有显示条件的,检查条件是否满足 return group.condition(checkedList); }); }, [checkedList, customerConfig]); // 当客户变更时清空选项 const handleCustomerChange = useCallback((newCustomer: string) => { console.log(`客户变更为: ${newCustomer},清空当前选项`); setCheckedList([]); }, [setCheckedList]); // 优化:使用useCallback缓存事件处理函数 const handleCheckedListChange = useCallback((newList: string[]) => { const addedOptions = newList.filter(opt => !checkedList.includes(opt)); if (addedOptions.length > 0) { const resetGroups = customerConfig?.options.filter(group => group.resetOn?.some(resetOpt => addedOptions.includes(resetOpt)) ) || []; if (resetGroups.length > 0) { const resetGroupsSet = new Set(resetGroups); const filteredOptions = newList.filter(opt => { const group = customerConfig?.options.find(g => g.options.includes(opt)); return !(group && resetGroupsSet.has(group)); }); setCheckedList(filteredOptions); return; } } setCheckedList(newList); }, [checkedList, setCheckedList, customerConfig]); // 优化:使用useCallback缓存验证函数 const validateRequired = useCallback(() => { if (!customerConfig) { message.error('请选择客户'); return false; } // 验证可见的必填项 for (const group of visibleGroups) { if (group.required) { const has = checkedList.some(v => group.options.includes(v)); if (!has) { message.error(`请至少选择一项【${group.title}】`); return false; } } } // 特殊验证:如果选择了"不需要打板",必须选择是否批色样 if (checkedList.includes('首单') && checkedList.includes('不需要打板')) { const hasColorSample = checkedList.some(v => v === '要批色样' || v === '不要批色样'); if (!hasColorSample) { message.error('请选择是否要批色样'); return false; } } return true; }, [checkedList, customerConfig, visibleGroups]); const needFabricTestDialog = useCallback(() => { return !(checkedList.includes('翻单') && checkedList.includes('无变动不需要修改')); }, [checkedList]); const [showFabricTestModal, setShowFabricTestModal] = useState(false); const [fabricTestSelection, setFabricTestSelection] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const [finalOptions, setFinalOptions] = useState([]); const handleSubmit = useCallback(() => { if (!validateRequired()) return; setFinalOptions([...checkedList]); if (needFabricTestDialog()) { setFabricTestSelection([]); setShowFabricTestModal(true); } else { onSubmit([...checkedList]); } }, [validateRequired, checkedList, needFabricTestDialog, onSubmit]); const handleFabricTestSubmit = useCallback(() => { if (fabricTestSelection.length === 0) { message.error('请选择是否需要面料测试'); return; } setIsSubmitting(true); onSubmit([...finalOptions, ...fabricTestSelection]); setShowFabricTestModal(false); }, [fabricTestSelection, finalOptions, onSubmit]); const handleCancel = useCallback(() => { onCancel(); setCheckedList([]); }, [onCancel, setCheckedList]); const handleFabricTestChange = useCallback((values: string[]) => { if (values.length > 0) { setFabricTestSelection([values[values.length - 1]]); } else { setFabricTestSelection([]); } }, []); useEffect(() => { if (!showFabricTestModal && isSubmitting) { setIsSubmitting(false); } }, [showFabricTestModal, isSubmitting]); return ( <> {/* 只有选择了客户才显示标签组 */} {selectedCustomer && customerConfig && (
{visibleGroups.map(group => ( ))}
)}
setShowFabricTestModal(false)} okText="确定" cancelText="取消" confirmLoading={isSubmitting} >

请选择是否需要面料测试:

); }); // 优化:主应用组件 const App: React.FC = () => { const [selectedRecordId, setSelectedRecordId] = useState(null); const [checkedList, setCheckedList] = useState([]); const [loading, setLoading] = useState(false); const [currentSelection, setCurrentSelection] = useState<{ tableId: string | null; recordId: string | null; }>({ tableId: null, recordId: null }); // 新增:当前选中的客户 const [selectedCustomer, setSelectedCustomer] = useState(null); // 优化:缓存字段选项映射,避免重复查找 const [fieldOptionsMap, setFieldOptionsMap] = useState>(new Map()); // 优化:使用useCallback缓存函数 const resetOptions = useCallback(() => { setSelectedRecordId(null); setCheckedList([]); setSelectedCustomer(null); // 重置时也重置客户选择 }, []); const saveOptions = useCallback(async () => { if (!selectedRecordId || !currentSelection.tableId) { message.warning('没有有效的选中记录或表格信息'); return; } if (!selectedCustomer) { message.error('请选择客户'); return; } setLoading(true); const FIELD_ID_TO_SAVE = 'fldTtRHwlo'; try { const table = await bitable.base.getTableById(currentSelection.tableId); if (!table) { message.error('无法获取表格对象'); return; } const field = await table.getFieldById(FIELD_ID_TO_SAVE); if (!field) { message.error('无法获取字段对象'); return; } // 优化:只在字段选项映射为空时才重新获取 let optionsMap = fieldOptionsMap; if (optionsMap.size === 0) { const fieldMeta = await field.getMeta(); console.log('字段元数据:', fieldMeta); // 检查字段类型是否为多选或单选字段 let fieldOptions = []; if (fieldMeta.type === FieldType.MultiSelect || fieldMeta.type === FieldType.SingleSelect) { fieldOptions = (fieldMeta.property as any).options || []; } optionsMap = new Map(fieldOptions.map(opt => [opt.name, opt])); setFieldOptionsMap(optionsMap); } let dataToSave = null; if (checkedList.length > 0) { console.log('字段中的所有选项:', Array.from(optionsMap.keys())); console.log('要保存的选项:', checkedList); // 优化:使用Map查找,O(1)时间复杂度 dataToSave = checkedList.map(optionText => { const matchedOption = optionsMap.get(optionText); if (!matchedOption) { console.warn(`未找到选项 "${optionText}" 对应的ID`); console.log('可能的匹配选项:', Array.from(optionsMap.keys()).filter(key => key.includes(optionText) || optionText.includes(key) )); return null; } console.log(`找到匹配: "${optionText}" -> ID: ${matchedOption.id}`); return { id: matchedOption.id, text: matchedOption.name }; }).filter(Boolean); } console.log('准备保存的数据:', dataToSave); await field.setValue(selectedRecordId, dataToSave); const savedValue = await field.getValue(selectedRecordId); console.log('保存后的字段值:', savedValue); if (savedValue) { console.log('数据成功写入单元格'); message.success('已保存选项'); } else { throw new Error('数据写入后未能读取到值'); } } catch (e: any) { console.error('保存失败:', e); message.error(`保存失败: ${e.message || '未知错误'}`); } finally { setLoading(false); resetOptions(); setCurrentSelection({ tableId: null, recordId: null }); } }, [selectedRecordId, currentSelection.tableId, checkedList, fieldOptionsMap, resetOptions, selectedCustomer]); useEffect(() => { const unsubscribe = bitable.base.onSelectionChange(async (event: any) => { try { const { data } = event; if (data && data.tableId && data.recordId) { const isNewCell = ( data.tableId !== currentSelection.tableId || data.recordId !== currentSelection.recordId ); if (isNewCell) { resetOptions(); setCurrentSelection({ tableId: data.tableId, recordId: data.recordId }); // 优化:切换单元格时清空字段选项缓存 setFieldOptionsMap(new Map()); } const table = await bitable.base.getTableById(data.tableId); const cellValue = await table.getCellValue('fldTtRHwlo', data.recordId); setSelectedRecordId(data.recordId); if (typeof cellValue === 'string' && cellValue.trim() !== '') { setCheckedList(cellValue.split(',').map(item => item.trim())); } else if (Array.isArray(cellValue)) { setCheckedList(cellValue.map(item => String(item))); } else { setCheckedList([]); } // 尝试从选中的选项中推断客户 const firstOption = checkedList[0]; const customer = CUSTOMER_CONFIG.find(c => c.options.some(g => g.options.includes(firstOption))); if (customer) { setSelectedCustomer(customer.title); } } } catch (e: any) { message.error('获取选中记录失败: ' + (e.message || e)); } }); return unsubscribe; }, [currentSelection, resetOptions]); return (
请先在多维表格中点击一个单元格以加载配置。
); }; export default App;