/* * @Author: code4eat awesomedema@gmail.com * @Date: 2025-09-30 00:00:00 * @LastEditors: code4eat awesomedema@gmail.com * @LastEditTime: 2025-10-17 11:57:04 * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/serviceEvaluate/index.tsx * @Description: 服务评价管理 - 左树右表 + 可展开选项子表 */ import React, { useEffect, useMemo, useState } from 'react'; import './style.less'; import { Button, Input, InputNumber, message, Modal, Popconfirm, Tooltip, Transfer, Tree } from 'antd'; import { ProColumns } from '@ant-design/pro-table'; import { ModalForm, ProFormDigit, ProFormText } from '@ant-design/pro-form'; import KCTable from '@/components/kcTable'; import DirectoryTree from 'antd/es/tree/DirectoryTree'; import { DataNode } from 'antd/es/tree'; import type { TransferDirection } from 'antd/es/transfer'; import { createFromIconfontCN } from '@ant-design/icons'; import expandedIcon from '../../../../../public/images/treenode_open.png'; import closeIcon from '../../../../../public/images/treenode_collapse.png'; import { KCInput } from '@/components/KCInput'; import { getDictByDictTypeAndSysid } from '@/service/dictionary'; import { getCategoryTree, addCategory, getProjectList, getOptionList, addProject, delProject, addOption, delOption, updateProjectWeight, updateOptionScore, CategoryNode, ProjectItem, OptionItem, SystemTreeNode, getSystemListForModal, addSystems, getSelectedSystemList, addServiceTypes, getServiceEvaluationItem, getEvaluationSelect, addEvaluationItem, addEvaluationSelect, editEvaluationSelect, deleteEvaluationItem, deleteEvaluationSelect, } from './service'; const IconFont = createFromIconfontCN({ scriptUrl: '/zhongtaiC.js', }); // 子表格:选项列表 const OptionsList: React.FC<{ project: ProjectItem; options: OptionItem[]; onAdd: (data: { optionCode: string; optionName: string; score: number }) => Promise; onDelete: (optionId: string) => Promise; onScoreChange: (optionId: string, score: number) => Promise; }> = ({ project, options, onAdd, onDelete, onScoreChange }) => { return (
{project.projectName} - 选项 新增选项} modalProps={{ destroyOnClose: true }} onFinish={async (values) => { await onAdd(values as any); return true; }} >
序号
选项代码
选项名称
选项得分
操作
{options && options.length > 0 ? ( options.map((opt, index) => (
{index + 1}
{opt.optionCode}
{opt.optionName}
{ const v = typeof val === 'number' ? val : Number(val); if (!Number.isNaN(v)) onScoreChange(opt.optionId, v); }} />
onDelete(opt.optionId)}> 删除
)) ) : (
暂无选项
)}
); }; const ServiceEvaluatePage: React.FC = () => { // 左侧分类树 const [categoryTree, set_categoryTree] = useState([]); const [currentCategory, set_currentCategory] = useState(); const [expandedKeys, setExpandedKeys] = useState([]); const [autoExpandParent, setAutoExpandParent] = useState(true); const [searchValue, setSearchValue] = useState(''); // 主表相关 const [reload, set_reload] = useState(false); const [tableParams, set_tableParams] = useState<{ current: number; pageSize: number; categoryId?: string }>({ current: 1, pageSize: 10 }); const [tableKey, set_tableKey] = useState(0); // 用于强制刷新表格 const [expandedRowKeys, set_expandedRowKeys] = useState([]); // 追踪展开的行 // 展开区缓存选项数据,避免频繁请求 const [projectOptionsMap, set_projectOptionsMap] = useState>({}); // 项目权重编辑态(保持原交互:编辑/勾选) const [editingProjectId, set_editingProjectId] = useState(null); const [editingProjectWeight, set_editingProjectWeight] = useState(0); // 添加系统弹窗 const [sysModalOpen, set_sysModalOpen] = useState(false); const [systemTree, set_systemTree] = useState([]); const [sysTargetKeys, set_sysTargetKeys] = useState([]); const [sysSelectedKeys, set_sysSelectedKeys] = useState([]); const [sysExpandedKeys, set_sysExpandedKeys] = useState([]); // 服务项弹窗 const [serviceModalOpen, set_serviceModalOpen] = useState(false); const [serviceDict, set_serviceDict] = useState<{ key: string; title: string }[]>([]); const [serviceSelected, set_serviceSelected] = useState([]); const [serviceBindSystemId, set_serviceBindSystemId] = useState(''); const [serviceSaving, set_serviceSaving] = useState(false); // 服务节点展开行:缓存“评价项 -> 选项”数据(与 /centerSys/evaluation/getEvaluationSelect 返回结构一致) const [serviceOptionsMap, set_serviceOptionsMap] = useState< Record >({}); // 评价项目弹窗(右侧“添加”) const [evalItemModalOpen, set_evalItemModalOpen] = useState(false); const [evalItemDict, set_evalItemDict] = useState<{ key: string; title: string }[]>([]); const [evalItemTargetKeys, set_evalItemTargetKeys] = useState([]); const [evalItemSelectedKeys, set_evalItemSelectedKeys] = useState([]); // 评价项权重编辑态 const [editingEvalCode, set_editingEvalCode] = useState(null); const [editingEvalValue, set_editingEvalValue] = useState(0); // 选项弹窗(评价项 -> 选项) const [selectModalOpen, set_selectModalOpen] = useState(false); const [selectDict, set_selectDict] = useState<{ key: string; title: string }[]>([]); const [selectTargetKeys, set_selectTargetKeys] = useState([]); const [selectSelectedKeys, set_selectSelectedKeys] = useState([]); const [bindItemCode, set_bindItemCode] = useState(''); // 服务节点主表数据缓存(用于拖拽排序后保持展示顺序) const [evalItemRows, set_evalItemRows] = useState([]); const [draggingMainKey, set_draggingMainKey] = useState(null); const [draggingOverMainKey, set_draggingOverMainKey] = useState(null); const [useLocalEvalOrder, set_useLocalEvalOrder] = useState(false); // 嵌套选项拖拽的中间态 const [draggingOption, set_draggingOption] = useState<{ parent: string; key: string } | null>(null); const [draggingOverOption, set_draggingOverOption] = useState<{ parent: string; key: string } | null>(null); // 工具:移动数组元素 const moveItem = (list: T[], from: number, to: number): T[] => { const next = [...list]; const [m] = next.splice(from, 1); next.splice(to, 0, m); return next; }; // 获取左侧“已选系统”并渲染为单层树 const fetchTree = async () => { const list = await getSelectedSystemList(); const tree: any[] = (list || []).map((item: any) => ({ id: String(item.systemId), name: String(item.systemName), nodeType: 'system', selectable: false, // 将 serviceList 映射为子节点 children: (item.serviceList || []).map((s: any) => ({ id: String(s.id), name: String(s.name), code: String(s.code || ''), nodeType: 'service', selectable: true, })), })); set_categoryTree(tree as any); // 默认选中第一个服务;若无服务则只展开系统,不触发右侧 if (tree.length > 0) { const sys = tree[0]; if (sys.children && sys.children.length > 0) { const firstChild = sys.children[0]; set_currentCategory(firstChild as any); set_tableParams((p) => ({ ...p, current: 1, categoryId: firstChild.id })); set_reload(true); set_editingEvalCode(null); // 服务节点:首次加载右侧评价项 if ((firstChild as any).nodeType === 'service') { await loadServiceEvalItems(String((firstChild as any).code || (firstChild as any).name)); } } if (sys.children && sys.children.length > 0) { setExpandedKeys([sys.id]); setAutoExpandParent(true); } } }; // 获取系统树(弹窗数据源) const fetchSystemTree = async () => { const { systemList, selectSystemList } = await getSystemListForModal(); set_systemTree(systemList || []); // 默认已选 set_sysTargetKeys((selectSystemList || []).map((v)=> String(v))); // 默认全部展开 const allKeys: string[] = []; const walk = (list: SystemTreeNode[]) => { list.forEach(n=>{ allKeys.push(String(n.code)); if (n.children && n.children.length) walk(n.children); }); }; walk(systemList || []); set_sysExpandedKeys(allKeys); }; // 获取服务项字典 const fetchServiceDict = async () => { // 使用中心系统ID:1547394914533380096 获取指定字典类型 const resp: any = await getDictByDictTypeAndSysid('1547394914533380096', 'EVALUATION_SERVICE'); const list = (resp?.dataVoList || []).map((x: any) => ({ key: String(x.code || x.value), title: String(x.name) })); set_serviceDict(list); }; // 获取评价项目字典 const fetchEvaluationItemsDict = async () => { const resp: any = await getDictByDictTypeAndSysid('1547394914533380096', 'EVALUATION_ITEM'); const list = (resp?.dataVoList || []).map((x: any) => ({ key: String(x.code || x.value), title: String(x.name) })); set_evalItemDict(list); }; // 获取“选项”字典 const fetchEvaluationSelectDict = async () => { const resp: any = await getDictByDictTypeAndSysid('1547394914533380096', 'EVALUATION_SELECT'); const list = (resp?.dataVoList || []).map((x: any) => ({ key: String(x.code || x.value), title: String(x.name) })); set_selectDict(list); }; // 拉取服务类型下的评价项(用于 dataSource 渲染 & 拖拽) const loadServiceEvalItems = async (serviceCode: string) => { const list = (await getServiceEvaluationItem(serviceCode)) || []; const normalizePercent = (v: any): number => { const n = Number(v); return Number.isFinite(n) && n >= 0 ? n : 0; }; const mapped = (list || []).map((it: any, idx: number) => ({ id: it.id, // 保留 id 字段,用于删除等操作 itemCode: it.itemCode, itemName: it.itemName, percent: normalizePercent(it.percent), sort: it.sort ?? idx + 1, })); set_evalItemRows(mapped); set_useLocalEvalOrder(true); }; // 获取表格数据:服务节点 → 评价项;系统/分类节点 → 项目列表 const getTableData = async (params: any) => { set_reload(false); if (!currentCategory) return { data: [], success: true } as any; const node: any = currentCategory as any; if (node.nodeType === 'service') { // 使用 dataSource 渲染时,这里返回本地 rows,避免与 KCTable 拖拽冲突 if (evalItemRows && evalItemRows.length > 0) { return { data: evalItemRows, success: true } as any; } await loadServiceEvalItems(String(node.code || node.name)); return { data: evalItemRows, success: true } as any; } const resp = await getProjectList({ categoryId: currentCategory.id, ...params }); return { data: resp.list || [], success: true } as any; }; // 主表列定义 - 项目 const projectColumns: ProColumns[] = useMemo( () => [ { title: '', dataIndex: 'drag', width: 32, render: () => () }, { title: '序号', dataIndex: 'index', width: 60, render: (_, __, index) => index + 1, }, { title: '评价项目代码', dataIndex: 'projectCode', width: 100, }, { title: '评价项目名称', dataIndex: 'projectName', ellipsis: true, }, { title: '权重', dataIndex: 'weight', width: 120, render: (_, record) => ( `${value}%`} parser={(value) => Number(String(value).replace('%', ''))} value={record.weight} onChange={async (val) => { const weight = typeof val === 'number' ? val : Number(val); if (Number.isNaN(weight)) return; const ok = await updateProjectWeight(record.projectId, weight); if (ok) set_reload(true); }} /> ), }, { title: '操作', key: 'option', width: 160, valueType: 'option', render: (_, record) => [ 选项} width={520} onFinish={async (values) => { const ok = await addOption({ projectId: record.projectId, ...(values as any) }); if (ok) { const opts = await getOptionList({ projectId: record.projectId }); set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] })); } return !!ok; }} > , { const ok = await delProject(record.projectId); if (ok) set_reload(true); }}> 删除 , ], }, ], [projectOptionsMap] ); // 评价项表格列 const evalItemColumns: ProColumns[] = useMemo( () => [ { title: '', dataIndex: 'drag', width: 32, render: () => () }, { title: '序号', dataIndex: 'index', width: 60, render: (_, __, index) => index + 1 }, { title: '评价项目代码', dataIndex: 'itemCode', width: 120 }, { title: '评价项目名称', dataIndex: 'itemName', ellipsis: true }, { title: '权重', dataIndex: 'percent', width: 120, render: (v: any) => `${(Number.isFinite(Number(v)) && Number(v) >= 0 ? Number(v) : 0)}%`, }, // 移除不需要的排序列 { title: '操作', valueType: 'option', width: 120, render: (_, record) => [ { set_bindItemCode(record.itemCode); // 获取该评价项已有的选项 const existingOptions = await getEvaluationSelect(record.itemCode); const existingKeys = (existingOptions || []).map((item: any) => String(item.code)); set_selectTargetKeys(existingKeys); set_selectSelectedKeys([]); await fetchEvaluationSelectDict(); set_selectModalOpen(true); }}>选项, { const res = await deleteEvaluationItem(record.id); if (res !== false) { // 删除成功后,如果该项是展开状态,从展开列表中移除 set_expandedRowKeys((prev) => prev.filter(k => k !== record.itemCode)); // 重新加载评价项列表 const current: any = currentCategory as any; if (current && current.nodeType === 'service') { await loadServiceEvalItems(String(current.code || current.name)); } } }}> 删除 , ], }, ], [editingEvalCode, editingEvalValue] ); // 展开行渲染(分类/系统场景使用) const expandedRowRender = (record: ProjectItem) => { const options = projectOptionsMap[record.projectId] || []; return ( { const ok = await addOption({ projectId: record.projectId, ...data }); if (ok) { const opts = await getOptionList({ projectId: record.projectId }); set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] })); } }} onDelete={async (optionId) => { const ok = await delOption(optionId); if (ok) { const opts = await getOptionList({ projectId: record.projectId }); set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] })); } }} onScoreChange={async (optionId, score) => { const ok = await updateOptionScore(optionId, score); if (ok) { const opts = await getOptionList({ projectId: record.projectId }); set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] })); } }} /> ); }; // 服务节点:展开渲染(与外层保持一致的 KCTable 风格) const expandedRowRenderService = (record: any) => { const parentCode: string = record.itemCode; const rows = serviceOptionsMap[parentCode] || []; const columns: ProColumns[] = [ { title: '', dataIndex: 'drag', width: 32, render: (_, row: any) => ( { set_draggingOption({ parent: parentCode, key: String(row.id ?? row.code) }); e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', String(row.id ?? row.code)); } catch {} }} onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }} onDrop={(e) => { e.preventDefault(); if (!draggingOption || draggingOption.parent !== parentCode) return; const list = serviceOptionsMap[parentCode] || []; const from = list.findIndex((r: any) => String(r.id ?? r.code) === draggingOption.key); const to = list.findIndex((r: any) => String(r.id ?? r.code) === String(row.id ?? row.code)); if (from === -1 || to === -1 || from === to) return; const next = moveItem(list, from, to); set_serviceOptionsMap((old) => ({ ...old, [parentCode]: next })); set_draggingOption(null); set_reload((v)=>!v); // TODO: 保存选项排序 }} title="拖动排序" > ), }, { title: '序号', dataIndex: 'index', width: 60, render: (_, __, index) => index + 1 }, { title: '选项代码', dataIndex: 'code', width: 120 }, { title: '选项名称', dataIndex: 'name', ellipsis: true }, { title: '选项得分', dataIndex: 'value', width: 200, render: (v: any, row: any) => { const isEditing = row.__editing === true; if (!isEditing) { return (
{v ?? 0}
{ set_serviceOptionsMap((old) => { const list = [...(old[parentCode] || [])]; const idx = list.findIndex((i: any) => String(i.id ?? i.code) === String(row.id ?? row.code)); if (idx > -1) list[idx] = { ...list[idx], __editing: true } as any; return { ...old, [parentCode]: list }; }); }} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: 28, width: 28, border: '1px solid #dae2f2', borderRadius: 6, cursor: 'pointer' }} >
); } return (
{ const next = Number(val) || 0; set_serviceOptionsMap((old) => { const list = [...(old[parentCode] || [])]; const idx = list.findIndex((i: any) => String(i.id ?? i.code) === String(row.id ?? row.code)); if (idx > -1) list[idx] = { ...list[idx], value: next, __editing: true } as any; return { ...old, [parentCode]: list }; }); }} style={{ width: 100 }} /> { // 调用接口保存选项分值 const res = await editEvaluationSelect(row.id, row.value); // 响应拦截器:成功返回 data 或 true,失败返回 false // 且 POST 成功时拦截器已自动显示"操作成功!"提示 if (res !== false) { // 成功,退出编辑状态 set_serviceOptionsMap((old) => { const list = [...(old[parentCode] || [])]; const idx = list.findIndex((i: any) => String(i.id ?? i.code) === String(row.id ?? row.code)); if (idx > -1) list[idx] = { ...list[idx], __editing: false } as any; return { ...old, [parentCode]: list }; }); } // 失败时拦截器已显示错误提示,这里只需保持编辑状态 }} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: 28, width: 28, border: '1px solid #dae2f2', borderRadius: 6, cursor: 'pointer' }} >
); }, }, { title: '操作', valueType: 'option', width: 120, render: (_, row: any) => [ { const res = await deleteEvaluationSelect(row.id); if (res !== false) { // 删除成功,重新加载该评价项的选项列表 const list = await getEvaluationSelect(parentCode); set_serviceOptionsMap((old) => ({ ...old, [parentCode]: (list || []) as any, })); } }} > 删除 ], }, ]; return (
String(r.id ?? r.code)} dragSortKey="id" dragType="KC_TABLE_NESTED_ROW" pagination={false} toolBarRender={false as any} search={false as any} options={false as any} bordered={false as any} size={'small' as any} dragSort onDragSortEnd={(next)=>{ set_serviceOptionsMap((old)=> ({ ...old, [parentCode]: next })); }} />
); }; // 表格展开回调 const onExpand = async (expanded: boolean, record: any) => { const node: any = currentCategory as any; const rowKey = node?.nodeType === 'service' ? record.itemCode : record.projectId; // 更新展开行状态 if (expanded) { set_expandedRowKeys((prev) => [...prev, rowKey]); } else { set_expandedRowKeys((prev) => prev.filter(k => k !== rowKey)); return; } // 加载数据 if (node && node.nodeType === 'service') { const list = await getEvaluationSelect(record.itemCode); set_serviceOptionsMap((old: Record) => ({ ...old, [record.itemCode]: (list || []) as { id: number | string; hospId?: string; itemCode: string; code: string; name: string; value: number; sort: number }[], })); } else { const opts = await getOptionList({ projectId: record.projectId }); set_projectOptionsMap((old) => ({ ...old, [record.projectId]: opts || [] })); } }; // 收起所有展开行 const collapseAllRows = () => { set_expandedRowKeys([]); }; useEffect(() => { fetchTree(); }, []); useEffect(() => { if (sysModalOpen) fetchSystemTree(); }, [sysModalOpen]); useEffect(() => { if (currentCategory) { set_tableParams((p) => ({ ...p, current: 1, categoryId: currentCategory.id })); set_reload(true); // 切换分类时清空展开行 set_expandedRowKeys([]); } }, [currentCategory]); return ( <>
setSearchValue(e.target.value)} /> set_sysModalOpen(true)}>
{categoryTree && categoryTree.length>0 && ( { const node = info.node as any; // 仅允许选择服务节点 if (node.nodeType !== 'service') return; set_currentCategory(node); set_tableParams((p)=>({ ...p, current:1, categoryId: node.id })); set_reload(true); // 服务节点:拉取右侧评价项(dataSource 渲染) await loadServiceEvalItems(String(node.code || node.name)); }} onExpand={(keys)=>{ setExpandedKeys(keys as React.Key[]); setAutoExpandParent(false);} } expandedKeys={expandedKeys} autoExpandParent={autoExpandParent} selectedKeys={currentCategory && (currentCategory as any).nodeType==='service' ? [currentCategory.id] : []} blockNode className="KC-DirectoryTree" icon={() => null} titleRender={(nodeData: any) => { const strTitle = nodeData.name as string; const index = strTitle.indexOf(searchValue); const beforeStr = strTitle.substring(0, index); const afterStr = strTitle.slice(index + searchValue.length); const title = index > -1 ? ( {beforeStr} {searchValue} {afterStr} ) : ( {strTitle} ); const isDirectory = Array.isArray(nodeData.children) && nodeData.children.length > 0; return (
{title} {nodeData.nodeType === 'system' && ( { e.stopPropagation(); set_serviceBindSystemId(String(nodeData.id)); // 打开前重置已选,避免上次选择残留 set_serviceSelected([]); await fetchServiceDict(); set_serviceModalOpen(true); }} > )}
); }} treeData={categoryTree as unknown as DataNode[]} switcherIcon={(props:any)=> props.expanded ? () : ()} /> )}
{currentCategory?.name || '-'}
{expandedRowKeys.length > 0 && ( 收起所有 ({expandedRowKeys.length}) )} { // 获取当前已有的评价项,用于回显 const current: any = currentCategory as any; if (current && current.nodeType === 'service') { const existingItems = await getServiceEvaluationItem(String(current.code || current.name)); const existingKeys = (existingItems || []).map((item: any) => String(item.itemCode)); set_evalItemTargetKeys(existingKeys); } else { set_evalItemTargetKeys([]); } set_evalItemSelectedKeys([]); await fetchEvaluationItemsDict(); set_evalItemModalOpen(true); }}> 添加
} onFinish={async (values) => { if (!currentCategory) return false; const ok = await addProject({ categoryId: currentCategory.id, ...(values as any) }); if (ok) set_reload(true); return !!ok; }} >
{currentCategory && ( String(r?.itemCode ?? '')) : ((r: any) => String(r?.projectId ?? ''))) as any} dragSortKey={(currentCategory as any)?.nodeType === 'service' ? 'itemCode' : 'projectId'} dragType="KC_TABLE_MAIN_ROW" disabledDragKeys={expandedRowKeys} scroll={{ y: `calc(100vh - 240px)` }} columns={((currentCategory as any)?.nodeType === 'service' ? evalItemColumns : projectColumns) as ProColumns[]} request={(((currentCategory as any)?.nodeType === 'service') ? undefined : ((p: any, s: any, f: any) => getTableData(p))) as any} expandable={{ expandedRowRender: (currentCategory as any)?.nodeType === 'service' ? expandedRowRenderService : expandedRowRender, onExpand, expandedRowKeys, onExpandedRowsChange: (keys: readonly React.Key[]) => set_expandedRowKeys([...keys]), expandIconColumnIndex: 1 // 展开图标显示在第2列,第1列留给拖拽手柄 }} pagination={false} dragSort={(currentCategory as any)?.nodeType === 'service'} dataSource={((currentCategory as any)?.nodeType === 'service') ? evalItemRows : undefined} onDragSortEnd={(rows: any[])=>{ set_evalItemRows(rows); set_useLocalEvalOrder(true); }} /> )}
set_sysModalOpen(false)} onOk={async ()=>{ const flat = (nodes: SystemTreeNode[]): SystemTreeNode[] => nodes.reduce((acc, n)=>{ acc.push(n); if (n.children && n.children.length) acc.push(...flat(n.children)); return acc; }, []); const all = flat(systemTree); const payload = all .filter(n => sysTargetKeys.includes(n.code) && n.type === 3) .map(n => ({ systemId: n.code, systemName: n.name })); if (payload.length === 0) { message.warning('请选择系统'); return; } const ok = await addSystems(payload as any); if (ok) { set_sysModalOpen(false); // 刷新左侧“已选系统”列表 set_reload(true); fetchTree(); } }} width={720} destroyOnClose > { list.forEach(n=>{ res.push({ key: n.code, code: n.code, name: n.name, type: n.type, node: n }); if (n.children && n.children.length) walk(n.children, false); }) }; walk(systemTree, false); return res; })()} rowKey={(item)=> item.code} titles={[`待选项`, `已选项`]} targetKeys={sysTargetKeys} selectedKeys={sysSelectedKeys} showSearch locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }} render={(item)=> item.name} onChange={(nextTargetKeys)=> set_sysTargetKeys(nextTargetKeys as string[])} onSelectChange={(sourceSelectedKeys, targetSelectedKeys)=> set_sysSelectedKeys([...(sourceSelectedKeys as string[]), ...(targetSelectedKeys as string[])])} > {({ direction, onItemSelect, onItemSelectAll, selectedKeys })=>{ if (direction === 'left') { const treeData = (function toTree(list: SystemTreeNode[]): any[] { return list.map((n: SystemTreeNode): any => ({ key: n.code, title: n.name, // 不再用 children 判断是否可勾选;统一允许勾选,由 onCheck 决定加入哪些 key disabled: false, children: n.children ? (toTree(n.children) as any[]) : undefined, })); })(systemTree); const checkedKeys = [...selectedKeys, ...sysTargetKeys]; return ( set_sysExpandedKeys(keys as string[])} onCheck={(keys, { node })=>{ // 仅加入“系统叶子”:type==3,或 type==5 但无 children(后端把系统标成5时兜底) const isSystemLeafByCode = (code: string) => { const flat = (function flat(){ const res: any[] = []; const walk = (list: SystemTreeNode[])=>{ list.forEach(nn=>{ res.push({ code: nn.code, type: nn.type, children: nn.children }); if(nn.children) walk(nn.children); }); }; walk(systemTree); return res; })(); const found = flat.find(i=> i.code===code); if (!found) return false; const noChildren = !found.children || found.children.length===0; return found.type===3 || (found.type===5 && noChildren); }; const getChildCodes = (n:any): string[] => { if (!n.children || n.children.length===0) return [n.key as string]; return n.children.reduce((acc:any,c:any)=> acc.concat(getChildCodes(c)), []); }; const isSelect = !(node as any).checked; const candidate = (node.children && node.children.length>0) ? getChildCodes(node) : [node.key as string]; const systemLeafKeys = candidate.filter((code)=> isSystemLeafByCode(code)); onItemSelectAll(systemLeafKeys as string[], isSelect); }} /> ); } return null; }} { set_serviceModalOpen(false); set_serviceSelected([]); set_serviceBindSystemId(''); }} confirmLoading={serviceSaving} onOk={async ()=> { if (!serviceBindSystemId) { set_serviceModalOpen(false); return; } try { set_serviceSaving(true); const ok: any = await addServiceTypes(serviceBindSystemId, serviceSelected); if (ok) { set_serviceModalOpen(false); set_serviceSelected([]); set_serviceBindSystemId(''); // 刷新左侧列表 set_reload(true); fetchTree(); } } finally { set_serviceSaving(false); } }} width={560} destroyOnClose > item.key} render={(item)=> item.title} targetKeys={serviceSelected} onChange={(next)=> set_serviceSelected(next as string[])} listStyle={{ height: 460, width: 'calc(50% - 32px)' }} operationStyle={{ margin: '0 12px', display: 'flex', alignItems: 'center' }} locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }} showSearch /> { set_evalItemModalOpen(false); set_evalItemTargetKeys([]); set_evalItemSelectedKeys([]); }} onOk={async ()=> { // 依据当前选中的服务类型保存评价项目 const current: any = currentCategory as any; if (!current || current.nodeType !== 'service') { set_evalItemModalOpen(false); return; } // 从字典中匹配详细信息 const itemCodes = evalItemTargetKeys.map((k, idx)=>{ const found = evalItemDict.find(i=> i.key === k); return { itemCode: k, itemName: found?.title || k, itemValue: 1, sort: idx + 1 }; }); const ok: any = await addEvaluationItem(String(current.code || current.name), itemCodes); if (ok) { set_evalItemModalOpen(false); set_evalItemTargetKeys([]); set_evalItemSelectedKeys([]); // 刷新右侧表格:重新加载评价项列表 await loadServiceEvalItems(String(current.code || current.name)); } }} width={560} destroyOnClose > item.key} render={(item)=> item.title} targetKeys={evalItemTargetKeys} selectedKeys={evalItemSelectedKeys} onChange={(next)=> set_evalItemTargetKeys(next as string[])} onSelectChange={(l,r)=> set_evalItemSelectedKeys([...(l as string[]), ...(r as string[])])} listStyle={{ height: 460, width: 'calc(50% - 32px)' }} operationStyle={{ margin: '0 12px', display: 'flex', alignItems: 'center' }} showSearch locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }} /> { set_selectModalOpen(false); set_selectTargetKeys([]); set_selectSelectedKeys([]); set_bindItemCode(''); }} onOk={async ()=> { const selectCodes = selectTargetKeys.map((k, idx)=>{ const found = selectDict.find(i=> i.key === k); return { itemCode: k, itemName: found?.title || k, itemValue: 1, sort: idx + 1 }; }); const ok: any = await addEvaluationSelect(bindItemCode, selectCodes); if (ok !== false) { set_selectModalOpen(false); set_selectTargetKeys([]); set_selectSelectedKeys([]); // 重新加载该评价项的选项数据 const list = await getEvaluationSelect(bindItemCode); set_serviceOptionsMap((old) => ({ ...old, [bindItemCode]: (list || []) as any, })); set_bindItemCode(''); set_reload(true); } }} width={560} destroyOnClose > item.key} render={(item)=> item.title} targetKeys={selectTargetKeys} selectedKeys={selectSelectedKeys} onChange={(next)=> set_selectTargetKeys(next as string[])} onSelectChange={(l,r)=> set_selectSelectedKeys([...(l as string[]), ...(r as string[])])} listStyle={{ height: 360, width: 'calc(50% - 32px)' }} operationStyle={{ margin: '0 12px', display: 'flex', alignItems: 'center' }} showSearch locale={{ itemUnit:'项', itemsUnit:'项', searchPlaceholder:'请输入' }} /> ); }; export default ServiceEvaluatePage;