|
|
@@ -0,0 +1,2740 @@
|
|
|
+/*
|
|
|
+ * @Author: code4eat awesomedema@gmail.com
|
|
|
+ * @Date: 2024-01-01 10:00:00
|
|
|
+ * @LastEditors: code4eat awesomedema@gmail.com
|
|
|
+ * @LastEditTime: 2025-07-25 15:25:35
|
|
|
+ * @FilePath: /pfmBackMana/src/pages/checkItemMana/index.tsx
|
|
|
+ * @Description: 查核项管理页面
|
|
|
+ */
|
|
|
+
|
|
|
+import { createFromIconfontCN } from '@ant-design/icons';
|
|
|
+import {
|
|
|
+ ActionType,
|
|
|
+ ProFormInstance,
|
|
|
+ ModalForm,
|
|
|
+ ProFormDigit,
|
|
|
+ ProFormText,
|
|
|
+ ProFormTextArea,
|
|
|
+ ProFormSelect,
|
|
|
+ ProFormRadio,
|
|
|
+ ProColumns,
|
|
|
+ ProFormUploadButton,
|
|
|
+} from '@ant-design/pro-components';
|
|
|
+import {
|
|
|
+ Input,
|
|
|
+ message,
|
|
|
+ Popconfirm,
|
|
|
+ Tree,
|
|
|
+ Button,
|
|
|
+ Dropdown,
|
|
|
+ Menu,
|
|
|
+ Tooltip,
|
|
|
+ Modal,
|
|
|
+ Tabs,
|
|
|
+ Card,
|
|
|
+ Radio,
|
|
|
+ Checkbox,
|
|
|
+ Pagination,
|
|
|
+} from 'antd';
|
|
|
+import { useEffect, useRef, useState } from 'react';
|
|
|
+import { SearchOutlined, MoreOutlined, PlusOutlined } from '@ant-design/icons';
|
|
|
+
|
|
|
+const { TabPane } = Tabs;
|
|
|
+
|
|
|
+import {
|
|
|
+ addData,
|
|
|
+ delData,
|
|
|
+ editData,
|
|
|
+ getData,
|
|
|
+ getDefectData,
|
|
|
+ addDefectData,
|
|
|
+ editDefectData,
|
|
|
+ delDefectData,
|
|
|
+ getCheckResultData,
|
|
|
+ addCheckResultData,
|
|
|
+ editCheckResultData,
|
|
|
+ delCheckResultData,
|
|
|
+ getCheckItemTree,
|
|
|
+ getCheckItemModeList,
|
|
|
+ copyCheckItemResult,
|
|
|
+ getCheckPointData,
|
|
|
+ addCheckPointData,
|
|
|
+ editCheckPointData,
|
|
|
+ delCheckPointData,
|
|
|
+ getItemPendingPageCheckpoint,
|
|
|
+ addCheckItemCheckpoint,
|
|
|
+} from './service';
|
|
|
+import './style.less';
|
|
|
+
|
|
|
+import KCIMPagecontainer from '@/components/KCIMPageContainer';
|
|
|
+import { KCIMTable } from '@/components/KCIMTable';
|
|
|
+import baobiaotubiaoIcon from '@/assets/images/icons/baobiaotubiao.png';
|
|
|
+import leixingIcon from '@/assets/images/icons/leixing.png';
|
|
|
+import fenzhiIcon from '@/assets/images/icons/fenzhi.png';
|
|
|
+import chaheIcon from '@/assets/images/icons/chahe.png';
|
|
|
+import tongjiIcon from '@/assets/images/icons/tongji.png';
|
|
|
+import guanbiIcon from '@/assets/images/icons/guanbi.png';
|
|
|
+import shangyiyeIcon from '@/assets/images/icons/shangyiye.png';
|
|
|
+import xiayiyeIcon from '@/assets/images/icons/xiayiye.png';
|
|
|
+
|
|
|
+const { Search } = Input;
|
|
|
+
|
|
|
+const IconFont = createFromIconfontCN({
|
|
|
+ scriptUrl: '',
|
|
|
+});
|
|
|
+
|
|
|
+type TableDataResponse = {
|
|
|
+ data: any[];
|
|
|
+ success: boolean;
|
|
|
+ total: number;
|
|
|
+ pageSize: number;
|
|
|
+ totalPage: number;
|
|
|
+};
|
|
|
+
|
|
|
+// 模拟树形数据
|
|
|
+const mockTreeData = [
|
|
|
+ {
|
|
|
+ key: '1',
|
|
|
+ title: '地面干净无水迹',
|
|
|
+ children: [
|
|
|
+ { key: '1-1', title: '科内无专管' },
|
|
|
+ { key: '1-2', title: '科内不知晓专管员' },
|
|
|
+ { key: '1-3', title: '麻醉药品未放入保险柜' },
|
|
|
+ { key: '1-4', title: '使用PDA时扫描帐后未查看PDA各项信息是否准确' },
|
|
|
+ {
|
|
|
+ key: '1-5',
|
|
|
+ title: '对无法有效沟通的患者,未让陪同人员除述患者姓名或未查看腕带',
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: '2',
|
|
|
+ title: '防火门关情况',
|
|
|
+ children: [
|
|
|
+ { key: '2-1', title: '防火门未正常关闭' },
|
|
|
+ { key: '2-2', title: '防火门被锁死' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: '3',
|
|
|
+ title: '防烟面具、指挥棒、任意门其他应急物品情况',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: '4',
|
|
|
+ title: '疏散指示灯情况',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: '5',
|
|
|
+ title: '科室消防安全自查表',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: '6',
|
|
|
+ title: '消防情况',
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+// 模拟表格数据
|
|
|
+const mockTableData = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ name: '科内无专管',
|
|
|
+ score: 2.0,
|
|
|
+ description: '科室内未配置专门管理人员',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ name: '科内不知晓专管员',
|
|
|
+ score: 1.5,
|
|
|
+ description: '科室人员不知道专管员是谁',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ name: '麻醉药品未放入保险柜',
|
|
|
+ score: 1.0,
|
|
|
+ description: '麻醉药品未按规定放入保险柜',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ name: '使用PDA时扫描帐后未查看PDA各项信息是否准确',
|
|
|
+ score: 0.5,
|
|
|
+ description: '使用PDA时未核实信息准确性',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5,
|
|
|
+ name: '对无法有效沟通的患者,未让陪同人员除述患者姓名或未查看腕带',
|
|
|
+ score: 1.0,
|
|
|
+ description: '对沟通困难患者未按规定核实身份',
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+export default function CheckItemMana() {
|
|
|
+ const [tableDataSearchKeywords, set_tableDataSearchKeywords] =
|
|
|
+ useState<string>('');
|
|
|
+ const [checkResultSearchKeywords, set_checkResultSearchKeywords] =
|
|
|
+ useState<string>(''); // 查核结果搜索关键字
|
|
|
+ const [checkPointSearchKeywords, set_checkPointSearchKeywords] =
|
|
|
+ useState<string>(''); // 查核要点搜索关键字
|
|
|
+ const [selectedTreeNode, set_selectedTreeNode] = useState<any>(null);
|
|
|
+ const [treeData, set_treeData] = useState<any[]>(mockTreeData);
|
|
|
+ const [leftSideCheckItems, set_leftSideCheckItems] = useState<any[]>([]); // 左侧查核项列表数据
|
|
|
+ const [leftSideLoading, set_leftSideLoading] = useState<boolean>(false); // 左侧列表加载状态
|
|
|
+ const [leftSideHasMore, set_leftSideHasMore] = useState<boolean>(true); // 是否还有更多数据
|
|
|
+ const [leftSideCurrentPage, set_leftSideCurrentPage] = useState<number>(1); // 当前页码
|
|
|
+ const [expandedKeys, set_expandedKeys] = useState<(string | number)[]>([
|
|
|
+ '1',
|
|
|
+ '2',
|
|
|
+ ]);
|
|
|
+ const [checkItemSearchKeywords, set_checkItemSearchKeywords] =
|
|
|
+ useState<string>('');
|
|
|
+ const [hoveredNode, set_hoveredNode] = useState<string | null>(null);
|
|
|
+ const [openDropdownNode, set_openDropdownNode] = useState<string | null>(
|
|
|
+ null,
|
|
|
+ );
|
|
|
+ const [activeTabKey, set_activeTabKey] = useState<string>('defects'); // 当前激活的tab
|
|
|
+ const [checkModeOptions, set_checkModeOptions] = useState<any[]>([]);
|
|
|
+ const [addCheckItemVisible, set_addCheckItemVisible] =
|
|
|
+ useState<boolean>(false); // 新增查核项弹窗显示状态
|
|
|
+ const [editCheckItemVisible, set_editCheckItemVisible] =
|
|
|
+ useState<boolean>(false); // 编辑查核项弹窗显示状态
|
|
|
+ const [currentEditItem, set_currentEditItem] = useState<any>(null); // 当前编辑的查核项
|
|
|
+ const [copySettingVisible, set_copySettingVisible] = useState<boolean>(false); // 复制设置弹窗显示状态
|
|
|
+ const [selectedCopyItems, set_selectedCopyItems] = useState<string[]>([]); // 选中的复制项(多选)
|
|
|
+ const [copySearchKeywords, set_copySearchKeywords] = useState<string>(''); // 复制设置搜索关键字
|
|
|
+ const [addCheckPointVisible, set_addCheckPointVisible] = useState<boolean>(false); // 添加查核要点弹窗显示状态
|
|
|
+ const [pendingCheckPoints, set_pendingCheckPoints] = useState<any[]>([]); // 待添加的查核要点列表
|
|
|
+ const [selectedCheckPoints, set_selectedCheckPoints] = useState<string[]>([]); // 选中的查核要点ID列表
|
|
|
+ const [addCheckPointSearchKeywords, set_addCheckPointSearchKeywords] = useState<string>(''); // 添加查核要点搜索关键字
|
|
|
+ const [selectedCheckPointTableKeys, set_selectedCheckPointTableKeys] = useState<string[]>([]); // 查核要点表格选中的行
|
|
|
+ const [actionBarStyle, set_actionBarStyle] = useState<{
|
|
|
+ left: number;
|
|
|
+ width: number;
|
|
|
+ }>({ left: 0, width: 0 }); // 批量操作栏定位样式
|
|
|
+ const [imagePreviewVisible, set_imagePreviewVisible] = useState<boolean>(false); // 图片预览弹窗显示状态
|
|
|
+ const [imagePreviewIndex, set_imagePreviewIndex] = useState<number>(0); // 当前预览图片索引
|
|
|
+ const isInitializedRef = useRef<boolean>(false); // 防止重复初始化
|
|
|
+ const tableRef = useRef<ActionType>(); // 缺陷项表格引用
|
|
|
+ const checkResultTableRef = useRef<ActionType>(); // 查核结果表格引用
|
|
|
+ const checkPointTableRef = useRef<ActionType>(); // 查核要点表格引用
|
|
|
+ const copySettingTableRef = useRef<ActionType>(); // 复制设置表格引用
|
|
|
+ const modalFormRef = useRef<ProFormInstance>();
|
|
|
+ const addCheckItemFormRef = useRef<ProFormInstance>(); // 新增查核项表单引用
|
|
|
+ const leftSideListRef = useRef<HTMLDivElement>(null); // 左侧列表容器引用
|
|
|
+ const mainContentRef = useRef<HTMLDivElement>(null); // 右侧主内容区域引用
|
|
|
+ const [copySettingTableData, set_copySettingTableData] = useState<any[]>([]); // 复制设置表格数据
|
|
|
+
|
|
|
+ // 更新操作栏定位
|
|
|
+ const updateActionBarPosition = () => {
|
|
|
+ if (mainContentRef.current) {
|
|
|
+ const rect = mainContentRef.current.getBoundingClientRect();
|
|
|
+ set_actionBarStyle({ left: rect.left, width: rect.width });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化操作栏定位
|
|
|
+ useEffect(() => {
|
|
|
+ updateActionBarPosition();
|
|
|
+ window.addEventListener('resize', updateActionBarPosition);
|
|
|
+ window.addEventListener('scroll', updateActionBarPosition, true);
|
|
|
+ let resizeObserver: ResizeObserver | null = null;
|
|
|
+ if (mainContentRef.current && typeof ResizeObserver !== 'undefined') {
|
|
|
+ resizeObserver = new ResizeObserver(() => {
|
|
|
+ updateActionBarPosition();
|
|
|
+ });
|
|
|
+ resizeObserver.observe(mainContentRef.current);
|
|
|
+ }
|
|
|
+ return () => {
|
|
|
+ window.removeEventListener('resize', updateActionBarPosition);
|
|
|
+ window.removeEventListener('scroll', updateActionBarPosition, true);
|
|
|
+ if (resizeObserver && mainContentRef.current) {
|
|
|
+ resizeObserver.unobserve(mainContentRef.current);
|
|
|
+ resizeObserver.disconnect();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 初始化加载数据
|
|
|
+ useEffect(() => {
|
|
|
+ const initializeData = async () => {
|
|
|
+ // 防止重复初始化
|
|
|
+ if (isInitializedRef.current) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ isInitializedRef.current = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 加载左侧查核项列表(第一页)
|
|
|
+ await loadMoreLeftSideItems(1, false);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('初始化数据失败:', error);
|
|
|
+ // 如果初始化失败,重置标记,允许重试
|
|
|
+ isInitializedRef.current = false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ initializeData();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 监听选中项变化,刷新表格数据
|
|
|
+ useEffect(() => {
|
|
|
+ if (selectedTreeNode?.key) {
|
|
|
+ // 根据当前激活的tab决定刷新哪个表格
|
|
|
+ if (activeTabKey === 'defects' && tableRef.current) {
|
|
|
+ tableRef.current.reload();
|
|
|
+ } else if (activeTabKey === 'results' && checkResultTableRef.current) {
|
|
|
+ checkResultTableRef.current.reload();
|
|
|
+ } else if (activeTabKey === 'points' && checkPointTableRef.current) {
|
|
|
+ checkPointTableRef.current.reload();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, [selectedTreeNode?.key, activeTabKey]);
|
|
|
+
|
|
|
+ // 监听左侧列表滚动事件
|
|
|
+ useEffect(() => {
|
|
|
+ const handleScroll = () => {
|
|
|
+ if (!leftSideListRef.current || leftSideLoading || !leftSideHasMore)
|
|
|
+ return;
|
|
|
+
|
|
|
+ const { scrollTop, scrollHeight, clientHeight } = leftSideListRef.current;
|
|
|
+ // 当滚动到距离底部50px时开始加载更多
|
|
|
+ if (scrollHeight - scrollTop - clientHeight < 50) {
|
|
|
+ loadMoreLeftSideItems(leftSideCurrentPage + 1, true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const listElement = leftSideListRef.current;
|
|
|
+ if (listElement) {
|
|
|
+ listElement.addEventListener('scroll', handleScroll);
|
|
|
+ return () => {
|
|
|
+ listElement.removeEventListener('scroll', handleScroll);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }, [leftSideLoading, leftSideHasMore, leftSideCurrentPage]);
|
|
|
+
|
|
|
+ // 加载更多左侧查核项数据
|
|
|
+ const loadMoreLeftSideItems = async (
|
|
|
+ page: number = 1,
|
|
|
+ isAppend: boolean = false,
|
|
|
+ ) => {
|
|
|
+ if (leftSideLoading) return; // 防止重复加载
|
|
|
+
|
|
|
+ set_leftSideLoading(true);
|
|
|
+ try {
|
|
|
+ const response = await getData({
|
|
|
+ current: page,
|
|
|
+ pageSize: 30,
|
|
|
+ filter: checkItemSearchKeywords || '',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && response.list) {
|
|
|
+ if (isAppend) {
|
|
|
+ // 追加数据
|
|
|
+ set_leftSideCheckItems((prev) => [...prev, ...response.list]);
|
|
|
+ } else {
|
|
|
+ // 替换数据(初始加载或搜索)
|
|
|
+ set_leftSideCheckItems(response.list);
|
|
|
+ // 如果有数据,总是设置第一个为默认选中
|
|
|
+ if (response.list.length > 0) {
|
|
|
+ set_selectedTreeNode({
|
|
|
+ key: response.list[0].id,
|
|
|
+ title: response.list[0].name,
|
|
|
+ ...response.list[0],
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 如果没有数据,清空选中项
|
|
|
+ set_selectedTreeNode(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否还有更多数据
|
|
|
+ const hasMore =
|
|
|
+ response.list.length === 30 && page < response.totalPage;
|
|
|
+ set_leftSideHasMore(hasMore);
|
|
|
+ set_leftSideCurrentPage(page);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载左侧列表数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ set_leftSideLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 缺陷项表格列定义
|
|
|
+ const defectColumns: ProColumns[] = [
|
|
|
+ {
|
|
|
+ title: '缺陷项',
|
|
|
+ dataIndex: 'resultName',
|
|
|
+ width: 600,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '分值',
|
|
|
+ dataIndex: 'percentScore',
|
|
|
+ width: 120,
|
|
|
+ align: 'center',
|
|
|
+ renderText: (text) => `${text}分`,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'option',
|
|
|
+ width: 120,
|
|
|
+ align: 'center',
|
|
|
+ valueType: 'option',
|
|
|
+ render: (_: any, record: any) => {
|
|
|
+ return [
|
|
|
+ <UpDataActBtn key={'edit'} record={record} type="EDIT" />,
|
|
|
+ <Popconfirm
|
|
|
+ key={'del'}
|
|
|
+ title="确定要删除这个缺陷项吗?"
|
|
|
+ onConfirm={() => handleDelete(record)}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <a style={{ color: '#ff4d4f' }}>删除</a>
|
|
|
+ </Popconfirm>,
|
|
|
+ ];
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 查核结果表格列定义
|
|
|
+ const checkResultColumns: ProColumns[] = [
|
|
|
+ {
|
|
|
+ title: '查核结果',
|
|
|
+ dataIndex: 'name',
|
|
|
+ width: 300,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '分值',
|
|
|
+ dataIndex: 'score',
|
|
|
+ width: 120,
|
|
|
+ align: 'center',
|
|
|
+ render: (text, record) => {
|
|
|
+ if (record.scoreType === 1) {
|
|
|
+ // 占比类型:将小数转换为百分比显示,颜色为绿色
|
|
|
+ const percentage = (parseFloat(String(text || 0)) * 100).toFixed(1);
|
|
|
+ return <span style={{ color: '#00BF8F' }}>{percentage}%</span>;
|
|
|
+ } else {
|
|
|
+ // 数值类型:显示分数
|
|
|
+ return <span>{String(text || 0)}分</span>;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '默认项',
|
|
|
+ dataIndex: 'defaultFlag',
|
|
|
+ width: 80,
|
|
|
+ align: 'center',
|
|
|
+ render: (_: any, record: any) => {
|
|
|
+ return (
|
|
|
+ <Radio
|
|
|
+ checked={record.defaultFlag === 1}
|
|
|
+ onChange={() => handleDefaultFlagChange(record)}
|
|
|
+ style={{ cursor: 'pointer' }}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '快捷项',
|
|
|
+ dataIndex: 'fastFlag',
|
|
|
+ width: 80,
|
|
|
+ align: 'center',
|
|
|
+ render: (_: any, record: any) => {
|
|
|
+ return (
|
|
|
+ <Radio
|
|
|
+ checked={record.fastFlag === 1}
|
|
|
+ onChange={() => handleFastFlagChange(record)}
|
|
|
+ style={{ cursor: 'pointer' }}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'option',
|
|
|
+ width: 120,
|
|
|
+ align: 'center',
|
|
|
+ valueType: 'option',
|
|
|
+ render: (_: any, record: any) => {
|
|
|
+ return [
|
|
|
+ <CheckResultUpDataActBtn key={'edit'} record={record} type="EDIT" />,
|
|
|
+ <Popconfirm
|
|
|
+ key={'del'}
|
|
|
+ title="确定要删除这个查核结果吗?"
|
|
|
+ onConfirm={() => handleCheckResultDelete(record)}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <a style={{ color: '#ff4d4f' }}>删除</a>
|
|
|
+ </Popconfirm>,
|
|
|
+ ];
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 查核要点表格列定义
|
|
|
+ const checkPointColumns: ProColumns[] = [
|
|
|
+ {
|
|
|
+ title: '查核要点ID',
|
|
|
+ dataIndex: 'checkPointId',
|
|
|
+ width: 150,
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '查核要点名称',
|
|
|
+ dataIndex: 'checkPointName',
|
|
|
+ ellipsis: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'option',
|
|
|
+ width: 80,
|
|
|
+ align: 'center',
|
|
|
+ valueType: 'option',
|
|
|
+ render: (_: any, record: any) => {
|
|
|
+ return [
|
|
|
+ <Popconfirm
|
|
|
+ key={'del'}
|
|
|
+ title="确定要删除这个查核要点吗?"
|
|
|
+ onConfirm={() => handleCheckPointDelete(record)}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <a style={{ color: '#ff4d4f' }}>删除</a>
|
|
|
+ </Popconfirm>,
|
|
|
+ ];
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 获取表格数据 - 根据选中的查核项获取其缺陷项列表
|
|
|
+ const getTableData = async (
|
|
|
+ params: any,
|
|
|
+ ): Promise<TableDataResponse | any[]> => {
|
|
|
+ // 如果没有选中的查核项,返回空数据
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: true,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 10,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取所有数据,不传递filter参数
|
|
|
+ const response = await getDefectData({
|
|
|
+ checkItemId: selectedTreeNode.key, // 查核项ID
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && Array.isArray(response)) {
|
|
|
+ // 在前端进行本地过滤
|
|
|
+ let filteredData = response;
|
|
|
+ if (tableDataSearchKeywords && tableDataSearchKeywords.trim()) {
|
|
|
+ filteredData = response.filter((item) =>
|
|
|
+ item.resultName &&
|
|
|
+ item.resultName.toLowerCase().includes(tableDataSearchKeywords.toLowerCase())
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动处理分页
|
|
|
+ const current = params.current || 1;
|
|
|
+ const pageSize = params.pageSize || 10;
|
|
|
+ const start = (current - 1) * pageSize;
|
|
|
+ const end = start + pageSize;
|
|
|
+ const paginatedData = filteredData.slice(start, end);
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: paginatedData,
|
|
|
+ success: true,
|
|
|
+ total: filteredData.length,
|
|
|
+ pageSize: pageSize,
|
|
|
+ totalPage: Math.ceil(filteredData.length / pageSize),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取缺陷项数据失败:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: false,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 10,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除缺陷项处理
|
|
|
+ const handleDelete = async (record: any) => {
|
|
|
+ try {
|
|
|
+ const response = await delDefectData([record.id]);
|
|
|
+ if (response) {
|
|
|
+ message.success('删除缺陷项成功!');
|
|
|
+ tableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除缺陷项失败:', error);
|
|
|
+ message.error('删除缺陷项失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取查核结果数据
|
|
|
+ const getCheckResultTableData = async (
|
|
|
+ params: any,
|
|
|
+ ): Promise<TableDataResponse | any[]> => {
|
|
|
+ // 如果没有选中的查核项,返回空数据
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: true,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 10,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取所有数据,不传递filter参数
|
|
|
+ const response = await getCheckResultData({
|
|
|
+ checkItemId: selectedTreeNode.key, // 查核项ID
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && Array.isArray(response)) {
|
|
|
+ // 在前端进行本地过滤
|
|
|
+ let filteredData = response;
|
|
|
+ if (checkResultSearchKeywords && checkResultSearchKeywords.trim()) {
|
|
|
+ filteredData = response.filter((item) =>
|
|
|
+ item.name &&
|
|
|
+ item.name.toLowerCase().includes(checkResultSearchKeywords.toLowerCase())
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动处理分页
|
|
|
+ const current = params.current || 1;
|
|
|
+ const pageSize = params.pageSize || 10;
|
|
|
+ const start = (current - 1) * pageSize;
|
|
|
+ const end = start + pageSize;
|
|
|
+ const paginatedData = filteredData.slice(start, end);
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: paginatedData,
|
|
|
+ success: true,
|
|
|
+ total: filteredData.length,
|
|
|
+ pageSize: pageSize,
|
|
|
+ totalPage: Math.ceil(filteredData.length / pageSize),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取查核结果数据失败:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: false,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 10,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除查核结果处理
|
|
|
+ const handleCheckResultDelete = async (record: any) => {
|
|
|
+ try {
|
|
|
+ const response = await delCheckResultData([record.id]);
|
|
|
+ if (response) {
|
|
|
+ message.success('删除查核结果成功!');
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除查核结果失败:', error);
|
|
|
+ message.error('删除查核结果失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理默认项变更
|
|
|
+ const handleDefaultFlagChange = async (record: any) => {
|
|
|
+ try {
|
|
|
+ const response = await editCheckResultData({
|
|
|
+ ...record,
|
|
|
+ defaultFlag: record.defaultFlag === 1 ? 0 : 1,
|
|
|
+ checkItemId: selectedTreeNode.key,
|
|
|
+ });
|
|
|
+ if (response) {
|
|
|
+ message.success('默认项设置成功!');
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('设置默认项失败:', error);
|
|
|
+ message.error('设置默认项失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理快捷项变更
|
|
|
+ const handleFastFlagChange = async (record: any) => {
|
|
|
+ try {
|
|
|
+ const response = await editCheckResultData({
|
|
|
+ ...record,
|
|
|
+ fastFlag: record.fastFlag === 1 ? 0 : 1,
|
|
|
+ checkItemId: selectedTreeNode.key,
|
|
|
+ });
|
|
|
+ if (response) {
|
|
|
+ message.success('快捷项设置成功!');
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('设置快捷项失败:', error);
|
|
|
+ message.error('设置快捷项失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 新增查核项处理
|
|
|
+ const handleAddCheckItem = async () => {
|
|
|
+ console.log('点击新增查核项按钮');
|
|
|
+
|
|
|
+ // 懒加载查核方式选项
|
|
|
+ if (checkModeOptions.length === 0) {
|
|
|
+ try {
|
|
|
+ const checkModeResponse = await getCheckItemModeList();
|
|
|
+ if (checkModeResponse) {
|
|
|
+ const options = checkModeResponse.map((item) => ({
|
|
|
+ label: item.itemName,
|
|
|
+ value: item.itemCode,
|
|
|
+ }));
|
|
|
+ set_checkModeOptions(options);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载查核方式选项失败:', error);
|
|
|
+ message.error('加载查核方式选项失败!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ set_addCheckItemVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 新增查核项提交处理
|
|
|
+ const handleAddCheckItemSubmit = async (values: any) => {
|
|
|
+ try {
|
|
|
+ console.log('提交新增查核项数据:', values);
|
|
|
+
|
|
|
+ // 处理图片上传
|
|
|
+ let rightAnswerImage = '';
|
|
|
+ if (values.rightAnswerImage && Array.isArray(values.rightAnswerImage) && values.rightAnswerImage.length > 0) {
|
|
|
+ try {
|
|
|
+ const uploadedUrls = await uploadFiles(values.rightAnswerImage);
|
|
|
+ rightAnswerImage = uploadedUrls.join(',');
|
|
|
+ console.log('图片上传成功,URLs:', uploadedUrls);
|
|
|
+ } catch (uploadError) {
|
|
|
+ console.error('图片上传失败:', uploadError);
|
|
|
+ message.error('图片上传失败,请重试!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const submitData = {
|
|
|
+ ...values,
|
|
|
+ rightAnswerImage,
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('最终提交数据:', submitData);
|
|
|
+
|
|
|
+ const response = await addData(submitData);
|
|
|
+ if (response) {
|
|
|
+ message.success('新增查核项成功!');
|
|
|
+ set_addCheckItemVisible(false);
|
|
|
+ // 重新加载左侧列表数据
|
|
|
+ set_leftSideCurrentPage(1);
|
|
|
+ set_leftSideHasMore(true);
|
|
|
+ await loadMoreLeftSideItems(1, false);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('新增查核项失败:', error);
|
|
|
+ message.error('新增查核项失败!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 更新表格数据 - 缺陷项的新增和编辑
|
|
|
+ const updateTable = async (formVal: any, type: 'EDIT' | 'ADD') => {
|
|
|
+ try {
|
|
|
+ // 如果没有选中的查核项,不能进行操作
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ message.error('请先选择查核项!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const submitData = {
|
|
|
+ ...formVal,
|
|
|
+ checkItemId: selectedTreeNode.key,
|
|
|
+ };
|
|
|
+
|
|
|
+ let response;
|
|
|
+ if (type === 'ADD') {
|
|
|
+ response = await addDefectData(submitData);
|
|
|
+ } else {
|
|
|
+ response = await editDefectData(submitData);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response) {
|
|
|
+ message.success(`${type === 'ADD' ? '新增' : '编辑'}缺陷项成功!`);
|
|
|
+ tableRef.current?.reload();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`${type === 'ADD' ? '新增' : '编辑'}缺陷项失败:`, error);
|
|
|
+ message.error(`${type === 'ADD' ? '新增' : '编辑'}缺陷项失败!`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 更新查核结果数据 - 查核结果的新增和编辑
|
|
|
+ const updateCheckResultTable = async (formVal: any, type: 'EDIT' | 'ADD') => {
|
|
|
+ try {
|
|
|
+ // 如果没有选中的查核项,不能进行操作
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ message.error('请先选择查核项!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const submitData = {
|
|
|
+ ...formVal,
|
|
|
+ checkItemId: selectedTreeNode.key,
|
|
|
+ };
|
|
|
+
|
|
|
+ let response;
|
|
|
+ if (type === 'ADD') {
|
|
|
+ response = await addCheckResultData(submitData);
|
|
|
+ } else {
|
|
|
+ response = await editCheckResultData(submitData);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response) {
|
|
|
+ message.success(`${type === 'ADD' ? '新增' : '编辑'}查核结果成功!`);
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`${type === 'ADD' ? '新增' : '编辑'}查核结果失败:`, error);
|
|
|
+ message.error(`${type === 'ADD' ? '新增' : '编辑'}查核结果失败!`);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 新增/编辑弹窗组件
|
|
|
+ const UpDataActBtn = ({
|
|
|
+ record,
|
|
|
+ type,
|
|
|
+ }: {
|
|
|
+ record: any;
|
|
|
+ type: 'EDIT' | 'ADD';
|
|
|
+ }) => {
|
|
|
+
|
|
|
+
|
|
|
+ return (
|
|
|
+ <ModalForm
|
|
|
+ title={`${type == 'EDIT' ? '编辑' : '新增'}缺陷项`}
|
|
|
+ width={352}
|
|
|
+ initialValues={
|
|
|
+ type == 'EDIT'
|
|
|
+ ? {
|
|
|
+ resultName: record.resultName,
|
|
|
+ percentScore: record.percentScore,
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ resultName: '',
|
|
|
+ percentScore: 0,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ trigger={
|
|
|
+ type == 'EDIT' ? (
|
|
|
+ <a key="edit" style={{ color: '#1890ff' }}>
|
|
|
+ 编辑
|
|
|
+ </a>
|
|
|
+ ) : (
|
|
|
+ <Button type="primary" className="add-btn" size="small">
|
|
|
+ 新增
|
|
|
+ </Button>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ formRef={modalFormRef}
|
|
|
+ onFinish={async (val) => {
|
|
|
+ console.log('缺陷项表单提交:', val);
|
|
|
+ // 编辑时需要传递ID
|
|
|
+ if (type === 'EDIT') {
|
|
|
+ val.id = record.id;
|
|
|
+ }
|
|
|
+ return updateTable(val, type);
|
|
|
+ }}
|
|
|
+ modalProps={{
|
|
|
+ destroyOnClose: true,
|
|
|
+ onCancel: () => console.log('缺陷项表单取消'),
|
|
|
+ }}
|
|
|
+ onVisibleChange={(visible) => {
|
|
|
+ console.log('缺陷项表单可见性变化:', visible);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ProFormTextArea
|
|
|
+ name="resultName"
|
|
|
+ label="缺陷项名称:"
|
|
|
+ placeholder="请输入缺陷项名称"
|
|
|
+ fieldProps={{
|
|
|
+ rows: 3,
|
|
|
+ maxLength: 200,
|
|
|
+ showCount: true,
|
|
|
+ }}
|
|
|
+ rules={[{ required: true, message: '请输入缺陷项名称' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormDigit
|
|
|
+ name="percentScore"
|
|
|
+ label="分值:"
|
|
|
+ placeholder="0"
|
|
|
+ min={0}
|
|
|
+ max={100}
|
|
|
+ fieldProps={{ precision: 1 }}
|
|
|
+ rules={[{ required: true, message: '请输入分值' }]}
|
|
|
+ />
|
|
|
+ </ModalForm>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 查核结果新增/编辑弹窗组件
|
|
|
+ const CheckResultUpDataActBtn = ({
|
|
|
+ record,
|
|
|
+ type,
|
|
|
+ }: {
|
|
|
+ record: any;
|
|
|
+ type: 'EDIT' | 'ADD';
|
|
|
+ }) => {
|
|
|
+ console.log('CheckResultUpDataActBtn 渲染:', { type, record });
|
|
|
+
|
|
|
+ return (
|
|
|
+ <ModalForm
|
|
|
+ title={`${type == 'EDIT' ? '编辑' : '新增'}查核结果`}
|
|
|
+ width={352}
|
|
|
+ initialValues={
|
|
|
+ type == 'EDIT'
|
|
|
+ ? {
|
|
|
+ name: record.name,
|
|
|
+ score: record.score,
|
|
|
+ scoreType: record.scoreType,
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ name: '',
|
|
|
+ score: 0,
|
|
|
+ scoreType: 0,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ trigger={
|
|
|
+ type == 'EDIT' ? (
|
|
|
+ <a key="edit" style={{ color: '#1890ff' }}>
|
|
|
+ 编辑
|
|
|
+ </a>
|
|
|
+ ) : (
|
|
|
+ <Button type="primary" className="add-btn" size="small">
|
|
|
+ 新增
|
|
|
+ </Button>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ formRef={modalFormRef}
|
|
|
+ onFinish={async (val) => {
|
|
|
+ console.log('查核结果表单提交:', val);
|
|
|
+ // 编辑时需要传递ID,新增时设置默认值
|
|
|
+ if (type === 'EDIT') {
|
|
|
+ val.id = record.id;
|
|
|
+ // 编辑时保持原有的默认项和快捷项状态
|
|
|
+ val.defaultFlag = record.defaultFlag;
|
|
|
+ val.fastFlag = record.fastFlag;
|
|
|
+ } else {
|
|
|
+ // 新增时设置默认值
|
|
|
+ val.defaultFlag = 0;
|
|
|
+ val.fastFlag = 0;
|
|
|
+ }
|
|
|
+ return updateCheckResultTable(val, type);
|
|
|
+ }}
|
|
|
+ modalProps={{
|
|
|
+ destroyOnClose: true,
|
|
|
+ onCancel: () => console.log('查核结果表单取消'),
|
|
|
+ }}
|
|
|
+ onVisibleChange={(visible) => {
|
|
|
+ console.log('查核结果表单可见性变化:', visible);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ProFormText
|
|
|
+ name="name"
|
|
|
+ label="查核结果:"
|
|
|
+ placeholder="请输入"
|
|
|
+ rules={[{ required: true, message: '请输入查核结果' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormRadio.Group
|
|
|
+ name="scoreType"
|
|
|
+ label="分值类型:"
|
|
|
+ options={[
|
|
|
+ { label: '数值', value: 0 },
|
|
|
+ { label: '占比', value: 1 },
|
|
|
+ ]}
|
|
|
+ rules={[{ required: true, message: '请选择分值类型' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormDigit
|
|
|
+ name="score"
|
|
|
+ label="分值数值:"
|
|
|
+ placeholder="0"
|
|
|
+ max={100}
|
|
|
+ fieldProps={{ precision: 1, min: -100000 }}
|
|
|
+ rules={[{ required: true, message: '请输入分值' }]}
|
|
|
+ />
|
|
|
+ </ModalForm>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 树节点选择
|
|
|
+ const onTreeSelect = (selectedKeys: any[], info: any) => {
|
|
|
+ if (selectedKeys.length > 0) {
|
|
|
+ set_selectedTreeNode(info.node);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 搜索处理
|
|
|
+ const tableDataSearchHandle = (keywords: string) => {
|
|
|
+ set_tableDataSearchKeywords(keywords);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 搜索处理 - 重置列表并重新加载
|
|
|
+ const handleCheckItemSearch = async () => {
|
|
|
+ set_leftSideCurrentPage(1);
|
|
|
+ set_leftSideHasMore(true);
|
|
|
+ await loadMoreLeftSideItems(1, false);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 搜索框内容变化处理
|
|
|
+ const handleSearchInputChange = async (
|
|
|
+ e: React.ChangeEvent<HTMLInputElement>,
|
|
|
+ ) => {
|
|
|
+ const value = e.target.value;
|
|
|
+ set_checkItemSearchKeywords(value);
|
|
|
+
|
|
|
+ // 如果搜索框被清空,自动重新加载数据
|
|
|
+ if (value === '') {
|
|
|
+ set_leftSideCurrentPage(1);
|
|
|
+ set_leftSideHasMore(true);
|
|
|
+ // 直接调用接口,传递空字符串作为filter参数
|
|
|
+ try {
|
|
|
+ set_leftSideLoading(true);
|
|
|
+ const response = await getData({
|
|
|
+ current: 1,
|
|
|
+ pageSize: 30,
|
|
|
+ filter: '', // 直接传递空字符串
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && response.list) {
|
|
|
+ set_leftSideCheckItems(response.list);
|
|
|
+ // 如果有数据,总是设置第一个为默认选中
|
|
|
+ if (response.list.length > 0) {
|
|
|
+ set_selectedTreeNode({
|
|
|
+ key: response.list[0].id,
|
|
|
+ title: response.list[0].name,
|
|
|
+ ...response.list[0],
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 如果没有数据,清空选中项
|
|
|
+ set_selectedTreeNode(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否还有更多数据
|
|
|
+ const hasMore = response.list.length === 30 && 1 < response.totalPage;
|
|
|
+ set_leftSideHasMore(hasMore);
|
|
|
+ set_leftSideCurrentPage(1);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载左侧列表数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ set_leftSideLoading(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除查核项处理
|
|
|
+ const handleDeleteCheckItem = async (item: any) => {
|
|
|
+ console.log('点击删除查核项按钮', item);
|
|
|
+ try {
|
|
|
+ const response = await delData([item.id]);
|
|
|
+ if (response) {
|
|
|
+ message.success('删除查核项成功!');
|
|
|
+ // 重新加载左侧列表数据
|
|
|
+ set_leftSideCurrentPage(1);
|
|
|
+ set_leftSideHasMore(true);
|
|
|
+ await loadMoreLeftSideItems(1, false);
|
|
|
+ // 如果删除的是当前选中项,清空选中状态
|
|
|
+ if (selectedTreeNode?.key === item.id) {
|
|
|
+ set_selectedTreeNode(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除查核项失败:', error);
|
|
|
+ message.error('删除查核项失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取更多菜单
|
|
|
+ const getMoreMenu = (node: any) => (
|
|
|
+ <Menu
|
|
|
+ items={[
|
|
|
+ {
|
|
|
+ key: 'edit',
|
|
|
+ label: '编辑',
|
|
|
+ onClick: () => {
|
|
|
+ handleEditCheckItem(node);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'delete',
|
|
|
+ label: '删除',
|
|
|
+ onClick: (e) => {
|
|
|
+ e.domEvent.stopPropagation();
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ onClick={({ key, domEvent }) => {
|
|
|
+ if (key === 'delete') {
|
|
|
+ domEvent.stopPropagation();
|
|
|
+ // 阻止菜单关闭,显示确认对话框
|
|
|
+ setTimeout(() => {
|
|
|
+ Modal.confirm({
|
|
|
+ title: '删除确认',
|
|
|
+ content: `确定要删除查核项"${node.name}"吗?`,
|
|
|
+ okText: '确定',
|
|
|
+ cancelText: '取消',
|
|
|
+ onOk: () => handleDeleteCheckItem(node),
|
|
|
+ });
|
|
|
+ }, 100);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ );
|
|
|
+
|
|
|
+ // 渲染左侧查核项分类列表
|
|
|
+ const renderCheckItemList = () => {
|
|
|
+ return (
|
|
|
+ <div className="item-tree">
|
|
|
+ <div className="search-wrapper">
|
|
|
+ <Input
|
|
|
+ placeholder="查核项"
|
|
|
+ suffix={
|
|
|
+ <SearchOutlined
|
|
|
+ style={{ color: '#99A6BF', cursor: 'pointer' }}
|
|
|
+ onClick={handleCheckItemSearch}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ className="search-input"
|
|
|
+ value={checkItemSearchKeywords}
|
|
|
+ onChange={handleSearchInputChange}
|
|
|
+ onPressEnter={handleCheckItemSearch}
|
|
|
+ allowClear
|
|
|
+ />
|
|
|
+ <Tooltip title="新增查核项">
|
|
|
+ <Button
|
|
|
+ className="add-button"
|
|
|
+ icon={<PlusOutlined style={{ fontSize: 12 }} />}
|
|
|
+ onClick={handleAddCheckItem}
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="item-list" ref={leftSideListRef}>
|
|
|
+ {leftSideCheckItems.map((item) => (
|
|
|
+ <div key={item.id} className="item-group">
|
|
|
+ <div
|
|
|
+ className={`item-leaf ${selectedTreeNode?.key === item.id ? 'selected' : ''
|
|
|
+ }`}
|
|
|
+ onClick={() => {
|
|
|
+ set_selectedTreeNode({
|
|
|
+ key: item.id,
|
|
|
+ title: item.name,
|
|
|
+ ...item,
|
|
|
+ });
|
|
|
+ }}
|
|
|
+ onMouseEnter={() => set_hoveredNode(item.id)}
|
|
|
+ onMouseLeave={() => set_hoveredNode(null)}
|
|
|
+ >
|
|
|
+ <div className="item-icon">
|
|
|
+ <img src={baobiaotubiaoIcon} alt="查核项图标" />
|
|
|
+ </div>
|
|
|
+ <div className="item-content">
|
|
|
+ <span className="item-title" title={item.name}>
|
|
|
+ {item.name.length > 8 ? `${item.name.substring(0, 8)}...` : item.name}
|
|
|
+ </span>
|
|
|
+ <span className="item-score">分值:{item.score || 0}分</span>
|
|
|
+ </div>
|
|
|
+ {(selectedTreeNode?.key === item.id ||
|
|
|
+ hoveredNode === item.id ||
|
|
|
+ openDropdownNode === item.id) && (
|
|
|
+ <Dropdown
|
|
|
+ overlay={getMoreMenu(item)}
|
|
|
+ trigger={['click']}
|
|
|
+ placement="bottomRight"
|
|
|
+ overlayClassName="check-item-dropdown"
|
|
|
+ onVisibleChange={(visible) => {
|
|
|
+ if (visible) {
|
|
|
+ set_openDropdownNode(item.id);
|
|
|
+ } else {
|
|
|
+ set_openDropdownNode(null);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ icon={<MoreOutlined />}
|
|
|
+ className="more-button"
|
|
|
+ onClick={(e) => e.stopPropagation()}
|
|
|
+ />
|
|
|
+ </Dropdown>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+
|
|
|
+ {/* 加载更多状态 */}
|
|
|
+ {leftSideLoading && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ padding: '12px',
|
|
|
+ textAlign: 'center',
|
|
|
+ color: '#999',
|
|
|
+ fontSize: '12px',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 加载中...
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 没有更多数据提示 */}
|
|
|
+ {!leftSideHasMore && leftSideCheckItems.length > 0 && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ padding: '12px',
|
|
|
+ textAlign: 'center',
|
|
|
+ color: '#999',
|
|
|
+ fontSize: '12px',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 已加载全部数据
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 暂无数据提示 */}
|
|
|
+ {!leftSideLoading && leftSideCheckItems.length === 0 && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ padding: '20px',
|
|
|
+ textAlign: 'center',
|
|
|
+ color: '#999',
|
|
|
+ fontSize: '14px',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 暂无数据
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 编辑查核项处理
|
|
|
+ const handleEditCheckItem = async (item: any) => {
|
|
|
+
|
|
|
+ // 懒加载查核方式选项
|
|
|
+ if (checkModeOptions.length === 0) {
|
|
|
+ try {
|
|
|
+ const checkModeResponse = await getCheckItemModeList();
|
|
|
+ if (checkModeResponse) {
|
|
|
+ const options = checkModeResponse.map((optionItem) => ({
|
|
|
+ label: optionItem.itemName,
|
|
|
+ value: optionItem.itemCode,
|
|
|
+ }));
|
|
|
+ set_checkModeOptions(options);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载查核方式选项失败:', error);
|
|
|
+ message.error('加载查核方式选项失败!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ set_currentEditItem(item);
|
|
|
+ set_editCheckItemVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 编辑查核项提交处理
|
|
|
+ const handleEditCheckItemSubmit = async (values: any) => {
|
|
|
+ try {
|
|
|
+ const submitData = { ...values, id: currentEditItem?.id };
|
|
|
+ const response = await editData(submitData);
|
|
|
+ if (response) {
|
|
|
+ message.success('编辑查核项成功!');
|
|
|
+ set_editCheckItemVisible(false);
|
|
|
+ set_currentEditItem(null);
|
|
|
+ set_leftSideCurrentPage(1);
|
|
|
+ set_leftSideHasMore(true);
|
|
|
+ await loadMoreLeftSideItems(1, false);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('编辑查核项失败:', error);
|
|
|
+ message.error('编辑查核项失败!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // tab切换处理函数
|
|
|
+ const handleTabChange = (tabKey: string) => {
|
|
|
+ set_activeTabKey(tabKey);
|
|
|
+ // 切换tab时,如果有选中的查核项,立即加载对应的数据
|
|
|
+ if (selectedTreeNode?.key) {
|
|
|
+ setTimeout(() => {
|
|
|
+ if (tabKey === 'defects' && tableRef.current) {
|
|
|
+ tableRef.current.reload();
|
|
|
+ } else if (tabKey === 'results' && checkResultTableRef.current) {
|
|
|
+ checkResultTableRef.current.reload();
|
|
|
+ } else if (tabKey === 'points' && checkPointTableRef.current) {
|
|
|
+ checkPointTableRef.current.reload();
|
|
|
+ }
|
|
|
+ }, 100); // 稍微延迟一下,确保tab切换完成
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理复制设置按钮点击
|
|
|
+ const handleCopySettingClick = async () => {
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ message.error('请先选择查核项!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 初始化弹窗状态
|
|
|
+ set_selectedCopyItems([]);
|
|
|
+ set_copySearchKeywords('');
|
|
|
+ set_copySettingVisible(true);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('打开复制设置弹窗失败:', error);
|
|
|
+ message.error('打开复制设置弹窗失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理复制设置确认
|
|
|
+ const handleCopySettingConfirm = async () => {
|
|
|
+ if (selectedCopyItems.length === 0) {
|
|
|
+ message.error('请选择要复制的查核项!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ message.error('请先选择源查核项!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const response = await copyCheckItemResult({
|
|
|
+ sourceId: String(selectedTreeNode.key),
|
|
|
+ targetIdList: selectedCopyItems,
|
|
|
+ });
|
|
|
+ if (response) {
|
|
|
+ message.success('批量复制查核结果成功!');
|
|
|
+ set_copySettingVisible(false);
|
|
|
+ set_selectedCopyItems([]);
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ } else {
|
|
|
+ message.error(response?.msg || '批量复制查核结果失败!');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('批量复制查核结果失败:', error);
|
|
|
+ message.error('批量复制查核结果失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取复制设置数据
|
|
|
+ const getCopySettingTableData = async (params: any): Promise<TableDataResponse | any[]> => {
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: true,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 100,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await getData({
|
|
|
+ current: params.current || 1,
|
|
|
+ pageSize: params.pageSize || 100,
|
|
|
+ filter: copySearchKeywords || '',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && response.list) {
|
|
|
+ // 过滤掉当前选中的查核项
|
|
|
+ const filteredItems = response.list.filter(
|
|
|
+ (item) => item.id !== selectedTreeNode.key,
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: filteredItems,
|
|
|
+ success: true,
|
|
|
+ total: response.totalCount > 0 ? response.totalCount - 1 : 0, // 减去当前选中项
|
|
|
+ pageSize: params.pageSize || 100,
|
|
|
+ totalPage: response.totalPage,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取复制设置数据失败:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: false,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 100,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理复制设置搜索
|
|
|
+ const handleCopySearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
+ const value = e.target.value;
|
|
|
+ set_copySearchKeywords(value);
|
|
|
+
|
|
|
+ // 如果搜索框被清空,立即刷新表格
|
|
|
+ if (value === '') {
|
|
|
+ copySettingTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理复制设置搜索确认
|
|
|
+ const handleCopySearchConfirm = () => {
|
|
|
+ // 刷新表格数据
|
|
|
+ copySettingTableRef.current?.reload();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取查核要点数据
|
|
|
+ const getCheckPointTableData = async (
|
|
|
+ params: any,
|
|
|
+ ): Promise<TableDataResponse | any[]> => {
|
|
|
+ // 如果没有选中的查核项,返回空数据
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: true,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 10,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取所有数据,不传递filter参数
|
|
|
+ const response = await getCheckPointData({
|
|
|
+ checkItemId: selectedTreeNode.key, // 查核项ID
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && Array.isArray(response)) {
|
|
|
+ // 在前端进行本地过滤
|
|
|
+ let filteredData = response;
|
|
|
+ if (checkPointSearchKeywords && checkPointSearchKeywords.trim()) {
|
|
|
+ filteredData = response.filter((item) =>
|
|
|
+ (item.checkPointName &&
|
|
|
+ item.checkPointName.toLowerCase().includes(checkPointSearchKeywords.toLowerCase())) ||
|
|
|
+ (item.checkPointId &&
|
|
|
+ item.checkPointId.toString().includes(checkPointSearchKeywords))
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动处理分页
|
|
|
+ const current = params.current || 1;
|
|
|
+ const pageSize = params.pageSize || 10;
|
|
|
+ const start = (current - 1) * pageSize;
|
|
|
+ const end = start + pageSize;
|
|
|
+ const paginatedData = filteredData.slice(start, end);
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: paginatedData,
|
|
|
+ success: true,
|
|
|
+ total: filteredData.length,
|
|
|
+ pageSize: pageSize,
|
|
|
+ totalPage: Math.ceil(filteredData.length / pageSize),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取查核要点数据失败:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: [],
|
|
|
+ success: false,
|
|
|
+ total: 0,
|
|
|
+ pageSize: 10,
|
|
|
+ totalPage: 0,
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除查核要点处理
|
|
|
+ const handleCheckPointDelete = async (record: any) => {
|
|
|
+ try {
|
|
|
+ // 使用 id 字段作为删除参数
|
|
|
+ const response = await delCheckPointData([record.id]);
|
|
|
+ if (response) {
|
|
|
+ message.success('删除查核要点成功!');
|
|
|
+ checkPointTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除查核要点失败:', error);
|
|
|
+ message.error('删除查核要点失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 批量删除查核要点处理
|
|
|
+ const handleBatchDeleteCheckPoints = async () => {
|
|
|
+ if (selectedCheckPointTableKeys.length === 0) {
|
|
|
+ message.error('请选择要删除的查核要点!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await delCheckPointData(selectedCheckPointTableKeys);
|
|
|
+ if (response) {
|
|
|
+ message.success(`成功删除 ${selectedCheckPointTableKeys.length} 个查核要点!`);
|
|
|
+ set_selectedCheckPointTableKeys([]);
|
|
|
+ checkPointTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('批量删除查核要点失败:', error);
|
|
|
+ message.error('批量删除查核要点失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理添加查核要点按钮点击
|
|
|
+ const handleAddCheckPointClick = async () => {
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ message.error('请先选择查核项!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取待添加的查核要点列表
|
|
|
+ const response = await getItemPendingPageCheckpoint({
|
|
|
+ current: 1,
|
|
|
+ pageSize: 1000, // 获取足够多的数据
|
|
|
+ checkItemId: selectedTreeNode.key,
|
|
|
+ filter: '',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response && response.list) {
|
|
|
+ set_pendingCheckPoints(response.list);
|
|
|
+ set_selectedCheckPoints([]);
|
|
|
+ set_addCheckPointSearchKeywords('');
|
|
|
+ set_addCheckPointVisible(true);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取待添加查核要点列表失败:', error);
|
|
|
+ message.error('获取待添加查核要点列表失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理添加查核要点确认
|
|
|
+ const handleAddCheckPointConfirm = async () => {
|
|
|
+ if (selectedCheckPoints.length === 0) {
|
|
|
+ message.error('请选择要添加的查核要点!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!selectedTreeNode?.key) {
|
|
|
+ message.error('请先选择查核项!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 构造提交数据
|
|
|
+ const mapObjList = selectedCheckPoints.map((id) => {
|
|
|
+ const point = pendingCheckPoints.find((item) => item.id === id);
|
|
|
+ return {
|
|
|
+ id: id,
|
|
|
+ name: point?.name || '',
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ const submitData = {
|
|
|
+ id: String(selectedTreeNode.key),
|
|
|
+ name: selectedTreeNode.name || selectedTreeNode.title || '',
|
|
|
+ mapObjList: mapObjList,
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await addCheckItemCheckpoint(submitData);
|
|
|
+
|
|
|
+ if (response) {
|
|
|
+ message.success(`成功添加 ${selectedCheckPoints.length} 个查核要点!`);
|
|
|
+ set_addCheckPointVisible(false);
|
|
|
+ set_selectedCheckPoints([]);
|
|
|
+ checkPointTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('添加查核要点失败:', error);
|
|
|
+ message.error('添加查核要点失败!');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理查核要点选择变化
|
|
|
+ const handleCheckPointSelection = (id: string, checked: boolean) => {
|
|
|
+ if (checked) {
|
|
|
+ set_selectedCheckPoints((prev) => [...prev, id]);
|
|
|
+ } else {
|
|
|
+ set_selectedCheckPoints((prev) => prev.filter((item) => item !== id));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理添加查核要点搜索
|
|
|
+ const handleAddCheckPointSearchChange = (
|
|
|
+ e: React.ChangeEvent<HTMLInputElement>,
|
|
|
+ ) => {
|
|
|
+ set_addCheckPointSearchKeywords(e.target.value);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取过滤后的待添加查核要点列表
|
|
|
+ const getFilteredPendingCheckPoints = () => {
|
|
|
+ if (addCheckPointSearchKeywords.trim() === '') {
|
|
|
+ return pendingCheckPoints;
|
|
|
+ }
|
|
|
+ return pendingCheckPoints.filter(
|
|
|
+ (item) =>
|
|
|
+ item.name
|
|
|
+ .toLowerCase()
|
|
|
+ .includes(addCheckPointSearchKeywords.toLowerCase()) ||
|
|
|
+ (item.pointCategoryName &&
|
|
|
+ item.pointCategoryName
|
|
|
+ .toLowerCase()
|
|
|
+ .includes(addCheckPointSearchKeywords.toLowerCase())),
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理全选/取消全选
|
|
|
+ const handleSelectAll = (checked: boolean) => {
|
|
|
+ if (checked) {
|
|
|
+ // 全选:将当前过滤后的所有项目ID添加到选中列表
|
|
|
+ const filteredItems = getFilteredPendingCheckPoints();
|
|
|
+ const allIds = filteredItems.map((item) => item.id);
|
|
|
+ set_selectedCheckPoints(allIds);
|
|
|
+ } else {
|
|
|
+ // 取消全选:清空选中列表
|
|
|
+ set_selectedCheckPoints([]);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 判断是否全选状态
|
|
|
+ const isAllSelected = () => {
|
|
|
+ const filteredItems = getFilteredPendingCheckPoints();
|
|
|
+ if (filteredItems.length === 0) return false;
|
|
|
+ return filteredItems.every((item) => selectedCheckPoints.includes(item.id));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 判断是否部分选中状态(用于indeterminate)
|
|
|
+ const isIndeterminate = () => {
|
|
|
+ const filteredItems = getFilteredPendingCheckPoints();
|
|
|
+ if (filteredItems.length === 0) return false;
|
|
|
+ const selectedCount = filteredItems.filter((item) =>
|
|
|
+ selectedCheckPoints.includes(item.id),
|
|
|
+ ).length;
|
|
|
+ return selectedCount > 0 && selectedCount < filteredItems.length;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 解析图片URL列表
|
|
|
+ const parseImageUrls = (imageString: string | null | undefined) => {
|
|
|
+ if (!imageString || typeof imageString !== 'string') {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ return imageString.split(',').filter(url => url.trim()).map(url => url.trim());
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理图片预览
|
|
|
+ const handleImagePreview = (imageIndex: number) => {
|
|
|
+ set_imagePreviewIndex(imageIndex);
|
|
|
+ set_imagePreviewVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 上传文件到服务器
|
|
|
+ const uploadFiles = async (fileList: any[]): Promise<string[]> => {
|
|
|
+ const uploadedUrls: string[] = [];
|
|
|
+
|
|
|
+ for (const file of fileList) {
|
|
|
+ if (file.originFileObj) {
|
|
|
+ // 新选择的文件,需要上传
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('file', file.originFileObj);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/gateway/centerSys/api/batchUpload', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ token: localStorage.getItem('userData')
|
|
|
+ ? JSON.parse(localStorage.getItem('userData') || '{}').token
|
|
|
+ : '',
|
|
|
+ },
|
|
|
+ body: formData,
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result?.data && Array.isArray(result.data)) {
|
|
|
+ // 批量上传返回数组格式
|
|
|
+ const urls = result.data.map((item: any) => item.downUrl);
|
|
|
+ uploadedUrls.push(...urls);
|
|
|
+ } else if (result?.data?.downUrl) {
|
|
|
+ // 单个上传格式
|
|
|
+ uploadedUrls.push(result.data.downUrl);
|
|
|
+ } else if (result?.downUrl) {
|
|
|
+ uploadedUrls.push(result.downUrl);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('文件上传失败:', error);
|
|
|
+ throw new Error(`文件 ${file.name} 上传失败`);
|
|
|
+ }
|
|
|
+ } else if (file.url) {
|
|
|
+ // 已存在的文件(编辑时的回显文件)
|
|
|
+ uploadedUrls.push(file.url);
|
|
|
+ } else if (file.response?.data?.downUrl) {
|
|
|
+ // 已经上传过的文件
|
|
|
+ uploadedUrls.push(file.response.data.downUrl);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return uploadedUrls;
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ return (
|
|
|
+ <KCIMPagecontainer title={false}>
|
|
|
+ <div className="CheckItemMana">
|
|
|
+ <div className="check-item-container">
|
|
|
+ {/* 左侧查核项分类 */}
|
|
|
+ {renderCheckItemList()}
|
|
|
+
|
|
|
+ {/* 右侧内容区域 */}
|
|
|
+ <div className="main-content" ref={mainContentRef}>
|
|
|
+ {/* 顶部标题和信息区域 */}
|
|
|
+ <div className="content-header">
|
|
|
+ <div className="header-left">
|
|
|
+ <h2 className="title" title={selectedTreeNode?.title || selectedTreeNode?.name || '请选择查核项'}>
|
|
|
+ {(() => {
|
|
|
+ const fullTitle = selectedTreeNode?.title || selectedTreeNode?.name || '请选择查核项';
|
|
|
+ return fullTitle.length > 25 ? `${fullTitle.substring(0, 25)}...` : fullTitle;
|
|
|
+ })()}
|
|
|
+ </h2>
|
|
|
+ <div className="description">
|
|
|
+ 应知应会:{selectedTreeNode?.rightAnswerText || '暂无内容'}
|
|
|
+ </div>
|
|
|
+ <div className="info-tags">
|
|
|
+ <div className="info-tag">
|
|
|
+ <div className="tag-icon">
|
|
|
+ <img src={leixingIcon} alt="选项类型" />
|
|
|
+ </div>
|
|
|
+ <div className="tag-content">
|
|
|
+ <span className="tag-label">选项类型</span>
|
|
|
+ <span className="tag-value">
|
|
|
+ {selectedTreeNode?.moreDeduction === 0
|
|
|
+ ? '单选'
|
|
|
+ : selectedTreeNode?.moreDeduction === 1
|
|
|
+ ? '多选'
|
|
|
+ : '未知'}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="info-tag">
|
|
|
+ <div className="tag-icon">
|
|
|
+ <img src={fenzhiIcon} alt="项目分值" />
|
|
|
+ </div>
|
|
|
+ <div className="tag-content">
|
|
|
+ <span className="tag-label">项目分值</span>
|
|
|
+ <span className="tag-value">
|
|
|
+ {selectedTreeNode?.score || 0}分
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="info-tag">
|
|
|
+ <div className="tag-icon">
|
|
|
+ <img src={chaheIcon} alt="查核方式" />
|
|
|
+ </div>
|
|
|
+ <div className="tag-content">
|
|
|
+ <span className="tag-label">查核方式</span>
|
|
|
+ <span className="tag-value">
|
|
|
+ {selectedTreeNode?.checkModeNameList || '未知'}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="info-tag">
|
|
|
+ <div className="tag-icon">
|
|
|
+ <img src={tongjiIcon} alt="统计小计" />
|
|
|
+ </div>
|
|
|
+ <div className="tag-content">
|
|
|
+ <span className="tag-label">统计小计</span>
|
|
|
+ <span className="tag-value">
|
|
|
+ {selectedTreeNode?.subtotal === 1 ? '是' : '否'}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="header-right">
|
|
|
+ <div className="image-gallery">
|
|
|
+ {(() => {
|
|
|
+ const imageUrls = parseImageUrls(selectedTreeNode?.rightAnswerImage);
|
|
|
+
|
|
|
+ // 如果没有图片,不显示任何内容
|
|
|
+ if (imageUrls.length === 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只显示前2张图片
|
|
|
+ const displayImages = imageUrls.slice(0, 2);
|
|
|
+ const remainingCount = imageUrls.length - 2;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {displayImages.map((url, index) => (
|
|
|
+ <div
|
|
|
+ key={index}
|
|
|
+ className="image-item"
|
|
|
+ onClick={() => handleImagePreview(index)}
|
|
|
+ style={{
|
|
|
+ cursor: 'pointer',
|
|
|
+ position: 'relative'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src={url}
|
|
|
+ alt={`查核项图片${index + 1}`}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 如果是第二张图片且有更多图片,显示遮罩层 */}
|
|
|
+ {index === 1 && remainingCount > 0 && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ position: 'absolute',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ right: 0,
|
|
|
+ bottom: 0,
|
|
|
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
|
+ color: 'white',
|
|
|
+ display: 'flex',
|
|
|
+ flexDirection: 'column',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ fontSize: '14px',
|
|
|
+ fontWeight: 'normal'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span style={{ fontSize: '24px', marginBottom: '4px' }}>
|
|
|
+ +{remainingCount}
|
|
|
+ </span>
|
|
|
+ <span style={{ opacity: 0.8 }}>查看更多图例</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ })()}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Tab区域和表格 */}
|
|
|
+ <div className="content-body">
|
|
|
+ <Card className="pfm-ant-card" bordered={false}>
|
|
|
+ <Tabs defaultActiveKey="defects" onChange={handleTabChange}>
|
|
|
+ <TabPane tab="缺陷项" key="defects">
|
|
|
+ <div className="tab-content">
|
|
|
+ <div className="toolbar">
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ gap: 8,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span style={{ color: '#17181A', fontSize: 14 }}>
|
|
|
+ 检索:
|
|
|
+ </span>
|
|
|
+ <Input
|
|
|
+ placeholder="缺陷项"
|
|
|
+ allowClear
|
|
|
+ suffix={
|
|
|
+ <SearchOutlined style={{ color: '#99A6BF' }} />
|
|
|
+ }
|
|
|
+ style={{ width: 250 }}
|
|
|
+ value={tableDataSearchKeywords}
|
|
|
+ onChange={(e) => {
|
|
|
+ set_tableDataSearchKeywords(e.target.value);
|
|
|
+ if (e.target.value === '') {
|
|
|
+ tableRef.current?.reload();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onPressEnter={(e) =>
|
|
|
+ tableDataSearchHandle(e.currentTarget.value)
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <UpDataActBtn record={{}} type="ADD" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <KCIMTable
|
|
|
+ actionRef={tableRef}
|
|
|
+ columns={defectColumns}
|
|
|
+ request={getTableData}
|
|
|
+ params={{ keywords: tableDataSearchKeywords }}
|
|
|
+ rowKey="id"
|
|
|
+ size="middle"
|
|
|
+ manualRequest={true}
|
|
|
+ search={false}
|
|
|
+ pagination={{
|
|
|
+ showTotal: (total, range) =>
|
|
|
+ `第 ${range[0]}-${range[1]} 条/共计 ${total} 条`,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showQuickJumper: true,
|
|
|
+ defaultPageSize: 10,
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </TabPane>
|
|
|
+ <TabPane tab="查核结果" key="results">
|
|
|
+ <div className="tab-content">
|
|
|
+ <div className="toolbar">
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ gap: 8,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span style={{ color: '#17181A', fontSize: 14 }}>
|
|
|
+ 检索:
|
|
|
+ </span>
|
|
|
+ <Input
|
|
|
+ placeholder="查核结果"
|
|
|
+ allowClear
|
|
|
+ suffix={
|
|
|
+ <SearchOutlined style={{ color: '#99A6BF' }} />
|
|
|
+ }
|
|
|
+ style={{ width: 250 }}
|
|
|
+ value={checkResultSearchKeywords}
|
|
|
+ onChange={(e) => {
|
|
|
+ set_checkResultSearchKeywords(e.target.value);
|
|
|
+ if (e.target.value === '') {
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onPressEnter={(e) => {
|
|
|
+ set_checkResultSearchKeywords(e.currentTarget.value);
|
|
|
+ checkResultTableRef.current?.reload();
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div style={{ display: 'flex', gap: 8 }}>
|
|
|
+ <Button
|
|
|
+ onClick={handleCopySettingClick}
|
|
|
+ size="small"
|
|
|
+ style={{
|
|
|
+ height: 24,
|
|
|
+ borderRadius: 4,
|
|
|
+ fontSize: 14,
|
|
|
+ backgroundColor: '#f5f7fa',
|
|
|
+ borderColor: '#d9d9d9',
|
|
|
+ color: '#17181a',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 复制设置
|
|
|
+ </Button>
|
|
|
+ <CheckResultUpDataActBtn record={{}} type="ADD" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <KCIMTable
|
|
|
+ actionRef={checkResultTableRef}
|
|
|
+ columns={checkResultColumns}
|
|
|
+ request={getCheckResultTableData}
|
|
|
+ params={{ keywords: checkResultSearchKeywords }}
|
|
|
+ rowKey="id"
|
|
|
+ size="middle"
|
|
|
+ manualRequest={true}
|
|
|
+ search={false}
|
|
|
+ pagination={{
|
|
|
+ showTotal: (total, range) =>
|
|
|
+ `第 ${range[0]}-${range[1]} 条/共计 ${total} 条`,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showQuickJumper: true,
|
|
|
+ defaultPageSize: 10,
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </TabPane>
|
|
|
+ <TabPane tab="查核要点" key="points">
|
|
|
+ <div className="tab-content">
|
|
|
+ <div className="toolbar">
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ gap: 8,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span style={{ color: '#17181A', fontSize: 14 }}>
|
|
|
+ 检索:
|
|
|
+ </span>
|
|
|
+ <Input
|
|
|
+ placeholder="查核要点"
|
|
|
+ allowClear
|
|
|
+ suffix={
|
|
|
+ <SearchOutlined style={{ color: '#99A6BF' }} />
|
|
|
+ }
|
|
|
+ style={{ width: 250 }}
|
|
|
+ value={checkPointSearchKeywords}
|
|
|
+ onChange={(e) => {
|
|
|
+ set_checkPointSearchKeywords(e.target.value);
|
|
|
+ if (e.target.value === '') {
|
|
|
+ checkPointTableRef.current?.reload();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onPressEnter={(e) => {
|
|
|
+ set_checkPointSearchKeywords(e.currentTarget.value);
|
|
|
+ checkPointTableRef.current?.reload();
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ className="add-btn"
|
|
|
+ size="small"
|
|
|
+ onClick={handleAddCheckPointClick}
|
|
|
+ >
|
|
|
+ 添加
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <KCIMTable
|
|
|
+ actionRef={checkPointTableRef}
|
|
|
+ columns={checkPointColumns}
|
|
|
+ request={getCheckPointTableData}
|
|
|
+ params={{ keywords: checkPointSearchKeywords }}
|
|
|
+ rowKey="id"
|
|
|
+ size="middle"
|
|
|
+ manualRequest={true}
|
|
|
+ search={false}
|
|
|
+ pagination={{
|
|
|
+ showTotal: (total, range) =>
|
|
|
+ `第 ${range[0]}-${range[1]} 条/共计 ${total} 条`,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showQuickJumper: true,
|
|
|
+ defaultPageSize: 10,
|
|
|
+ }}
|
|
|
+ rowSelection={{
|
|
|
+ selectedRowKeys: selectedCheckPointTableKeys,
|
|
|
+ onChange: (selectedRowKeys) => {
|
|
|
+ set_selectedCheckPointTableKeys(selectedRowKeys as string[]);
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ tableAlertRender={false}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 批量操作栏 */}
|
|
|
+ {selectedCheckPointTableKeys.length > 0 && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ left: actionBarStyle.left,
|
|
|
+ width: actionBarStyle.width,
|
|
|
+ bottom: 16,
|
|
|
+ height: 56,
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ zIndex: 999,
|
|
|
+ boxShadow: 'rgba(0, 0, 0, 0.04) 0px -2px 8px',
|
|
|
+ borderRadius: 4,
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'space-between',
|
|
|
+ padding: '0 16px',
|
|
|
+ transition: 'left 0.2s, width 0.2s',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span style={{ color: '#3377FF', fontWeight: 500 }}>
|
|
|
+ 已选 {selectedCheckPointTableKeys.length} 项
|
|
|
+ </span>
|
|
|
+ <div style={{ display: 'flex', gap: 8 }}>
|
|
|
+ <Button
|
|
|
+ onClick={() => {
|
|
|
+ set_selectedCheckPointTableKeys([]);
|
|
|
+ }}
|
|
|
+ style={{ height: 32, borderRadius: 4, fontSize: 14 }}
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ 取消选择
|
|
|
+ </Button>
|
|
|
+ <Popconfirm
|
|
|
+ title={`删除${selectedCheckPointTableKeys.length} 个查核要点?`}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ onConfirm={handleBatchDeleteCheckPoints}
|
|
|
+ placement="top"
|
|
|
+ getPopupContainer={(triggerNode) => triggerNode.parentElement || document.body}
|
|
|
+ >
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ style={{
|
|
|
+ background: '#3377FF',
|
|
|
+ borderColor: '#3377FF',
|
|
|
+ height: 32,
|
|
|
+ borderRadius: 4,
|
|
|
+ fontSize: 14,
|
|
|
+ }}
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ 批量删除
|
|
|
+ </Button>
|
|
|
+ </Popconfirm>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </TabPane>
|
|
|
+ </Tabs>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 新增查核项弹窗 */}
|
|
|
+ <ModalForm
|
|
|
+ title="新增查核项"
|
|
|
+ width={352}
|
|
|
+ visible={addCheckItemVisible}
|
|
|
+ onVisibleChange={set_addCheckItemVisible}
|
|
|
+ formRef={addCheckItemFormRef}
|
|
|
+ onFinish={handleAddCheckItemSubmit}
|
|
|
+ initialValues={{
|
|
|
+ subtotal: 1,
|
|
|
+ moreDeduction: 0,
|
|
|
+ score: 0,
|
|
|
+ rightAnswerText: '',
|
|
|
+ }}
|
|
|
+ modalProps={{
|
|
|
+ destroyOnClose: true,
|
|
|
+ maskClosable: false,
|
|
|
+ className: 'add-check-item-modal',
|
|
|
+ bodyStyle: {
|
|
|
+ maxHeight: '60vh',
|
|
|
+ overflowY: 'auto',
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ProFormText
|
|
|
+ name="name"
|
|
|
+ label="查核项"
|
|
|
+ placeholder="请输入"
|
|
|
+ rules={[{ required: true, message: '请输入查核项' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormRadio.Group
|
|
|
+ name="subtotal"
|
|
|
+ label="统计小计"
|
|
|
+ options={[
|
|
|
+ { label: '是', value: 1 },
|
|
|
+ { label: '否', value: 0 },
|
|
|
+ ]}
|
|
|
+ rules={[{ required: true, message: '请选择是否统计小计' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormRadio.Group
|
|
|
+ name="moreDeduction"
|
|
|
+ label="缺陷项类型"
|
|
|
+ options={[
|
|
|
+ { label: '单选', value: 0 },
|
|
|
+ { label: '多选', value: 1 },
|
|
|
+ ]}
|
|
|
+ rules={[{ required: true, message: '请选择缺陷项类型' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormSelect
|
|
|
+ name="checkModeList"
|
|
|
+ label="查核方式"
|
|
|
+ placeholder="请选择"
|
|
|
+ options={checkModeOptions}
|
|
|
+ rules={[{ required: true, message: '请选择查核方式' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormDigit
|
|
|
+ name="score"
|
|
|
+ label="分值"
|
|
|
+ placeholder="0"
|
|
|
+ min={0}
|
|
|
+ max={100}
|
|
|
+ fieldProps={{ precision: 1 }}
|
|
|
+ rules={[{ required: true, message: '请输入分值' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormTextArea
|
|
|
+ name="rightAnswerText"
|
|
|
+ label="应知应会"
|
|
|
+ placeholder="请输入"
|
|
|
+ fieldProps={{
|
|
|
+ rows: 3,
|
|
|
+ maxLength: 500,
|
|
|
+ showCount: true,
|
|
|
+ }}
|
|
|
+ rules={[{ required: true, message: '请输入应知应会内容' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormUploadButton
|
|
|
+ name="rightAnswerImage"
|
|
|
+ label="附加图片"
|
|
|
+ max={10}
|
|
|
+ fieldProps={{
|
|
|
+ accept: 'image/*',
|
|
|
+ listType: 'picture',
|
|
|
+ multiple: true,
|
|
|
+ beforeUpload: () => {
|
|
|
+ // 阻止自动上传,只做本地预览
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ title="点击选择图片"
|
|
|
+ />
|
|
|
+ </ModalForm>
|
|
|
+
|
|
|
+ {/* 编辑查核项弹窗 */}
|
|
|
+ <ModalForm
|
|
|
+ title="编辑查核项"
|
|
|
+ width={352}
|
|
|
+ visible={editCheckItemVisible}
|
|
|
+ onVisibleChange={set_editCheckItemVisible}
|
|
|
+ formRef={addCheckItemFormRef}
|
|
|
+ onFinish={async (values) => {
|
|
|
+ // 健壮处理图片字段,兼容字符串和数组两种情况
|
|
|
+ let rightAnswerImage = '';
|
|
|
+ if (typeof values.rightAnswerImage === 'string') {
|
|
|
+ // 用户未编辑图片,直接用原字符串
|
|
|
+ rightAnswerImage = values.rightAnswerImage;
|
|
|
+ } else if (Array.isArray(values.rightAnswerImage)) {
|
|
|
+ // 用户编辑过图片,处理为字符串
|
|
|
+ const uploadedUrls = [];
|
|
|
+ for (const file of values.rightAnswerImage) {
|
|
|
+ if (file.originFileObj) {
|
|
|
+ // 新上传的图片,需上传
|
|
|
+ try {
|
|
|
+ const res = await uploadFiles([file]);
|
|
|
+ if (res && res.length > 0) {
|
|
|
+ uploadedUrls.push(res[0]);
|
|
|
+ }
|
|
|
+ } catch (uploadError) {
|
|
|
+ console.error('编辑时图片上传失败:', uploadError);
|
|
|
+ message.error('图片上传失败,请重试!');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else if (file.url) {
|
|
|
+ // 已有图片
|
|
|
+ uploadedUrls.push(file.url);
|
|
|
+ } else if (file.response?.data?.downUrl) {
|
|
|
+ // 兼容已上传格式
|
|
|
+ uploadedUrls.push(file.response.data.downUrl);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ rightAnswerImage = uploadedUrls.join(',');
|
|
|
+ } else {
|
|
|
+ // 兜底
|
|
|
+ rightAnswerImage = '';
|
|
|
+ }
|
|
|
+ // 组装最终提交数据
|
|
|
+ const submitData = {
|
|
|
+ ...values,
|
|
|
+ rightAnswerImage,
|
|
|
+ };
|
|
|
+ return handleEditCheckItemSubmit(submitData);
|
|
|
+ }}
|
|
|
+ initialValues={
|
|
|
+ currentEditItem
|
|
|
+ ? {
|
|
|
+ name: currentEditItem.name,
|
|
|
+ subtotal: currentEditItem.subtotal,
|
|
|
+ moreDeduction: currentEditItem.moreDeduction,
|
|
|
+ checkModeList: currentEditItem.checkModeList,
|
|
|
+ score: currentEditItem.score,
|
|
|
+ rightAnswerText: currentEditItem.rightAnswerText,
|
|
|
+ // 直接传字符串,回显由convertValue处理
|
|
|
+ rightAnswerImage: currentEditItem.rightAnswerImage || ''
|
|
|
+ }
|
|
|
+ : {}
|
|
|
+ }
|
|
|
+ modalProps={{
|
|
|
+ destroyOnClose: true,
|
|
|
+ maskClosable: false,
|
|
|
+ className: 'add-check-item-modal',
|
|
|
+ bodyStyle: {
|
|
|
+ maxHeight: '60vh',
|
|
|
+ overflowY: 'auto',
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ProFormText
|
|
|
+ name="name"
|
|
|
+ label="查核项"
|
|
|
+ placeholder="请输入"
|
|
|
+ rules={[{ required: true, message: '请输入查核项' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormRadio.Group
|
|
|
+ name="subtotal"
|
|
|
+ label="统计小计"
|
|
|
+ options={[
|
|
|
+ { label: '是', value: 1 },
|
|
|
+ { label: '否', value: 0 },
|
|
|
+ ]}
|
|
|
+ rules={[{ required: true, message: '请选择是否统计小计' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormRadio.Group
|
|
|
+ name="moreDeduction"
|
|
|
+ label="缺陷项类型"
|
|
|
+ options={[
|
|
|
+ { label: '单选', value: 0 },
|
|
|
+ { label: '多选', value: 1 },
|
|
|
+ ]}
|
|
|
+ rules={[{ required: true, message: '请选择缺陷项类型' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormSelect
|
|
|
+ name="checkModeList"
|
|
|
+ label="查核方式"
|
|
|
+ placeholder="请选择"
|
|
|
+ options={checkModeOptions}
|
|
|
+ rules={[{ required: true, message: '请选择查核方式' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormDigit
|
|
|
+ name="score"
|
|
|
+ label="分值"
|
|
|
+ placeholder="0"
|
|
|
+ min={0}
|
|
|
+ max={100}
|
|
|
+ fieldProps={{ precision: 1 }}
|
|
|
+ rules={[{ required: true, message: '请输入分值' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormTextArea
|
|
|
+ name="rightAnswerText"
|
|
|
+ label="应知应会"
|
|
|
+ placeholder="请输入"
|
|
|
+ fieldProps={{
|
|
|
+ rows: 3,
|
|
|
+ maxLength: 500,
|
|
|
+ showCount: true,
|
|
|
+ }}
|
|
|
+ rules={[{ required: true, message: '请输入应知应会内容' }]}
|
|
|
+ />
|
|
|
+
|
|
|
+ <ProFormUploadButton
|
|
|
+ name="rightAnswerImage"
|
|
|
+ label="附加图片"
|
|
|
+ max={10}
|
|
|
+ convertValue={(value) => {
|
|
|
+ // 编辑时:将URL字符串转换为文件列表格式用于回显
|
|
|
+ if (typeof value === 'string' && value.trim()) {
|
|
|
+ const urls = value.split(',').filter((url) => url.trim());
|
|
|
+ return urls.map((url, index) => {
|
|
|
+ const trimmedUrl = url.trim();
|
|
|
+ let fileName = `图片${index + 1}`;
|
|
|
+ const uploadIndex = trimmedUrl.indexOf('upload/');
|
|
|
+ if (uploadIndex !== -1) {
|
|
|
+ const encodedFileName = trimmedUrl.substring(uploadIndex + 7);
|
|
|
+ try {
|
|
|
+ fileName = decodeURIComponent(encodedFileName);
|
|
|
+ } catch (error) {
|
|
|
+ fileName = encodedFileName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ uid: `existing-${index}`,
|
|
|
+ name: fileName,
|
|
|
+ status: 'done',
|
|
|
+ url: trimmedUrl,
|
|
|
+ response: {
|
|
|
+ data: {
|
|
|
+ downUrl: trimmedUrl,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return value || [];
|
|
|
+ }}
|
|
|
+ fieldProps={{
|
|
|
+ accept: 'image/*',
|
|
|
+ listType: 'picture',
|
|
|
+ multiple: true,
|
|
|
+ beforeUpload: () => {
|
|
|
+ // 阻止自动上传,只做本地预览
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ title="点击选择图片"
|
|
|
+ />
|
|
|
+ </ModalForm>
|
|
|
+
|
|
|
+ {/* 复制设置弹窗 */}
|
|
|
+ <Modal
|
|
|
+ title={`复制查核项设置 (${selectedTreeNode?.name || selectedTreeNode?.title || '未选择'})`}
|
|
|
+ width={700}
|
|
|
+ open={copySettingVisible}
|
|
|
+ onCancel={() => set_copySettingVisible(false)}
|
|
|
+ footer={[
|
|
|
+ <Button key="cancel" onClick={() => set_copySettingVisible(false)}>
|
|
|
+ 取消
|
|
|
+ </Button>,
|
|
|
+ <Button
|
|
|
+ key="submit"
|
|
|
+ type="primary"
|
|
|
+ onClick={handleCopySettingConfirm}
|
|
|
+ disabled={selectedCopyItems.length === 0}
|
|
|
+ style={{
|
|
|
+ backgroundColor: '#3377FF',
|
|
|
+ borderColor: '#3377FF',
|
|
|
+ color: '#FFFFFF',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 确定
|
|
|
+ {selectedCopyItems.length > 0 ? ` (${selectedCopyItems.length})` : ''}
|
|
|
+ </Button>,
|
|
|
+ ]}
|
|
|
+ destroyOnClose
|
|
|
+ className="copy-setting-modal"
|
|
|
+ >
|
|
|
+ <div style={{ marginBottom: 8 }}>
|
|
|
+ <Input
|
|
|
+ placeholder="请输入"
|
|
|
+ suffix={<SearchOutlined style={{ color: '#99A6BF' }} />}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ value={copySearchKeywords}
|
|
|
+ onChange={handleCopySearchChange}
|
|
|
+ onPressEnter={handleCopySearchConfirm}
|
|
|
+ allowClear
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <KCIMTable
|
|
|
+ actionRef={copySettingTableRef}
|
|
|
+ columns={[
|
|
|
+ {
|
|
|
+ title: (
|
|
|
+ <Checkbox
|
|
|
+ checked={copySettingTableData.length > 0 && selectedCopyItems.length === copySettingTableData.length}
|
|
|
+ indeterminate={selectedCopyItems.length > 0 && selectedCopyItems.length < copySettingTableData.length}
|
|
|
+ onChange={e => {
|
|
|
+ if (e.target.checked) {
|
|
|
+ set_selectedCopyItems(copySettingTableData.map((item: any) => String(item.id)));
|
|
|
+ } else {
|
|
|
+ set_selectedCopyItems([]);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ dataIndex: 'selected',
|
|
|
+ width: 50,
|
|
|
+ align: 'center' as 'center',
|
|
|
+ render: (_: any, record: any) => (
|
|
|
+ <Checkbox
|
|
|
+ checked={selectedCopyItems.includes(String(record.id))}
|
|
|
+ onChange={e => {
|
|
|
+ if (e.target.checked) {
|
|
|
+ set_selectedCopyItems(prev => [...prev, String(record.id)]);
|
|
|
+ } else {
|
|
|
+ set_selectedCopyItems(prev => prev.filter(id => id !== String(record.id)));
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '查核项',
|
|
|
+ dataIndex: 'name',
|
|
|
+ align: 'left' as 'left',
|
|
|
+ ellipsis: true,
|
|
|
+ render: (text) => <span>{text}</span>,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '应知应会',
|
|
|
+ dataIndex: 'rightAnswerText',
|
|
|
+ align: 'left' as 'left',
|
|
|
+ ellipsis: true,
|
|
|
+ render: (text) => text || '无相关物品...',
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ request={async (params) => {
|
|
|
+ const data = await getCopySettingTableData(params);
|
|
|
+ // 记录当前表格数据用于全选判断
|
|
|
+ if (data && typeof data === 'object' && 'data' in data) {
|
|
|
+ set_copySettingTableData(data.data || []);
|
|
|
+ } else {
|
|
|
+ set_copySettingTableData([]);
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ }}
|
|
|
+ params={{ keywords: copySearchKeywords }}
|
|
|
+ rowKey="id"
|
|
|
+ search={false}
|
|
|
+ pagination={{
|
|
|
+ simple: true,
|
|
|
+ pageSize: 100,
|
|
|
+ size: 'small',
|
|
|
+ showTotal: () => null,
|
|
|
+ }}
|
|
|
+ tableAlertRender={false}
|
|
|
+ scroll={{ y: 360 }}
|
|
|
+ />
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ {/* 添加查核要点弹窗 */}
|
|
|
+ <Modal
|
|
|
+ title={`选择查核要点 (${selectedTreeNode?.name || selectedTreeNode?.title || ''})`}
|
|
|
+ width={700}
|
|
|
+ open={addCheckPointVisible}
|
|
|
+ onCancel={() => set_addCheckPointVisible(false)}
|
|
|
+ footer={[
|
|
|
+ <Button key="cancel" onClick={() => set_addCheckPointVisible(false)}>
|
|
|
+ 取消
|
|
|
+ </Button>,
|
|
|
+ <Button
|
|
|
+ key="submit"
|
|
|
+ type="primary"
|
|
|
+ onClick={handleAddCheckPointConfirm}
|
|
|
+ disabled={selectedCheckPoints.length === 0}
|
|
|
+ style={{
|
|
|
+ backgroundColor: '#3377FF',
|
|
|
+ borderColor: '#3377FF',
|
|
|
+ color: '#FFFFFF',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 确定
|
|
|
+ {selectedCheckPoints.length > 0 ? ` (${selectedCheckPoints.length})` : ''}
|
|
|
+ </Button>,
|
|
|
+ ]}
|
|
|
+ destroyOnClose
|
|
|
+ className="add-check-point-modal"
|
|
|
+ >
|
|
|
+ <div style={{ marginBottom: 8 }}>
|
|
|
+ <Input
|
|
|
+ placeholder="请输入"
|
|
|
+ suffix={<SearchOutlined style={{ color: '#99A6BF' }} />}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ value={addCheckPointSearchKeywords}
|
|
|
+ onChange={handleAddCheckPointSearchChange}
|
|
|
+ allowClear
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <KCIMTable
|
|
|
+ actionRef={useRef<ActionType>()}
|
|
|
+ columns={[
|
|
|
+ {
|
|
|
+ title: (
|
|
|
+ <Checkbox
|
|
|
+ checked={isAllSelected()}
|
|
|
+ indeterminate={isIndeterminate()}
|
|
|
+ onChange={(e) => handleSelectAll(e.target.checked)}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ dataIndex: 'selected',
|
|
|
+ width: 50,
|
|
|
+ align: 'center' as 'center',
|
|
|
+ render: (_, record) => (
|
|
|
+ <Checkbox
|
|
|
+ checked={selectedCheckPoints.includes(record.id)}
|
|
|
+ onChange={(e) => {
|
|
|
+ handleCheckPointSelection(record.id, e.target.checked);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '要点ID',
|
|
|
+ dataIndex: 'id',
|
|
|
+ width: 120,
|
|
|
+ align: 'left' as 'left',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '查核要点名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ align: 'left' as 'left',
|
|
|
+ ellipsis: true,
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ dataSource={getFilteredPendingCheckPoints()}
|
|
|
+ rowKey="id"
|
|
|
+ search={false}
|
|
|
+ pagination={{
|
|
|
+ simple: true,
|
|
|
+ pageSize: 10,
|
|
|
+ size: 'small',
|
|
|
+ showTotal: () => null,
|
|
|
+ }}
|
|
|
+ tableAlertRender={false}
|
|
|
+ scroll={{ y: 360 }}
|
|
|
+ />
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ {/* 全局图片预览遮罩层 */}
|
|
|
+ {imagePreviewVisible && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ right: 0,
|
|
|
+ bottom: 0,
|
|
|
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
|
+ zIndex: 9999,
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ cursor: 'pointer'
|
|
|
+ }}
|
|
|
+ onClick={() => set_imagePreviewVisible(false)}
|
|
|
+ >
|
|
|
+ {(() => {
|
|
|
+ const imageUrls = parseImageUrls(selectedTreeNode?.rightAnswerImage);
|
|
|
+ if (imageUrls.length === 0) return null;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ position: 'relative',
|
|
|
+ maxWidth: '90vw',
|
|
|
+ maxHeight: '90vh',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center'
|
|
|
+ }}
|
|
|
+ onClick={(e) => e.stopPropagation()} // 阻止点击图片区域关闭
|
|
|
+ >
|
|
|
+ {/* 当前图片 */}
|
|
|
+ <img
|
|
|
+ src={imageUrls[imagePreviewIndex]}
|
|
|
+ alt={`图片 ${imagePreviewIndex + 1}`}
|
|
|
+ style={{
|
|
|
+ maxWidth: '70%',
|
|
|
+ maxHeight: '70%',
|
|
|
+ objectFit: 'contain',
|
|
|
+ borderRadius: '8px'
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 固定位置的控制按钮 */}
|
|
|
+ {/* 关闭按钮 - 固定在右上角 */}
|
|
|
+ <img
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ set_imagePreviewVisible(false);
|
|
|
+ }}
|
|
|
+ src={guanbiIcon}
|
|
|
+ alt="关闭"
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ top: '20px',
|
|
|
+ right: '20px',
|
|
|
+ width: '40px',
|
|
|
+ height: '40px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ zIndex: 10001
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 图片导航 */}
|
|
|
+ {imageUrls.length > 1 && (
|
|
|
+ <>
|
|
|
+ {/* 左箭头 - 固定在屏幕左边 */}
|
|
|
+ <img
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ // 循环切换:如果是第一张,跳转到最后一张;否则跳转到上一张
|
|
|
+ const nextIndex = imagePreviewIndex === 0 ? imageUrls.length - 1 : imagePreviewIndex - 1;
|
|
|
+ set_imagePreviewIndex(nextIndex);
|
|
|
+ }}
|
|
|
+ src={shangyiyeIcon}
|
|
|
+ alt="上一页"
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ left: '20px',
|
|
|
+ top: '50%',
|
|
|
+ transform: 'translateY(-50%)',
|
|
|
+ width: '80px',
|
|
|
+ height: '160px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ zIndex: 10001
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 右箭头 - 固定在屏幕右边 */}
|
|
|
+ <img
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ // 循环切换:如果是最后一张,跳转到第一张;否则跳转到下一张
|
|
|
+ const nextIndex = imagePreviewIndex === imageUrls.length - 1 ? 0 : imagePreviewIndex + 1;
|
|
|
+ set_imagePreviewIndex(nextIndex);
|
|
|
+ }}
|
|
|
+ src={xiayiyeIcon}
|
|
|
+ alt="下一页"
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ right: '20px',
|
|
|
+ top: '50%',
|
|
|
+ transform: 'translateY(-50%)',
|
|
|
+ width: '80px',
|
|
|
+ height: '160px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ zIndex: 10001
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 页码指示器 - 固定在底部 */}
|
|
|
+ <div
|
|
|
+ onClick={(e) => e.stopPropagation()}
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ bottom: '20px',
|
|
|
+ left: '50%',
|
|
|
+ transform: 'translateX(-50%)',
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
|
+ color: '#333',
|
|
|
+ padding: '8px 16px',
|
|
|
+ borderRadius: '20px',
|
|
|
+ fontSize: '14px',
|
|
|
+ fontWeight: 'normal',
|
|
|
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
|
|
+ zIndex: 10001
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {imagePreviewIndex + 1} / {imageUrls.length}
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ })()}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </KCIMPagecontainer>
|
|
|
+ );
|
|
|
+}
|