/* * @Author: code4eat awesomedema@gmail.com * @Date: 2025-06-10 12:15:25 * @LastEditors: code4eat awesomedema@gmail.com * @LastEditTime: 2025-07-25 16:09:17 * @FilePath: /pfmBackMana/src/pages/checkUnitMana/index.tsx * @Description: 查核单位管理 */ import { createFromIconfontCN, SearchOutlined, PlusOutlined, MoreOutlined, InboxOutlined, } from '@ant-design/icons'; import { ActionType, ProColumns, ModalForm, ProFormText, ProFormRadio, ProFormUploadDragger, ProFormDigit, } from '@ant-design/pro-components'; import { Button, Input, message, Tabs, Radio, Empty, Card, Dropdown, Menu, Modal, Select, Checkbox, Pagination, Popconfirm, Tooltip, } from 'antd'; import React, { useRef, useState, useEffect } from 'react'; import { request } from 'umi'; import { getDeptClassList, addDeptClass, addDepartmentByClass, getDeptClassTree, getDeptClassFilterConditionList, applyDeptClassFilterCondition, updateDeptClass, delDeptClass, updateDepartmentByClass, delDepartmentByClass, getDeptEmployee, getDeptPendingEmployee, addDeptEmployee, setDeptManager, delDeptEmp, getDeptCheckpoint, getDeptPendingCheckpoint, addDeptCheckpoint, delDeptCheckpoint, } from './service'; import './style.less'; import KCIMPagecontainer from '@/components/KCIMPageContainer'; import { KCIMTable } from '@/components/KCIMTable'; const IconFont = createFromIconfontCN({ scriptUrl: '', }); const { TabPane } = Tabs; // 下载模板方法 const downloadTemplate = async (type: number) => { try { // 获取token(如有) let token = ''; const userDataStr = localStorage.getItem('userData'); if (userDataStr) { try { token = JSON.parse(userDataStr).token || ''; } catch (e) { token = ''; } } const fetchOptions: any = { method: 'GET' }; if (token) { fetchOptions.headers = { token }; } const res = await fetch(`/gateway/manager/Department/exportPfmKnowledgeTemp?type=${type}`, fetchOptions); if (!res.ok) { message.error('下载模板失败'); return; } const blob = await res.blob(); // 尝试从header获取文件名 let fileName = ''; const disposition = res.headers.get('content-disposition'); if (disposition && disposition.indexOf('filename=') !== -1) { fileName = decodeURIComponent(disposition.split('filename=')[1].replace(/"/g, '')); } else { fileName = type === 1 ? '系统追踪模板.xlsx' : '自查督查模板.xlsx'; } const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); } catch (e) { message.error('下载模板失败'); } }; export default function CheckUnitMana() { // 状态定义 const [selectedDept, setSelectedDept] = useState(null); const [deptInfo, setDeptInfo] = useState(''); const [searchKeywords, setSearchKeywords] = useState(''); const [personnelSearchInput, setPersonnelSearchInput] = useState(''); // 人员搜索输入框的值 const [activeTab, setActiveTab] = useState('personnel'); const tableRef = useRef(); const conditionTableRef = useRef(); const [expandedKeys, setExpandedKeys] = useState([]); const [treeData, setTreeData] = useState([]); const [treeSearchKeywords, setTreeSearchKeywords] = useState(''); // 树形搜索关键词 const [filteredTreeData, setFilteredTreeData] = useState([]); // 过滤后的树形数据 const [hoveredNode, setHoveredNode] = useState(null); // 当前悬停的节点 const [openDropdownNode, setOpenDropdownNode] = useState(null); // 当前打开下拉菜单的节点 // 查核条件弹窗相关状态 const [conditionModalVisible, setConditionModalVisible] = useState(false); // 条件弹窗显示状态 const [conditionSearchType, setConditionSearchType] = useState('个案'); // 条件搜索类型 const [conditionSearchKeyword, setConditionSearchKeyword] = useState(''); // 条件搜索关键词 const [selectedConditions, setSelectedConditions] = useState([]); // 已选择的条件ID列表 const [currentNode, setCurrentNode] = useState(null); // 当前操作的节点 // 右侧内容区ref和操作栏定位状态 const cardRef = useRef(null); const [actionBarStyle, setActionBarStyle] = useState<{ left: number; width: number; }>({ left: 0, width: 0 }); // 1. 新增弹窗相关状态 const [checkPointModalVisible, setCheckPointModalVisible] = useState(false); const [checkPointTableRef, setCheckPointTableRef] = useState(null); const [selectedCheckPointKeys, setSelectedCheckPointKeys] = useState< string[] >([]); // 记录当前页数据用于全选逻辑 const [currentCheckPointPageData, setCurrentCheckPointPageData] = useState< any[] >([]); // 获取当前页面所有可选的要点ID(用于全选逻辑) const getCurrentPageCheckPointIds = () => { return currentCheckPointPageData .filter((item) => item.rowSpan > 0) // 只选择有rowSpan的行(要点行) .map((item) => item.id); }; // 更新操作栏定位,支持ResizeObserver const updateActionBarPosition = () => { if (cardRef.current) { const rect = cardRef.current.getBoundingClientRect(); setActionBarStyle({ left: rect.left, width: rect.width }); } }; useEffect(() => { updateActionBarPosition(); window.addEventListener('resize', updateActionBarPosition); window.addEventListener('scroll', updateActionBarPosition, true); let resizeObserver: ResizeObserver | null = null; if (cardRef.current && typeof ResizeObserver !== 'undefined') { resizeObserver = new ResizeObserver(() => { updateActionBarPosition(); }); resizeObserver.observe(cardRef.current); } return () => { window.removeEventListener('resize', updateActionBarPosition); window.removeEventListener('scroll', updateActionBarPosition, true); if (resizeObserver && cardRef.current) { resizeObserver.unobserve(cardRef.current); resizeObserver.disconnect(); } }; }, []); // 获取单位分类树 const fetchDeptClassList = async () => { try { const res = await getDeptClassTree(); // 尝试多种数据结构格式 let dataArray = null; if (res && res.data && Array.isArray(res.data)) { // 格式1: { data: [...] } dataArray = res.data; } else if (Array.isArray(res)) { // 格式2: 直接返回数组 dataArray = res; } else if (res && Array.isArray(res.items)) { // 格式3: { items: [...] } dataArray = res.items; } if (dataArray && dataArray.length > 0) { const transformedData = dataArray.map((item: any) => { // 处理子节点数据 - 只处理children字段,不处理filterConditionList let children = []; // 只使用 children 字段作为子节点 if (item.children && Array.isArray(item.children)) { children = item.children.map((child: any) => ({ title: child.name, key: child.id || child.code, type: child.type || 'department', // 标记为查核单位 visitType: child.visitType, deptType: child.deptType ?? 0, // 确保有默认值 sort: child.sort, filterConditionList: child.filterConditionList || [], // 保留查核条件 })); } // 如果没有children,说明这是叶子节点,不需要处理filterConditionList作为子节点 return { title: item.name, key: item.id, type: item.type, // 类型:1单位分类 2查核单位 visitType: item.visitType, // 诊别:1门诊 2急诊 deptType: item.deptType ?? 0, // 单位类型:0普通单位 1职能科室,确保有默认值 sort: item.sort, children: children, filterConditionList: item.filterConditionList || [], // 保留查核条件 }; }); setTreeData(transformedData); setFilteredTreeData(transformedData); // 初始化过滤数据 // 默认展开所有父节点 setExpandedKeys(transformedData.map((item: any) => item.key)); // 默认选中逻辑:选择第一个叶子节点(没有子节点的节点本身就是叶子节点) if (transformedData.length > 0) { let defaultSelected = null; // 遍历所有节点,找到第一个叶子节点 for (const parent of transformedData) { if (parent.children && parent.children.length > 0) { // 如果有子节点,选择第一个子节点 const firstChild = parent.children[0]; defaultSelected = { ...firstChild, parentTitle: parent.title, filterConditionList: firstChild.filterConditionList || [], }; break; } else { // 如果没有子节点,说明它本身就是叶子节点,直接选中 defaultSelected = { ...parent, parentTitle: '单位分类', filterConditionList: parent.filterConditionList || [], }; break; } } setSelectedDept(defaultSelected); } } else { message.error('获取单位分类树失败'); } } catch (error) { console.error('接口调用异常:', error); message.error('获取单位分类树失败'); } }; useEffect(() => { fetchDeptClassList(); }, []); // 监听selectedDept变化,加载人员数据 useEffect(() => { if (selectedDept?.key) { if (activeTab === 'personnel') { tableRef.current?.reload(); } else if (activeTab === 'checkPoints') { checkPointsTableRef.current?.reload(); } } }, [selectedDept?.key, activeTab]); // 树形数据搜索过滤函数 const filterTreeData = (data: any[], keywords: string) => { if (!keywords.trim()) { return data; } const filtered = data .map((parent) => { // 检查父节点是否匹配 const parentMatches = parent.title .toLowerCase() .includes(keywords.toLowerCase()); // 过滤子节点 const filteredChildren = parent.children.filter((child: any) => child.title.toLowerCase().includes(keywords.toLowerCase()), ); // 如果父节点匹配或有匹配的子节点,则保留该父节点 if (parentMatches || filteredChildren.length > 0) { return { ...parent, children: parentMatches ? parent.children : filteredChildren, // 如果父节点匹配,显示所有子节点;否则只显示匹配的子节点 }; } return null; }) .filter(Boolean); // 移除 null 值 return filtered; }; // 处理树形搜索 const handleTreeSearch = (value: string) => { setTreeSearchKeywords(value); const filtered = filterTreeData(treeData, value); setFilteredTreeData(filtered); // 如果有搜索关键词,自动展开所有匹配的父节点 if (value.trim()) { const expandKeys = filtered.map((item: any) => item.key); setExpandedKeys(expandKeys); } else { // 如果清空搜索,恢复默认展开状态 setExpandedKeys(treeData.map((item: any) => item.key)); } }; // 获取人员数据 const getPersonnelData = async (params: any) => { try { // 如果没有选中的部门,返回空数据 if (!selectedDept?.key) { return { data: [], success: true, total: 0, }; } // 调用真实API获取人员列表(不传filter参数,获取全部数据) const res = await getDeptEmployee({ deptId: Number(selectedDept.key), // 当前选中的查核单位ID filter: '', // 不传搜索关键词,获取全部数据 deptType: selectedDept.deptType ?? 0, // 单位类型,使用空值合并操作符,默认为0 }); // 尝试多种数据结构格式 let dataArray: any[] | null = null; if (res) { if (Array.isArray(res.data)) { dataArray = res.data; // { data: [...] } } else if (Array.isArray(res.items)) { dataArray = res.items; // { items: [...] } } else if (res.data && Array.isArray(res.data.items)) { dataArray = res.data.items; // { data: { items: [...] } } } else if (Array.isArray(res)) { dataArray = res; // 直接返回数组 } } if (dataArray && dataArray.length >= 0) { // 保留接口字段,不做映射,仅把 id 转成字符串,避免 rowKey 警告 let formattedData = dataArray.map((item: any) => ({ ...item, id: String(item.id), })); // 前端本地搜索过滤 if (searchKeywords && searchKeywords.trim()) { formattedData = formattedData.filter((item: any) => (item.empCode && item.empCode.includes(searchKeywords)) || (item.empName && item.empName.includes(searchKeywords)) ); } return { data: formattedData, success: true, total: formattedData.length, }; } return { data: [], success: true, total: 0, }; } catch (error) { console.error('获取人员列表失败:', error); message.error('获取人员列表失败'); return { data: [], success: false, total: 0, }; } }; // 查核条件表格列定义 const conditionColumns: ProColumns[] = [ { title: '条件名称', dataIndex: 'name', width: 200, align: 'left' as 'left', }, { title: '说明', dataIndex: 'description', align: 'left' as 'left', ellipsis: true, }, ]; // 人员表格列定义 const [selectedPersonnelKeys, setSelectedPersonnelKeys] = useState( [], ); const personnelColumns: ProColumns[] = [ { title: '工号', dataIndex: 'empCode', width: 120, align: 'center', }, { title: '姓名', dataIndex: 'empName', width: 120, align: 'center', }, { title: '负责人', dataIndex: 'isManager', width: 120, align: 'center', render: (_, record) => { return ( handleSetManager(record)} /> ); }, }, { title: '操作', key: 'option', width: 100, align: 'center', render: (_, record) => { return ( handleDeletePersonnel(record)} > 删除 ); }, }, ]; // 处理科室选择 const handleSelectDept = (dept: string) => { setSelectedDept(dept); }; // 处理部门展开/收起 const toggleExpand = (dept: string) => { if (expandedKeys.includes(dept)) { setExpandedKeys(expandedKeys.filter((key) => key !== dept)); } else { setExpandedKeys([...expandedKeys, dept]); } }; // 人员选择相关状态 const [employeeSearchKeyword, setEmployeeSearchKeyword] = useState(''); const [selectedEmployees, setSelectedEmployees] = useState([]); const [employeeModalVisible, setEmployeeModalVisible] = useState(false); const employeeTableRef = useRef(); // 查核要点相关状态 const [checkPointsSearchKeyword, setCheckPointsSearchKeyword] = useState(''); const checkPointsTableRef = useRef(); // 处理添加人员 const handleAddPersonnel = () => { // 如果没有选择单位,提示先选择单位 if (!selectedDept?.key) { message.warning('请先选择一个查核单位'); return; } // 重置状态 setEmployeeSearchKeyword(''); setSelectedEmployees([]); // 打开选择人员弹窗 setEmployeeModalVisible(true); // 延迟触发表格重新加载,确保弹窗打开后重新请求数据 setTimeout(() => { employeeTableRef.current?.reload(); }, 100); }; // 获取选择人员列表的数据 const getEmployeeSelectData = async (params: any) => { try { // 如果没有选中的部门,返回空数据 if (!selectedDept?.key) { return { data: [], success: true, total: 0, }; } // 调用真实API获取可选人员列表 const res = await getDeptPendingEmployee({ deptId: Number(selectedDept.key), // 当前选中的查核单位ID filter: employeeSearchKeyword || '', // 搜索关键词 deptType: selectedDept.deptType ?? 0, // 单位类型,使用空值合并操作符,默认为0 }); if (res && res.data) { // 尝试多种数据结构格式 let dataArray = null; if (Array.isArray(res.data)) { // 格式1: { data: [...] } dataArray = res.data; } else if (res.data.items && Array.isArray(res.data.items)) { // 格式2: { data: { items: [...] } } dataArray = res.data.items; } else if (res.data.list && Array.isArray(res.data.list)) { // 格式3: { data: { list: [...] } } dataArray = res.data.list; } if (dataArray && dataArray.length >= 0) { // 格式化返回的数据 const formattedData = dataArray.map((item: any) => ({ id: String(item.id || item.empId), // 人员ID empCode: item.code || item.empCode, // 人员工号 empName: item.name || item.empName, // 人员姓名 userId: item.userId, // 人员中台ID })); return { data: formattedData, success: true, total: formattedData.length, }; } } else if (Array.isArray(res)) { // 格式4: 直接返回数组 const formattedData = res.map((item: any) => ({ id: String(item.id || item.empId), // 人员ID empCode: item.code || item.empCode, // 人员工号 empName: item.name || item.empName, // 人员姓名 userId: item.userId, // 人员中台ID })); return { data: formattedData, success: true, total: formattedData.length, }; } return { data: [], success: true, total: 0, }; } catch (error) { console.error('获取可选人员列表失败:', error); message.error('获取可选人员列表失败'); return { data: [], success: false, total: 0, }; } }; // 处理员工选择 const handleEmployeeSelect = (employeeId: string, checked: boolean) => { if (checked) { setSelectedEmployees([...selectedEmployees, employeeId]); } else { setSelectedEmployees(selectedEmployees.filter((id) => id !== employeeId)); } }; // 确认添加选择的人员 const handleConfirmAddEmployees = async () => { if (selectedEmployees.length === 0) { message.warning('请至少选择一名人员'); return; } try { // 调用真实API保存添加的人员 const res = await addDeptEmployee({ id: selectedDept.key, // 查核单位ID type: String(selectedDept.deptType ?? 0), // 单位类型:0普通单位1职能科室 mapIdList: selectedEmployees, // 人员ID列表 }); if (res) { message.success(`已添加 ${selectedEmployees.length} 名人员`); setEmployeeModalVisible(false); // 重置选择状态 setSelectedEmployees([]); // 刷新人员列表 tableRef.current?.reload(); } else { message.error('添加人员失败'); } } catch (error) { console.error('添加人员失败:', error); message.error('添加人员失败'); } }; // 处理搜索 const handleSearch = (value: string) => { setSearchKeywords(value); // 使用新接口时,不需要本地过滤,直接重新请求API tableRef.current?.reload(); }; // 处理标签页切换 const handleTabChange = (key: string) => { setActiveTab(key); // 切换tab时清空相关选择状态 if (key !== 'personnel') { setSelectedPersonnelKeys([]); } if (key !== 'checkPoints') { setSelectedCheckPointKeys([]); } }; // 获取查核要点数据 const getCheckPointsData = async (params: any) => { try { // 如果没有选中的部门,返回空数据 if (!selectedDept?.key) { return { data: [], success: true, total: 0, }; } // 模拟数据,后续替换为真实API调用 const mockData = [ { id: '1', pointName: '麻醉科管理规程', checkItemName: '审阅麻醉前评估记录内容完整、(10分)', rowSpan: 4, // 麻醉科管理规程有4行 }, { id: '2', pointName: '麻醉科管理规程', checkItemName: '审阅麻醉记录单内手术时间与用药记录(10分)', rowSpan: 0, // 合并到上一行 }, { id: '3', pointName: '麻醉科管理规程', checkItemName: '审阅麻醉前评估记录内容完整、(10分)', rowSpan: 0, // 合并到上一行 }, { id: '4', pointName: '麻醉科管理规程', checkItemName: '审阅麻醉记录单内手术时间与用药记录(10分)', rowSpan: 0, // 合并到上一行 }, { id: '5', pointName: '药品管理规程', checkItemName: '审阅麻醉前评估记录内容完整、(10分)', rowSpan: 2, // 药品管理规程有2行 }, { id: '6', pointName: '药品管理规程', checkItemName: '审阅麻醉记录单内手术时间与用药记录(10分)', rowSpan: 0, // 合并到上一行 }, { id: '7', pointName: '急救药品管理规程', checkItemName: '如果发现冰箱温度不正常,应该如何处理?(15分)', rowSpan: 1, // 急救药品管理规程有1行 }, ]; // 根据搜索关键词过滤数据 const filteredData = checkPointsSearchKeyword ? mockData.filter( (item) => item.pointName.includes(checkPointsSearchKeyword) || item.checkItemName.includes(checkPointsSearchKeyword), ) : mockData; return { data: filteredData, success: true, total: filteredData.length, }; } catch (error) { console.error('获取查核要点失败:', error); message.error('获取查核要点失败'); return { data: [], success: false, total: 0, }; } }; // 处理查核要点搜索 const handleSearchCheckPoints = (value: string) => { setCheckPointsSearchKeyword(value); checkPointsTableRef.current?.reload(); }; // 处理添加查核要点 const handleAddCheckPoint = () => { if (!selectedDept?.key) { message.warning('请先选择一个查核单位'); return; } // TODO: 实现添加查核要点的弹窗 message.info('添加查核要点功能待实现'); }; // 处理删除查核要点 const handleDeleteCheckPoint = async (record: any) => { try { await delDeptCheckpoint([record.id]); message.success('删除要点成功'); checkPointsTableRef.current?.reload(); } catch (e) { message.error('删除要点失败'); } }; // 获取查核条件列表 - 用于KCIMTable const getConditionData = async (params: any) => { try { const { current = 1, pageSize = 10 } = params; // 调用真实API获取条件列表(不传filter,获取所有数据) const requestParams = { deptClassId: currentNode?.key, // 当前选中的部门分类ID conditionType: conditionSearchType === '个案' ? 0 : 1, // 条件类型:0个案1系统 filter: '', // 不传搜索关键词,获取所有数据 }; const res = await getDeptClassFilterConditionList(requestParams); if (res && res.itemList) { // 直接从res中获取数据 const { itemList = [], mapIdList = [] } = res; // 更新已选择的条件ID列表 - 确保ID类型一致 const selectedIds = (mapIdList || []).map((id: any) => String(id)); setSelectedConditions(selectedIds); // 转换数据格式,保持树形结构 const transformConditions = (conditions: any[]): any[] => { return conditions.map((item: any) => { const transformed: any = { id: String(item.id), // 确保ID为字符串类型 name: item.name, description: item.description || '暂无说明', }; // 如果有子条件,递归处理 if ( item.children && Array.isArray(item.children) && item.children.length > 0 ) { transformed.children = transformConditions(item.children); } return transformed; }); }; // 保持树形结构的条件数据 const allConditions = transformConditions(itemList); // 递归搜索树形数据 const searchInTree = (data: any[], keyword: string): any[] => { if (!keyword.trim()) return data; const result: any[] = []; data.forEach((item) => { // 检查当前节点是否匹配 const currentMatches = item.name.includes(keyword) || item.description.includes(keyword); // 递归搜索子节点 let filteredChildren: any[] = []; if (item.children && Array.isArray(item.children)) { filteredChildren = searchInTree(item.children, keyword); } // 如果当前节点匹配或有匹配的子节点,则保留 if (currentMatches || filteredChildren.length > 0) { const newItem = { ...item }; if (filteredChildren.length > 0) { newItem.children = filteredChildren; } else if (!currentMatches) { // 如果当前节点不匹配但有匹配的子节点,保留原有children // 这种情况在上面的逻辑中已经处理了 } result.push(newItem); } }); return result; }; // 根据搜索关键词过滤(本地搜索) const filteredData = searchInTree( allConditions, conditionSearchKeyword, ); // 前端分页处理 const startIndex = (current - 1) * pageSize; const endIndex = startIndex + pageSize; const pageData = filteredData.slice(startIndex, endIndex); return { data: pageData, success: true, total: filteredData.length, }; } else { return { data: [], success: true, total: 0, }; } } catch (error) { console.error('进入了catch块,获取条件列表失败:', error); message.error('获取条件列表失败'); return { data: [], success: false, total: 0, }; } }; // 打开条件选择弹窗 const handleOpenConditionModal = (node: any) => { setCurrentNode(node); setConditionModalVisible(true); setSelectedConditions([]); // 重置选择,实际选择状态会在API调用后更新 setConditionSearchKeyword(''); // 重置搜索关键词 setConditionSearchType('个案'); // 重置搜索类型 // 延迟触发表格重新加载,确保弹窗打开后重新请求数据 setTimeout(() => { conditionTableRef.current?.reload(); }, 100); }; // 处理条件选择 const handleConditionSelect = (conditionId: string, checked: boolean) => { if (checked) { setSelectedConditions([...selectedConditions, conditionId]); } else { setSelectedConditions( selectedConditions.filter((id) => id !== conditionId), ); } }; // 处理级联选择(父子关联) const handleConditionSelectWithCascade = (record: any, selected: boolean) => { // 获取当前记录及其所有子记录的ID const getAllIds = (item: any): string[] => { const ids = [item.id]; if ( item.children && Array.isArray(item.children) && item.children.length > 0 ) { item.children.forEach((child: any) => { ids.push(...getAllIds(child)); }); } return ids; }; const allIds = getAllIds(record); if (selected) { // 选中:添加当前记录及其所有子记录 const newSelectedIds = [...new Set([...selectedConditions, ...allIds])]; setSelectedConditions(newSelectedIds); } else { // 取消选中:移除当前记录及其所有子记录 const newSelectedIds = selectedConditions.filter( (id) => !allIds.includes(id), ); setSelectedConditions(newSelectedIds); } }; // 确认选择条件 const handleConfirmConditions = async () => { try { // 调用保存接口 const res = await applyDeptClassFilterCondition({ id: currentNode?.key, // 单位分类ID mapIdList: selectedConditions, // 查核条件ID列表 }); if (res) { message.success( `已为 ${currentNode?.title} 保存了 ${selectedConditions.length} 个查核条件`, ); setConditionModalVisible(false); // 重新获取列表,更新条件信息 await fetchDeptClassList(); // 查询选中的条件名称 if (res.items && Array.isArray(res.items)) { // 如果API返回了条件列表,可以直接从返回值获取条件名称 const conditionNames = res.items .map((item: any) => item.name) .join('、'); // 更新treeData中对应节点的conditions属性 const updatedTreeData = treeData.map((item: any) => { if (item.key === currentNode?.key) { return { ...item, conditions: conditionNames || '手术室、重症加护病房、医学影像', }; } return item; }); setTreeData(updatedTreeData); setFilteredTreeData(updatedTreeData); } } else { message.error('保存查核条件失败'); } } catch (error) { console.error('保存查核条件失败:', error); message.error('保存查核条件失败'); } }; // 处理删除节点 const handleDeleteNode = (node: any, isParent: boolean) => { Modal.confirm({ title: '确认删除', content: `确定要删除${isParent ? '单位分类' : '查核单位'} "${node.title }" 吗?`, okText: '确定', cancelText: '取消', onOk: async () => { try { if (isParent) { // 删除单位分类 const res = await delDeptClass([Number(node.key)]); if (res) { message.success('删除单位分类成功'); // 重新获取列表数据 await fetchDeptClassList(); } else { message.error('删除单位分类失败'); } } else { // 删除查核单位 const res = await delDepartmentByClass({ deptId: Number(node.key), // 查核单位ID deptType: node.deptType ?? 0, // 单位类型:0普通单位 1职能科室 }); if (res) { message.success('删除查核单位成功'); // 重新获取列表数据 await fetchDeptClassList(); } else { message.error('删除查核单位失败'); } } } catch (error) { message.error('删除失败'); } }, }); }; // 自定义空状态 const customizeRenderEmpty = () => (
); // 生成更多按钮的菜单 const getMoreMenu = ( node: any, isParent: boolean = false, parentNode?: any, ) => { const menuItems = []; if (isParent) { // 父级节点菜单:新增单位、条件、修改、删除 menuItems.push( 新增单位} width={352} onFinish={async (values) => { try { const { name, sort, deptType } = values; const res = await addDepartmentByClass({ deptClassId: node.key, // 使用当前选中的单位分类ID name, sort, deptType, // 使用表单中用户选择的职能科室值 }); if (res) { message.success('新增单位成功'); // 重新获取列表数据 await fetchDeptClassList(); return true; } else { message.error('新增单位失败'); return false; } } catch (error) { message.error('新增单位失败'); return false; } }} modalProps={{ destroyOnClose: true, }} > , handleOpenConditionModal(node)} > 条件 , ); } // 公共菜单项:修改、删除 menuItems.push( 修改} width={352} initialValues={{ name: node.title, depType: node.visitType || 1, sort: node.sort || 0, deptType: node.deptType ?? 0, // 单位类型:0普通单位 1职能科室 }} onFinish={async (values) => { try { if (isParent) { // 修改单位分类 const { name, depType } = values; const res = await updateDeptClass({ id: Number(node.key), // 单位分类ID name, depType, // 门急诊类型:1-门诊 2-急诊 }); if (res) { message.success('修改单位分类成功'); } else { message.error('修改单位分类失败'); return false; } } else { // 修改查核单位 const { name, sort, deptType } = values; const res = await updateDepartmentByClass({ deptClassId: Number(parentNode?.key), // 单位分类ID id: Number(node.key), // 查核单位ID name, sort, deptType, // 使用表单中用户选择的职能科室值 }); if (res) { message.success('修改查核单位成功'); } else { message.error('修改查核单位失败'); return false; } } // 重新获取列表数据 await fetchDeptClassList(); return true; } catch (error) { message.error('修改失败'); return false; } }} modalProps={{ destroyOnClose: true, }} > {isParent ? ( ) : ( <> )} , handleDeleteNode(node, isParent)}> 删除 , ); return {menuItems}; }; // 新增按钮的菜单 const menu = ( 新增单位分类} width={352} onFinish={async (values) => { try { const { name, depType } = values; const res = await addDeptClass({ name, depType }); if (res) { message.success('新增单位分类成功'); // 重新获取列表数据 await fetchDeptClassList(); return true; } else { message.error('新增失败'); return false; } } catch (error) { message.error('新增失败'); return false; } }} modalProps={{ destroyOnClose: true, }} > 导入系统追踪} width={600} onFinish={async (values) => { try { let token = ''; const userDataStr = localStorage.getItem('userData'); if (userDataStr) { try { token = JSON.parse(userDataStr).token || ''; } catch (e) { token = ''; } } const formData = new FormData(); formData.append('type', '1'); if (values.systemTrackingFile && values.systemTrackingFile[0]?.originFileObj) { formData.append('file', values.systemTrackingFile[0].originFileObj); } else { message.error('请上传文件'); return false; } const res = await fetch('/gateway/manager/Department/importPfmKnowledge', { method: 'POST', headers: { token: token, }, body: formData, }); let result = null; try { result = await res.json(); } catch (e) { message.error('导入失败'); return false; } if (result && result.status === 200) { message.success('导入成功'); // 重新获取列表数据,刷新左侧树形结构 await fetchDeptClassList(); return true; } else { message.error(result.errorMessage || '导入失败'); return false; } } catch (error) { message.error('导入失败'); return false; } }} modalProps={{ destroyOnClose: true, }} >
文件上传
} title="点击或将文件拖拽到这里上传" description="支持扩展名:.rar .zip .doc .docx .pdf .jpg..." fieldProps={{ name: 'file', multiple: false, accept: '.rar,.zip,.doc,.docx,.pdf,.jpg,.jpeg,.png,.xls,.xlsx', beforeUpload: () => false, // 阻止自动上传 }} rules={[{ required: true, message: '请上传文件' }]} />
{/* 导入自查督查} width={600} onFinish={async (values) => { try { let token = ''; const userDataStr = localStorage.getItem('userData'); if (userDataStr) { try { token = JSON.parse(userDataStr).token || ''; } catch (e) { token = ''; } } const formData = new FormData(); formData.append('type', '2'); if (values.selfInspectionFile && values.selfInspectionFile[0]?.originFileObj) { formData.append('file', values.selfInspectionFile[0].originFileObj); } else { message.error('请上传文件'); return false; } const res = await fetch('/manager/Department/importPfmKnowledge', { method: 'POST', headers: { token: token, }, body: formData, }); let result = null; try { result = await res.json(); } catch (e) { message.error('导入失败'); return false; } if (result && result.status === 200) { message.success('导入成功'); return true; } else { message.error(result.errorMessage || '导入失败'); return false; } } catch (error) { message.error('导入失败'); return false; } }} modalProps={{ destroyOnClose: true, }} >
文件上传
} title="点击或将文件拖拽到这里上传" description="支持扩展名:.rar .zip .doc .docx .pdf .jpg..." fieldProps={{ name: 'file', multiple: false, accept: '.rar,.zip,.doc,.docx,.pdf,.jpg,.jpeg,.png,.xls,.xlsx', beforeUpload: () => false, // 阻止自动上传 }} rules={[{ required: true, message: '请上传文件' }]} />
*/}
); // 渲染左侧部门树 const renderDeptTree = () => { return (
} className="search-input" value={treeSearchKeywords} onChange={(e) => handleTreeSearch(e.target.value)} allowClear />
{filteredTreeData.map((parent) => { // 判断是否有子项 const hasChildren = parent.children && parent.children.length > 0; return (
{hasChildren ? (
toggleExpand(parent.key)} onMouseEnter={() => setHoveredNode(parent.key)} onMouseLeave={() => setHoveredNode(null)} >
) : (
{ // 设置分类自身的信息 setSelectedDept({ ...parent, parentTitle: '单位分类', // 保留原始的filterConditionList数据 filterConditionList: parent.filterConditionList || [], }); // 不需要手动重新加载,useEffect会监听selectedDept变化 }} onMouseEnter={() => setHoveredNode(parent.key)} onMouseLeave={() => setHoveredNode(null)} > {parent.title} {(selectedDept?.key === parent.key || hoveredNode === parent.key || openDropdownNode === parent.key) && ( { if (visible) { setOpenDropdownNode(parent.key); } else { setOpenDropdownNode(null); } }} >
)} {hasChildren && expandedKeys.includes(parent.key) && (
{parent.children.map((child: any) => (
{ // 添加父节点信息,保留原始查核条件数据 setSelectedDept({ ...child, parentTitle: parent.title, // 保留原始的filterConditionList数据 filterConditionList: child.filterConditionList || [], }); // 不需要手动重新加载,useEffect会监听selectedDept变化 }} onMouseEnter={() => setHoveredNode(child.key)} onMouseLeave={() => setHoveredNode(null)} > {child.title} {child.deptType === 1 && ( )} {(selectedDept?.key === child.key || hoveredNode === child.key || openDropdownNode === child.key) && ( { if (visible) { setOpenDropdownNode(child.key); } else { setOpenDropdownNode(null); } }} >
))}
)}
); })}
); }; // 处理设置负责人 const [settingManagerId, setSettingManagerId] = useState(''); const handleSetManager = async (record: any) => { if (record.isManager) return; // 已是负责人 setSettingManagerId(record.id); try { await setDeptManager({ empId: Number(record.empId || record.id), empName: record.empName, deptId: Number(selectedDept.key), deptType: selectedDept.deptType ?? 0, // 使用空值合并操作符,默认为0 }); message.success(`已设置 ${record.empName} 为负责人`); tableRef.current?.reload(); } catch (e) { message.error('设置负责人失败'); } finally { setSettingManagerId(''); } }; const handleDeletePersonnel = async (record: any) => { try { await delDeptEmp([ { id: String(record.id), type: String(selectedDept.deptType ?? 0) }, ]); message.success('删除成功'); tableRef.current?.reload(); } catch (e) { message.error('删除失败'); } }; // Tab下table专用数据获取方法 const getDeptCheckpointData = async (params: any) => { try { if (!selectedDept?.key) { return { data: [], success: true, total: 0 }; } // 不传搜索关键词,获取全部数据 const res = await getDeptCheckpoint({ deptId: selectedDept.key, filter: '', // 获取全部数据 deptType: selectedDept.deptType ?? 0, }); // 这里根据接口返回结构处理数据 if (Array.isArray(res)) { const flatData: any[] = []; res.forEach((point: any) => { if ( Array.isArray(point.checkItemList) && point.checkItemList.length > 0 ) { point.checkItemList.forEach((item: any, idx: number) => { flatData.push({ id: idx === 0 ? String(point.id) : `${point.id}_item_${idx}`, checkPointName: point.checkPointName, // 直接用接口字段 checkItemName: item.name, rowSpan: idx === 0 ? point.checkItemList.length : 0, isFirst: idx === 0, }); }); } else { flatData.push({ id: String(point.id), checkPointName: point.checkPointName, // 直接用接口字段 checkItemName: '-', rowSpan: 1, isFirst: true, }); } }); // 前端本地搜索过滤 let filteredData = flatData; if (checkPointsSearchKeyword && checkPointsSearchKeyword.trim()) { filteredData = flatData.filter((item: any) => (item.checkPointName && item.checkPointName.includes(checkPointsSearchKeyword)) || (item.checkItemName && item.checkItemName.includes(checkPointsSearchKeyword)) ); } // 更新当前页数据用于全选逻辑 setCurrentCheckPointPageData(filteredData); return { data: filteredData, success: true, total: filteredData.length, }; } return { data: [], success: true, total: 0 }; } catch (e) { return { data: [], success: false, total: 0 }; } }; // Tab页columns const checkPointTabColumns = [ { title: ( 0 && selectedCheckPointKeys.length > 0 && selectedCheckPointKeys.length < getCurrentPageCheckPointIds().length } checked={ getCurrentPageCheckPointIds().length > 0 && selectedCheckPointKeys.length === getCurrentPageCheckPointIds().length } onChange={(e) => { const ids = getCurrentPageCheckPointIds(); if (e.target.checked) { setSelectedCheckPointKeys(Array.from(new Set([...selectedCheckPointKeys, ...ids]))); } else { setSelectedCheckPointKeys(selectedCheckPointKeys.filter((id) => !ids.includes(id))); } }} > 要点名称 ), dataIndex: 'checkPointName', width: 200, align: 'left' as 'left', render: (dom: React.ReactNode, record: any) => { if (record.rowSpan > 0) { return { children: (
{ if (selectedCheckPointKeys.includes(record.id)) { setSelectedCheckPointKeys( selectedCheckPointKeys.filter((k) => k !== record.id), ); } else { setSelectedCheckPointKeys([ ...selectedCheckPointKeys, record.id, ]); } }} /> {dom}
), props: { rowSpan: record.rowSpan }, }; } return { children: null, props: { rowSpan: 0 } }; }, }, { title: '查核项名称', dataIndex: 'checkItemName', align: 'left' as 'left', }, { title: '操作', dataIndex: 'option', width: 80, align: 'center' as 'center', render: (_: any, record: any) => { if (record.rowSpan > 0) { return { children: ( handleDeleteCheckPoint(record)} > 删除 ), props: { rowSpan: record.rowSpan }, }; } return { children: null, props: { rowSpan: 0 } }; }, }, ]; // 4. 打开弹窗方法 const handleOpenCheckPointModal = () => { setCheckPointModalVisible(true); setSelectedCheckPointKeys([]); setTimeout(() => { checkPointTableRef?.reload?.(); }, 100); }; // 选择查核要点弹窗表格数据获取方法 const getDeptPendingCheckpointData = async (params: any) => { try { if (!selectedDept?.key) { return { data: [], success: true, total: 0 }; } const res = await getDeptPendingCheckpoint({ deptId: selectedDept.key, filter: '', deptType: selectedDept.deptType ?? 0, }); if (Array.isArray(res)) { const flatData: any[] = []; res.forEach((point: any) => { if ( Array.isArray(point.checkItemList) && point.checkItemList.length > 0 ) { point.checkItemList.forEach((item: any, idx: number) => { flatData.push({ id: idx === 0 ? String(point.id) : `${point.id}_item_${idx}`, name: point.name, checkItemName: item.name, rowSpan: idx === 0 ? point.checkItemList.length : 0, isFirst: idx === 0, }); }); } else { flatData.push({ id: String(point.id), name: point.name, checkItemName: '-', rowSpan: 1, isFirst: true, }); } }); return { data: flatData, success: true, total: flatData.length, }; } return { data: [], success: true, total: 0 }; } catch (e) { return { data: [], success: false, total: 0 }; } }; // 选择查核要点弹窗"确定"按钮逻辑 const handleConfirmAddCheckPoints = async () => { if (!selectedDept?.key) { message.warning('请先选择查核单位'); return; } if (selectedCheckPointKeys.length === 0) { message.warning('请至少选择一个查核要点'); return; } try { await addDeptCheckpoint({ id: String(selectedDept.key), type: String(selectedDept.deptType ?? 0), mapIdList: selectedCheckPointKeys, }); message.success('添加查核要点成功'); setCheckPointModalVisible(false); // 可选:刷新Tab下表格 checkPointsTableRef.current?.reload(); } catch (e) { message.error('添加查核要点失败'); } }; // 弹窗columns const checkPointModalColumns = [ { title: '要点名称', dataIndex: 'name', width: 200, align: 'left' as 'left', render: (dom: React.ReactNode, record: any) => { if (record.rowSpan > 0) { return { children: (
{ if (selectedCheckPointKeys.includes(record.id)) { setSelectedCheckPointKeys( selectedCheckPointKeys.filter((k) => k !== record.id), ); } else { setSelectedCheckPointKeys([ ...selectedCheckPointKeys, record.id, ]); } }} /> {dom}
), props: { rowSpan: record.rowSpan }, }; } return { children: null, props: { rowSpan: 0 } }; }, }, { title: '查核项名称', dataIndex: 'checkItemName', align: 'left' as 'left', }, ]; return (
{/* 左侧科室树 */} {renderDeptTree()} {/* 右侧内容区 */}
0) || (activeTab === 'checkPoints' && selectedCheckPointKeys.length > 0) ? 68 : 0 }}>
{selectedDept && ( <> {selectedDept.title} 单位分类:{selectedDept.parentTitle || '暂无'}   分类查核条件: {selectedDept.filterConditionList ? selectedDept.filterConditionList .map((item: any) => item.name) .join('、') : '手术室、重症加护病房、医学影像'} )}
handleSearch(personnelSearchInput)} style={{ cursor: 'pointer', color: '#99A6BF' }} /> } style={{ width: 250 }} onChange={(e) => { setPersonnelSearchInput(e.target.value); if (e.target.value === '') { setSearchKeywords(''); tableRef.current?.reload(); } }} onPressEnter={(e) => handleSearch(e.currentTarget.value)} />
setSelectedPersonnelKeys(keys as string[]), }} locale={{ emptyText: customizeRenderEmpty() }} pagination={false} tableAlertRender={false} />
handleSearchCheckPoints(checkPointsSearchKeyword)} style={{ cursor: 'pointer', color: '#99A6BF' }} /> } style={{ width: 250 }} value={checkPointsSearchKeyword} onChange={(e) => { setCheckPointsSearchKeyword(e.target.value); if (e.target.value === '') { // 清空搜索时自动刷新 checkPointsTableRef.current?.reload(); } }} onPressEnter={(e) => handleSearchCheckPoints(e.currentTarget.value) } />
{/* 批量操作栏 - 固定在页面底部,参考empMana样式 */} {activeTab === 'personnel' && selectedPersonnelKeys.length > 0 && (
已选择 {selectedPersonnelKeys.length} 项,将进行批量删除
{ setSelectedPersonnelKeys([]); }} style={{ cursor: 'pointer', display: 'inline-block', fontSize: 14, fontFamily: 'SourceHanSansCN-Normal, SourceHanSansCN', fontWeight: 400, color: '#666666', lineHeight: '24px', padding: '0 14px', background: '#ffffff', border: '1px solid #dae2f2', borderRadius: 4, }} > 取消选择
{ try { await delDeptEmp( selectedPersonnelKeys.map((id) => ({ id: String(id), type: String(selectedDept.deptType ?? 0), })), ); message.success('批量删除成功'); setSelectedPersonnelKeys([]); tableRef.current?.reload(); } catch (e) { message.error('批量删除失败'); } }} >
批量删除
)} {activeTab === 'checkPoints' && !checkPointModalVisible && selectedCheckPointKeys.length > 0 && (
已选择 {selectedCheckPointKeys.length} 项,将进行批量删除
{ setSelectedCheckPointKeys([]); }} style={{ cursor: 'pointer', display: 'inline-block', fontSize: 14, fontFamily: 'SourceHanSansCN-Normal, SourceHanSansCN', fontWeight: 400, color: '#666666', lineHeight: '24px', padding: '0 14px', background: '#ffffff', border: '1px solid #dae2f2', borderRadius: 4, }} > 取消选择
{ try { await delDeptCheckpoint(selectedCheckPointKeys); message.success('批量删除成功'); setSelectedCheckPointKeys([]); checkPointsTableRef.current?.reload(); } catch (e) { message.error('批量删除失败'); } }} >
批量删除
)} {/* 查核条件选择弹窗 */} setConditionModalVisible(false)} width={532} footer={[ , , ]} >
{ setConditionSearchKeyword(e.target.value); // 实时搜索,触发表格重新渲染 conditionTableRef.current?.reload(); }} onPressEnter={() => conditionTableRef.current?.reload()} suffix={ conditionTableRef.current?.reload()} style={{ cursor: 'pointer', color: '#99A6BF' }} /> } style={{ flex: 1 }} />
null, }} rowSelection={{ selectedRowKeys: selectedConditions, onChange: (selectedRowKeys) => { setSelectedConditions(selectedRowKeys as string[]); }, checkStrictly: false, // 启用父子关联选择 }} tableAlertRender={false} scroll={{ y: 360 }} />
{/* 选择人员弹窗 */} setEmployeeModalVisible(false)} width={372} footer={[ , , ]} >
{ setEmployeeSearchKeyword(e.target.value); if (e.target.value === '') { // 清空搜索时自动刷新 employeeTableRef.current?.reload(); } }} onPressEnter={() => employeeTableRef.current?.reload()} suffix={ employeeTableRef.current?.reload()} style={{ cursor: 'pointer', color: '#99A6BF' }} /> } style={{ width: '100%' }} />
null, }} rowSelection={{ selectedRowKeys: selectedEmployees, onChange: (selectedRowKeys) => { setSelectedEmployees(selectedRowKeys as string[]); }, }} tableAlertRender={false} scroll={{ y: 360 }} />
{/* 选择查核要点弹窗 */} setCheckPointModalVisible(false)} width={700} footer={[ , , ]} > {/* fragment 包裹,防止JSX语法错误 */} <> null, }} tableAlertRender={false} scroll={{ y: 360 }} />
); }