diff --git a/src/App.tsx b/src/App.tsx index 3537baa..fd84de5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,728 +1,1305 @@ -import React, { useEffect, useState } from 'react'; -import { bitable, ITable, IRecord, FieldType, ISingleSelectField, IMultiSelectField } from '@lark-base-open/js-sdk'; -import { Card, Table, Typography, Button, Spin, Empty, Toast, Modal, Select, Dropdown } from '@douyinfe/semi-ui'; -import { IconPlus } from '@douyinfe/semi-icons'; +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'; -// 常量定义 -const CONSTANTS = { - POS_TABLE_NAME: 'pos_订单基础信息明细', - PROCESS_TABLE_NAME: '订单流程表', - POS_RECORD_ID_FIELD_NAME: 'POS表recordid', - TARGET_VIEW_NAME: '插件专用', - TEMPLATE_FIELD_NAME: '流程模板', - PROCESS_ORDER_FIELD: '流程顺序', - CURRENT_PROGRESS_FIELD: '当前进度', - PROCESS_NAME_FIELD: '流程名称', - FEISHU_CALLBACK_URL: 'https://open.feishu.cn/anycross/trigger/callback/MDgzNzk1ZTE1MGVhOWEzZTcyZDFjOTliZmJmOTNiYTZk', - DEFAULT_COLUMNS: ['操作', '款式SKC', '流程顺序', '流程名称', '实际开始时间', '实际完成时间'] +// 客户配置类型定义 +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: ['绣花', '印花'], + multiple: true, + 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: ['绣花', '印花'], + multiple: true, + 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: ['简单工艺(普通缝制、基础款T恤、常规裤装)', '中等工艺', '复杂工艺(立体裁剪、特殊刺绣、多色渐变印染、立体剪裁、特殊压褶)'], + required: true + }, + { + title: '订单量', + options: ['<500', '500-2000', '>2000'], + required: true + }, + { + title: '二次工艺', + options: ['绣花', '印花','压胶','烫标'], + multiple: true, + 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: true + } + ] + }, + + // RBE 客户配置 - 简化示例 + { + title: 'BE', + 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: ['绣花', '印花'], + multiple: true, + 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: 'RIVER ISLAND', + 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: true, + multiple: true, + level: 5, + parentOption: '换料重新打板', + condition: (checkedList) => checkedList.includes('换料重新打板'), + resetOn: ['首单', '无变动不需要修改','换料重新打板'] + }, + { + title: '打版类型', + options: ['复版','拍照版','测试版'], + required: true, + multiple: true, + 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: ['面料寄SGS测试', '辅料寄SGS测试', '样衣寄SGS测试','无'], + required: true, + multiple: true + }, + { + title: '二次工艺', + options: ['绣花', '印花'], + required: false, + multiple: true + }, + { + 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 + } + ] + } +]; + +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('不需要打板') + }; }; -// 类型定义 -interface ProcessRecord { - recordId: string; - fields: Record; - displayData: Record; +// 优化:使用memo包装OptionGroup组件 +interface OptionGroupProps { + group: CustomerTagGroup; + checkedList: string[]; + customer: string; + onChange: (newList: string[]) => void; + lockedOptions?: string[]; + level?: number; } -interface AppState { - selectedRecordId?: string; - processRecords: ProcessRecord[]; - loading: boolean; - error: string | null; - showAllColumns: boolean; - currentProgress: string | null; - nextProgress: string | null; - parallelProgress: string[]; -} - -interface InsertState { - modalVisible: boolean; - currentRecord: ProcessRecord | null; - direction: 'up' | 'down' | null; - selectedTemplate: string[]; - templateOptions: { label: string; value: string }[]; -} - -interface TableState { - posTable: ITable | null; - processTable: ITable | null; - posRecordIdFieldId: string | null; - tableColumns: any[]; - allTableColumns: any[]; -} - -export default function App() { - // 状态管理 - const [appState, setAppState] = useState({ - processRecords: [], - loading: false, - error: null, - showAllColumns: false, - currentProgress: null, - nextProgress: null, - parallelProgress: [] - }); - - const [insertState, setInsertState] = useState({ - modalVisible: false, - currentRecord: null, - direction: null, - selectedTemplate: [], - templateOptions: [] - }); - - const [tableState, setTableState] = useState({ - posTable: null, - processTable: null, - posRecordIdFieldId: null, - tableColumns: [], - allTableColumns: [] - }); - - // 工具函数 - const formatDateTime = (value: any): string => { - if (!value || value === '-') return '-'; - try { - const timestamp = typeof value === 'string' && /^\d+$/.test(value) ? parseInt(value) : value; - const date = new Date(timestamp > 1000000000000 ? timestamp : timestamp * 1000); - if (isNaN(date.getTime())) return value; - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - }).replace(/\//g, '-'); - } catch { - return value; - } - }; - - const isDateField = (fieldName: string): boolean => { - return ['时间', '日期', 'time', 'date'].some(keyword => - fieldName.toLowerCase().includes(keyword.toLowerCase()) - ); - }; - - const updateAppState = (updates: Partial) => { - setAppState(prev => ({ ...prev, ...updates })); - }; - - const updateInsertState = (updates: Partial) => { - setInsertState(prev => ({ ...prev, ...updates })); - }; - - const updateTableState = (updates: Partial) => { - setTableState(prev => ({ ...prev, ...updates })); - }; - - // 初始化表格 - const initializeTables = async () => { - try { - updateAppState({ loading: true, error: null }); - - const tableList = await bitable.base.getTableList(); - const tables = await Promise.all( - tableList.map(async table => ({ table, name: await table.getName() })) - ); - - const posTable = tables.find(t => t.name === CONSTANTS.POS_TABLE_NAME)?.table; - const processTable = tables.find(t => t.name === CONSTANTS.PROCESS_TABLE_NAME)?.table; - - if (!posTable) throw new Error(`未找到表格: ${CONSTANTS.POS_TABLE_NAME}`); - if (!processTable) throw new Error(`未找到表格: ${CONSTANTS.PROCESS_TABLE_NAME}`); - - const processFields = await processTable.getFieldMetaList(); - const posRecordIdField = processFields.find(field => field.name === CONSTANTS.POS_RECORD_ID_FIELD_NAME); - - if (!posRecordIdField) { - throw new Error(`在表格 ${CONSTANTS.PROCESS_TABLE_NAME} 中未找到字段: ${CONSTANTS.POS_RECORD_ID_FIELD_NAME}`); - } - - // 设置表格列 - const actionColumn = { - title: '操作', - key: 'action', - width: 80, - fixed: 'left' as const, - render: (_: any, record: ProcessRecord) => renderInsertButton(record) - }; - - const dataColumns = processFields.map(field => ({ - title: field.name, - dataIndex: field.id, - key: field.id, - render: (value: any, record: ProcessRecord) => { - const displayValue = record.displayData[field.id] || '-'; - return isDateField(field.name) && displayValue !== '-' - ? formatDateTime(displayValue) - : displayValue; - } - })); - - const allColumns = [actionColumn, ...dataColumns]; - const visibleColumns = allColumns.filter(col => - appState.showAllColumns || CONSTANTS.DEFAULT_COLUMNS.includes(col.title) - ); - - updateTableState({ - posTable, - processTable, - posRecordIdFieldId: posRecordIdField.id, - allTableColumns: allColumns, - tableColumns: visibleColumns - }); - - } catch (error) { - const message = error instanceof Error ? error.message : '初始化失败'; - updateAppState({ error: message }); - Toast.error('初始化表格失败'); - } finally { - updateAppState({ loading: false }); - } - }; - - // 加载订单流程记录 - const loadProcessRecords = async (posRecordId: string) => { - const { processTable, posRecordIdFieldId } = tableState; - if (!processTable || !posRecordIdFieldId) { - Toast.error('系统未初始化完成,请稍后重试'); - return; - } - - try { - updateAppState({ loading: true }); - - const viewList = await processTable.getViewList(); - const targetView = await Promise.all( - viewList.map(async view => ({ view, meta: await view.getMeta() })) - ).then(views => views.find(v => v.meta.name === CONSTANTS.TARGET_VIEW_NAME)); - - if (!targetView) { - throw new Error(`未找到名为"${CONSTANTS.TARGET_VIEW_NAME}"的视图`); - } - - // 清理并设置筛选条件 - const currentFilterInfo = await targetView.view.getFilterInfo(); - if (currentFilterInfo?.conditions) { - await Promise.all( - currentFilterInfo.conditions.map(condition => - targetView.view.deleteFilterCondition(condition.conditionId) - ) - ); - } - - await targetView.view.addFilterCondition({ - fieldId: posRecordIdFieldId, - operator: 'is', - value: posRecordId - }); - - await targetView.view.setFilterConjunction('and'); - await targetView.view.applySetting(); - await new Promise(resolve => setTimeout(resolve, 500)); - - const recordsResponse = await processTable.getRecords({ - pageSize: 1000, - viewId: targetView.meta.id - }); - - const processedRecords = await Promise.all( - recordsResponse.records.map(async record => { - const fields = await record.fields; - const displayData: Record = {}; - - Object.entries(fields).forEach(([fieldId, value]) => { - if (value == null) { - displayData[fieldId] = '-'; - } else if (Array.isArray(value)) { - displayData[fieldId] = value.map(item => - typeof item === 'object' && item.text ? item.text : String(item) - ).join(', '); - } else if (typeof value === 'object' && value.text) { - displayData[fieldId] = value.text; - } else { - displayData[fieldId] = String(value); - } - }); - - return { recordId: record.recordId, fields, displayData }; - }) - ); - - // 计算进度信息和排序 - const { sortedRecords, progressInfo } = await processRecordsWithProgress( - processedRecords, processTable - ); - - updateAppState({ - processRecords: sortedRecords, - ...progressInfo - }); - - Toast.success(`找到 ${sortedRecords.length} 条匹配的订单流程记录`); - - } catch (error) { - const message = error instanceof Error ? error.message : '未知错误'; - Toast.error(`加载订单流程记录失败: ${message}`); - updateAppState({ processRecords: [] }); - } finally { - updateAppState({ loading: false }); - } - }; - - // 处理记录排序和进度计算 - const processRecordsWithProgress = async (records: ProcessRecord[], processTable: ITable) => { - const processFields = await processTable.getFieldMetaList(); - const orderField = processFields.find(f => f.name === CONSTANTS.PROCESS_ORDER_FIELD); - const currentProgressField = processFields.find(f => f.name === CONSTANTS.CURRENT_PROGRESS_FIELD); - const processNameField = processFields.find(f => f.name === CONSTANTS.PROCESS_NAME_FIELD); - - let progressInfo = { - currentProgress: null as string | null, - nextProgress: null as string | null, - parallelProgress: [] as string[] - }; - - // 计算进度信息 - if (currentProgressField && processNameField && orderField) { - const currentProgressRecord = records.find(record => { - const progressValue = record.fields[currentProgressField.id]; - return progressValue != null && progressValue !== ''; - }); - - if (currentProgressRecord) { - progressInfo.currentProgress = currentProgressRecord.displayData[processNameField.id]; - const currentOrder = Number(currentProgressRecord.fields[orderField.id]); - - const nextRecords = records - .filter(record => { - const order = Number(record.fields[orderField.id]); - return Number.isInteger(order) && order > currentOrder; - }) - .sort((a, b) => Number(a.fields[orderField.id]) - Number(b.fields[orderField.id])); - - if (nextRecords.length > 0) { - const nextRecord = nextRecords[0]; - progressInfo.nextProgress = nextRecord.displayData[processNameField.id]; - - const nextOrder = Number(nextRecord.fields[orderField.id]); - progressInfo.parallelProgress = records - .filter(record => { - const order = Number(record.fields[orderField.id]); - return !Number.isInteger(order) && Math.floor(order) === nextOrder && order > nextOrder; - }) - .sort((a, b) => Number(a.fields[orderField.id]) - Number(b.fields[orderField.id])) - .map(record => record.displayData[processNameField.id]); - } else { - progressInfo.nextProgress = '已完成所有流程'; - } - } - } - - // 排序记录 - const sortedRecords = orderField ? records.sort((a, b) => { - const aNum = Number(a.fields[orderField.id]); - const bNum = Number(b.fields[orderField.id]); - if (!isNaN(aNum) && !isNaN(bNum)) return aNum - bNum; - if (isNaN(aNum) && !isNaN(bNum)) return 1; - if (!isNaN(aNum) && isNaN(bNum)) return -1; - return String(a.fields[orderField.id]).localeCompare(String(b.fields[orderField.id])); - }) : records; - - return { sortedRecords, progressInfo }; - }; - - // 加载模板选项 - const loadTemplateOptions = async () => { - let { posTable } = tableState; - - if (!posTable) { - try { - const tableList = await bitable.base.getTableList(); - const tables = await Promise.all( - tableList.map(async table => ({ table, name: await table.getName() })) - ); - posTable = tables.find(t => t.name === CONSTANTS.POS_TABLE_NAME)?.table || null; - if (!posTable) throw new Error('POS表未找到'); - updateTableState({ posTable }); - } catch (error) { - Toast.error('POS表初始化失败'); - return; - } - } - - try { - const posFields = await posTable.getFieldMetaList(); - const templateField = posFields.find(field => field.name === CONSTANTS.TEMPLATE_FIELD_NAME); - - if (!templateField) { - Toast.error('未找到[流程模板]字段'); - return; - } - - let options: { label: string; value: string }[] = []; - - if (templateField.type === FieldType.MultiSelect) { - const field = await posTable.getField(templateField.id); - const fieldOptions = await field.getOptions(); - options = fieldOptions.map(option => ({ label: option.name, value: option.id })); - } else if (templateField.type === FieldType.SingleSelect) { - const field = await posTable.getField(templateField.id); - const fieldOptions = await field.getOptions(); - options = fieldOptions.map(option => ({ label: option.name, value: option.id })); - } else { - const records = await posTable.getRecords({ pageSize: 1000 }); - const uniqueTemplates = new Set(); - - for (const record of records.records) { - const fields = await record.fields; - const templateValue = fields[templateField.id]; - - if (typeof templateValue === 'string') { - uniqueTemplates.add(templateValue); - } else if (Array.isArray(templateValue)) { - templateValue.forEach(value => { - const text = typeof value === 'object' && value.text ? value.text : String(value); - if (text) uniqueTemplates.add(text); - }); - } else if (templateValue?.text) { - uniqueTemplates.add(templateValue.text); - } - } - - options = Array.from(uniqueTemplates).map(template => ({ - label: template, - value: template - })); - } - - updateInsertState({ templateOptions: options }); - } catch (error) { - Toast.error(`获取流程模板选项失败: ${error instanceof Error ? error.message : '未知错误'}`); - } - }; - - // 处理插入确认 - const handleInsertConfirm = async () => { - const { selectedTemplate, currentRecord, direction } = insertState; - - if (!selectedTemplate.length) { - Toast.warning('请选择一个或多个流程模板'); - return; - } - - if (!currentRecord) { - Toast.error('系统状态不完整,无法执行操作'); - return; - } - - try { - updateAppState({ loading: true }); - - const processFields = await tableState.processTable!.getFieldMetaList(); - const processOrderField = processFields.find(field => field.name === CONSTANTS.PROCESS_ORDER_FIELD); - - if (!processOrderField) throw new Error('未找到"流程顺序"字段'); - - const currentOrder = currentRecord.fields[processOrderField.id]; - if (currentOrder == null) throw new Error('当前记录缺少"流程顺序"值'); - - const newOrder = Number(currentOrder) + (direction === 'up' ? -0.1 : 0.1); - - const callbackData = { - selectedTemplates: selectedTemplate, - processOrder: currentOrder, - insertDirection: direction, - newProcessOrder: newOrder, - recordId: appState.selectedRecordId, - timestamp: new Date().toISOString() - }; - - await fetch(CONSTANTS.FEISHU_CALLBACK_URL, { - method: 'POST', - mode: 'no-cors', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(callbackData) - }); - - Toast.success('数据已发送到飞书回调'); - - } catch (error) { - Toast.error(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`); - } finally { - updateAppState({ loading: false }); - resetInsertState(); - } - }; - - // 重置插入状态 - const resetInsertState = () => { - updateInsertState({ - modalVisible: false, - currentRecord: null, - direction: null, - selectedTemplate: [], - templateOptions: [] - }); - }; - - // 渲染插入按钮 - const renderInsertButton = (record: ProcessRecord) => { - const handleDirectionSelect = (direction: 'up' | 'down') => { - updateInsertState({ - direction, - currentRecord: record, - modalVisible: true - }); - loadTemplateOptions(); - }; - - return ( - - {['up', 'down'].map(dir => ( -
handleDirectionSelect(dir as 'up' | 'down')} - style={{ padding: '8px 16px', cursor: 'pointer' }} - > - {dir === 'up' ? '向上插入' : '向下插入'} -
- ))} - - } - > - -
- ); - }; - - // 切换列显示 - const toggleColumns = () => { - const newShowAll = !appState.showAllColumns; - const columnsToShow = newShowAll - ? tableState.allTableColumns - : tableState.allTableColumns.filter(col => CONSTANTS.DEFAULT_COLUMNS.includes(col.title)); - - updateAppState({ showAllColumns: newShowAll }); - updateTableState({ tableColumns: columnsToShow }); - }; - - // Effects - useEffect(() => { - initializeTables(); - }, []); - - useEffect(() => { - let cleanup: (() => void) | undefined; - - const setupSelectionListener = async () => { - const { posTable, processTable, posRecordIdFieldId } = tableState; - if (!posTable || !processTable || !posRecordIdFieldId) return; - - try { - const meta = await posTable.getMeta(); - cleanup = bitable.base.onSelectionChange(async (event) => { - if (event.data?.tableId === meta.id && event.data?.recordId) { - updateAppState({ selectedRecordId: event.data.recordId }); - await loadProcessRecords(event.data.recordId); - } - }); - } catch (error) { - console.error('设置选择监听失败:', error); - } - }; - - setupSelectionListener(); - return () => cleanup?.(); - }, [tableState.posTable, tableState.processTable, tableState.posRecordIdFieldId]); - - // 渲染组件 - const renderProgressInfo = () => { - const { currentProgress, nextProgress, parallelProgress } = appState; - if (!currentProgress && !nextProgress) return null; - - return ( - - - 进度信息 - -
- {currentProgress && ( -
- - 当前进度 - - - {currentProgress} - -
- )} - {nextProgress && ( -
- - 下个进度 - - - {nextProgress} - -
- )} - {parallelProgress.length > 0 && ( -
- - 下个进度-并行进度 - - - {parallelProgress.join(', ')} - -
- )} -
-
- ); - }; - - const renderUsageInstructions = () => ( - - - 使用说明 - - - 1. 在 {CONSTANTS.POS_TABLE_NAME} 表中点击选择任意单元格
- 2. 系统将自动获取该记录的ID
- 3. 在 {CONSTANTS.PROCESS_TABLE_NAME} 表中筛选 {CONSTANTS.POS_RECORD_ID_FIELD_NAME} 字段匹配的记录
- 4. 在下方表格中展示筛选结果
- 5. 点击"展开所有字段"按钮可查看完整信息 -
-
+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 ( -
- 订单流程查询 - - {/* 当前选中记录信息 */} - - - 当前选中记录 - - {appState.selectedRecordId ? ( - - 记录ID: {appState.selectedRecordId} - - ) : ( - - 请在 {CONSTANTS.POS_TABLE_NAME} 表中选择一条记录 - - )} - +
+
+ {group.title} + {group.required && *} +
+ +
+ ); +}); - {/* 筛选结果 */} - {appState.selectedRecordId && ( - <> - {renderProgressInfo()} - - -
- - 匹配的订单流程记录 - -
- - {appState.loading && } -
-
- - {appState.processRecords.length > 0 ? ( - `共 ${total} 条记录` - }} - rowKey="recordId" - size="small" - scroll={{ x: 'max-content' }} - style={{ marginTop: 16 }} - /> - ) : ( - !appState.loading && ( - - 未找到匹配的订单流程记录 - - } - style={{ marginTop: 20 }} - /> - ) - )} - - - )} +// 客户选择器组件 - 添加变更时清空选项的功能 +const CustomerSelector = memo(({ customers, selectedCustomer, onChange, onCustomerChange }) => { + return ( +
+
选择客户:
+
+ {customers.map(customer => ( + + ))} +
+
+ ); +}); - {/* 使用说明 */} - {!appState.selectedRecordId && renderUsageInstructions()} +// 优化:使用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 ( + <> -
- 请选择要插入的流程模板: -
+ - - - {insertState.selectedTemplate.length > 0 && ( -
- - 已选择: {insertState.selectedTemplate.map(id => - insertState.templateOptions.find(opt => opt.value === id)?.label - ).filter(Boolean).join(', ')} - + {/* 只有选择了客户才显示标签组 */} + {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 ( +
+ +
请先在多维表格中点击一个单元格以加载配置。
+
+ +
); -} \ No newline at end of file +}; + +export default App; \ No newline at end of file