| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367 |
- /*
- * @Author: code4eat awesomedema@gmail.com
- * @Date: 2023-03-03 11:30:33
- * @LastEditors: code4eat awesomedema@gmail.com
- * @LastEditTime: 2025-12-15 11:18:35
- * @FilePath: /KC-MiddlePlatform/src/pages/platform/setting/pubDicTypeMana/index.tsx
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
- import React, { useEffect, useRef, useState } from 'react';
- import {
- Input,
- message,
- Card,
- Button,
- Modal,
- Tabs,
- Radio,
- Popconfirm,
- Dropdown,
- Menu,
- Checkbox,
- } from 'antd';
- import { ActionType, ProColumns } from '@ant-design/pro-table';
- import KCIMPagecontainer from '@/components/KCIMPageContainer';
- import { KCIMTable } from '@/components/KCIMTable';
- import {
- ModalForm,
- ProFormText,
- ProFormTextArea,
- ProFormSelect,
- } from '@ant-design/pro-form';
- import { PlusOutlined, SearchOutlined, MoreOutlined } from '@ant-design/icons';
- import { createFromIconfontCN } from '@ant-design/icons';
- import './style.less';
- import {
- getChackGroupData,
- addData,
- editData,
- delData,
- getCheckGroupEmployees,
- addCheckGroupEmployee,
- delCheckGroupEmployee,
- getAvailableEmployees,
- setGroupManager,
- getCheckGroupCheckpoints,
- getGroupPendingCheckpoints,
- addCheckGroupCheckpoints,
- delCheckGroupCheckpoints,
- } from './service';
- const IconFont = createFromIconfontCN({
- scriptUrl: '',
- });
- // 添加查核要点弹窗组件
- const AddCheckpointModal: React.FC<{
- selectedGroup: any;
- onSuccess: () => void;
- }> = ({ selectedGroup, onSuccess }) => {
- const [modalVisible, setModalVisible] = useState(false);
- const [selectedCheckpointIds, setSelectedCheckpointIds] = useState<
- React.Key[]
- >([]);
- const [searchKeyword, setSearchKeyword] = useState('');
- const [allCheckpointSelectData, setAllCheckpointSelectData] = useState<any[]>(
- [],
- );
- const checkpointSelectTableRef = useRef<ActionType>();
- // 获取可添加的查核要点列表
- const getCheckpointSelectData = async (params: any) => {
- if (!selectedGroup?.id) {
- return {
- data: [],
- success: true,
- total: 0,
- };
- }
- try {
- const { current = 1, pageSize = 10 } = params;
- const res = await getGroupPendingCheckpoints(
- selectedGroup.id,
- searchKeyword,
- current,
- pageSize,
- );
- // 保存所有数据,用于后续选择时获取完整信息
- if (current === 1) {
- setAllCheckpointSelectData(res.list || []);
- } else {
- setAllCheckpointSelectData((prev) => [...prev, ...(res.list || [])]);
- }
- return {
- data: res.list || [],
- success: res.success,
- total: res.total || 0,
- };
- } catch (error) {
- return {
- data: [],
- success: false,
- total: 0,
- };
- }
- };
- // 打开弹窗时重置状态
- const handleOpenModal = () => {
- setModalVisible(true);
- setSelectedCheckpointIds([]);
- setSearchKeyword('');
- setAllCheckpointSelectData([]);
- // 延迟触发表格重新加载
- setTimeout(() => {
- checkpointSelectTableRef.current?.reload();
- }, 100);
- };
- // 提交添加
- const handleSubmit = async () => {
- if (selectedCheckpointIds.length === 0) {
- message.warning('请至少选择一个查核要点');
- return;
- }
- try {
- // 直接按“要点ID”提交(与接口分页口径一致)
- const selectedIds = Array.from(
- new Set(
- selectedCheckpointIds
- .map((k) => Number(k))
- .filter((n) => !Number.isNaN(n)),
- ),
- ) as number[];
- const result = await addCheckGroupCheckpoints(
- selectedGroup.id,
- selectedIds,
- );
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success(`已添加 ${selectedIds.length} 个查核要点`);
- setModalVisible(false);
- onSuccess(); // 调用成功回调
- } catch (error) {
- message.error('添加失败');
- }
- };
- // 定义表格列
- const checkpointSelectColumns: ProColumns[] = [
- {
- title: '要点名称',
- dataIndex: 'name',
- width: 260,
- align: 'left' as 'left',
- },
- {
- title: '查核项数',
- dataIndex: 'checkItemList',
- width: 100,
- align: 'center' as 'center',
- render: (_: any, record: any) =>
- Array.isArray(record?.checkItemList) ? record.checkItemList.length : 0,
- },
- ];
- return (
- <>
- <Button
- type="primary"
- className="add-btn"
- size="small"
- onClick={handleOpenModal}
- >
- 添加
- </Button>
- <Modal
- title={`选择查核要点(${selectedGroup?.name || ''})`}
- open={modalVisible}
- onCancel={() => setModalVisible(false)}
- width={700}
- footer={[
- <Button key="cancel" onClick={() => setModalVisible(false)}>
- 取消
- </Button>,
- <Button
- key="submit"
- type="primary"
- onClick={handleSubmit}
- disabled={selectedCheckpointIds.length === 0}
- style={{
- backgroundColor: '#3377FF',
- borderColor: '#3377FF',
- color: '#FFFFFF',
- }}
- >
- 确定
- {selectedCheckpointIds.length > 0
- ? ` (${selectedCheckpointIds.length})`
- : ''}
- </Button>,
- ]}
- className="checkpoint-select-modal"
- >
- <div style={{ marginBottom: 8 }}>
- <Input
- placeholder="要点名称"
- value={searchKeyword}
- allowClear
- onChange={(e) => {
- setSearchKeyword(e.target.value);
- if (e.target.value === '') {
- // 清空搜索时自动刷新
- checkpointSelectTableRef.current?.reload();
- }
- }}
- onPressEnter={() => checkpointSelectTableRef.current?.reload()}
- suffix={
- <SearchOutlined
- onClick={() => checkpointSelectTableRef.current?.reload()}
- style={{ cursor: 'pointer', color: '#99A6BF' }}
- />
- }
- style={{ width: '100%' }}
- />
- </div>
- <KCIMTable
- actionRef={checkpointSelectTableRef}
- columns={checkpointSelectColumns}
- request={getCheckpointSelectData}
- rowKey="id"
- bordered
- search={false}
- pagination={{
- simple: true,
- pageSize: 10,
- size: 'small',
- showTotal: () => null,
- }}
- rowSelection={{
- selectedRowKeys: selectedCheckpointIds,
- onChange: (selectedRowKeys) => {
- setSelectedCheckpointIds(selectedRowKeys);
- },
- preserveSelectedRowKeys: true,
- }}
- tableAlertRender={false}
- scroll={{ y: 360 }}
- expandable={{
- expandedRowRender: (record: any) => (
- <div style={{ paddingLeft: 24 }}>
- {Array.isArray(record?.checkItemList) &&
- record.checkItemList.length > 0 ? (
- <ul style={{ margin: 0, paddingLeft: 16 }}>
- {record.checkItemList.map((ci: any) => (
- <li key={ci.id} style={{ lineHeight: '22px' }}>
- {ci.name}
- </li>
- ))}
- </ul>
- ) : (
- <span>无明细</span>
- )}
- </div>
- ),
- }}
- />
- </Modal>
- </>
- );
- };
- // 添加人员弹窗组件
- const AddEmployeeModal: React.FC<{
- selectedGroup: any;
- onSuccess: () => void;
- }> = ({ selectedGroup, onSuccess }) => {
- const [modalVisible, setModalVisible] = useState(false);
- const [selectedEmployeeIds, setSelectedEmployeeIds] = useState<React.Key[]>(
- [],
- );
- const [selectedEmployeeData, setSelectedEmployeeData] = useState<any[]>([]);
- const [searchKeyword, setSearchKeyword] = useState('');
- const [allEmployeeData, setAllEmployeeData] = useState<any[]>([]);
- const employeeTableRef = useRef<ActionType>();
- // 获取可添加的人员列表
- const getEmployeeSelectData = async (params: any) => {
- if (!selectedGroup?.id) {
- return {
- data: [],
- success: true,
- total: 0,
- };
- }
- try {
- const { current = 1, pageSize = 10 } = params;
- const res = await getAvailableEmployees(
- selectedGroup.id,
- searchKeyword,
- current,
- pageSize,
- );
- // 保存所有数据,用于后续选择时获取完整信息
- if (current === 1) {
- setAllEmployeeData(res.list || []);
- } else {
- setAllEmployeeData((prev) => [...prev, ...(res.list || [])]);
- }
- return {
- data: res.list || [],
- success: res.success,
- total: res.total || 0,
- };
- } catch (error) {
- return {
- data: [],
- success: false,
- total: 0,
- };
- }
- };
- // 打开弹窗时重置状态
- const handleOpenModal = () => {
- setModalVisible(true);
- setSelectedEmployeeIds([]);
- setSelectedEmployeeData([]);
- setSearchKeyword('');
- setAllEmployeeData([]);
- // 延迟触发表格重新加载
- setTimeout(() => {
- employeeTableRef.current?.reload();
- }, 100);
- };
- // 提交添加
- const handleSubmit = async () => {
- if (selectedEmployeeIds.length === 0) {
- message.warning('请至少选择一个人员');
- return;
- }
- try {
- // 根据选中的ID获取完整的人员信息
- const selectedEmployees = allEmployeeData.filter((emp) =>
- selectedEmployeeIds.includes(emp.id),
- );
- const result = await addCheckGroupEmployee(
- selectedGroup.id,
- selectedEmployees,
- );
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success(`已添加 ${selectedEmployeeIds.length} 个人员`);
- setModalVisible(false);
- onSuccess(); // 调用成功回调
- } catch (error) {
- message.error('添加失败');
- }
- };
- // 定义表格列
- const employeeSelectColumns: ProColumns[] = [
- {
- title: '工号',
- dataIndex: 'code',
- width: 120,
- },
- {
- title: '姓名',
- dataIndex: 'name',
- width: 120,
- },
- ];
- return (
- <>
- <Button
- type="primary"
- className="add-btn"
- size="small"
- onClick={handleOpenModal}
- >
- 添加
- </Button>
- <Modal
- title={`选择人员(${selectedGroup?.name || ''})`}
- open={modalVisible}
- onCancel={() => setModalVisible(false)}
- width={352}
- footer={[
- <Button key="cancel" onClick={() => setModalVisible(false)}>
- 取消
- </Button>,
- <Button
- key="submit"
- type="primary"
- onClick={handleSubmit}
- disabled={selectedEmployeeIds.length === 0}
- style={{
- backgroundColor: '#3377FF',
- borderColor: '#3377FF',
- color: '#FFFFFF',
- }}
- >
- 确定
- {selectedEmployeeIds.length > 0
- ? ` (${selectedEmployeeIds.length})`
- : ''}
- </Button>,
- ]}
- className="employee-select-modal"
- >
- <div style={{ marginBottom: 8 }}>
- <Input
- placeholder="请输入姓名或工号"
- value={searchKeyword}
- allowClear
- onChange={(e) => {
- setSearchKeyword(e.target.value);
- if (e.target.value === '') {
- // 清空搜索时自动刷新
- employeeTableRef.current?.reload();
- }
- }}
- onPressEnter={() => employeeTableRef.current?.reload()}
- suffix={
- <SearchOutlined
- onClick={() => employeeTableRef.current?.reload()}
- style={{ cursor: 'pointer', color: '#99A6BF' }}
- />
- }
- style={{ width: '100%' }}
- />
- </div>
- <KCIMTable
- actionRef={employeeTableRef}
- columns={employeeSelectColumns}
- request={getEmployeeSelectData}
- rowKey="id"
- search={false}
- pagination={{
- simple: true,
- pageSize: 10,
- size: 'small',
- showTotal: () => null,
- }}
- rowSelection={{
- selectedRowKeys: selectedEmployeeIds,
- onChange: (selectedRowKeys) => {
- setSelectedEmployeeIds(selectedRowKeys);
- },
- }}
- tableAlertRender={false}
- scroll={{ y: 360 }}
- />
- </Modal>
- </>
- );
- };
- // 主页面组件
- const CheckGroupMana: React.FC = () => {
- // 左侧查核组数据
- const [groupData, setGroupData] = useState<any[]>([]);
- const [filteredGroupData, setFilteredGroupData] = useState<any[]>([]);
- const [selectedGroup, setSelectedGroup] = useState<any>(null);
- const [groupSearchKeywords, setGroupSearchKeywords] = useState<string>('');
- const [hoveredNode, setHoveredNode] = useState<string | null>(null);
- const [openDropdownNode, setOpenDropdownNode] = useState<string | null>(null);
- // 分页加载相关状态
- const [currentPage, setCurrentPage] = useState(1);
- const [totalCount, setTotalCount] = useState(0);
- const [loading, setLoading] = useState(false);
- const [hasMore, setHasMore] = useState(true);
- // 右侧人员表格相关
- const [employeeSearchKeywords, setEmployeeSearchKeywords] =
- useState<string>('');
- const [selectedEmployeeKeys, setSelectedEmployeeKeys] = useState<string[]>(
- [],
- );
- const [allEmployeeList, setAllEmployeeList] = useState<any[]>([]); // 存储原始组员数据
- const [filteredEmployeeList, setFilteredEmployeeList] = useState<any[]>([]); // 存储过滤后的组员数据
- const employeeTableRef = useRef<ActionType>();
- // 查核要点相关状态
- const [checkpointSearchKeywords, setCheckpointSearchKeywords] =
- useState<string>('');
- const [selectedCheckpointKeys, setSelectedCheckpointKeys] = useState<
- string[]
- >([]);
- const [allCheckpointList, setAllCheckpointList] = useState<any[]>([]); // 存储原始查核要点数据
- const [filteredCheckpointList, setFilteredCheckpointList] = useState<any[]>(
- [],
- ); // 存储过滤后的查核要点数据
- const checkpointTableRef = useRef<ActionType>();
- // 右侧内容区ref和操作栏定位状态
- const deptContentRef = useRef<HTMLDivElement>(null);
- const [actionBarStyle, setActionBarStyle] = useState<{
- left: number;
- width: number;
- }>({ left: 0, width: 0 });
- // 更新操作栏定位,支持ResizeObserver
- const updateActionBarPosition = () => {
- if (deptContentRef.current) {
- const rect = deptContentRef.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 (deptContentRef.current && typeof ResizeObserver !== 'undefined') {
- resizeObserver = new ResizeObserver(() => {
- updateActionBarPosition();
- });
- resizeObserver.observe(deptContentRef.current);
- }
- return () => {
- window.removeEventListener('resize', updateActionBarPosition);
- window.removeEventListener('scroll', updateActionBarPosition, true);
- if (resizeObserver && deptContentRef.current) {
- resizeObserver.unobserve(deptContentRef.current);
- resizeObserver.disconnect();
- }
- };
- }, []);
- // 获取左侧查核组数据
- useEffect(() => {
- fetchGroupData();
- }, []);
- const fetchGroupData = async (
- page: number = 1,
- isLoadMore: boolean = false,
- keyword?: string,
- ) => {
- try {
- setLoading(true);
- // 如果传入了keyword参数,使用它;否则使用状态中的groupSearchKeywords
- const searchKeyword =
- keyword !== undefined ? keyword : groupSearchKeywords;
- const res = await getChackGroupData({
- current: page,
- pageSize: 100,
- groupName: searchKeyword,
- });
- if (res) {
- // 转换数据格式,符合树形组件需要的格式,同时保留原始数据
- const transformedData = res.list.map((item: any) => ({
- title: item.name,
- key: item.id,
- type: 'checkGroup', // 标记为查核组
- // 保留原始数据
- id: item.id,
- name: item.name,
- groupManagerName: item.groupManagerName,
- remark: item.remark,
- createUserName: item.createUserName,
- }));
- if (isLoadMore) {
- // 加载更多时,追加到现有数据
- setGroupData((prev) => [...prev, ...transformedData]);
- setFilteredGroupData((prev) => [...prev, ...transformedData]);
- } else {
- // 首次加载或搜索时,替换数据
- setGroupData(transformedData);
- setFilteredGroupData(transformedData);
- // 默认选中第一个
- if (transformedData.length > 0) {
- setSelectedGroup(transformedData[0]);
- }
- }
- // 更新分页信息
- setTotalCount(res.totalCount);
- setCurrentPage(page);
- setHasMore(res.list.length === 100 && page * 100 < res.totalCount);
- }
- } catch (error) {
- message.error('获取查核组数据失败');
- } finally {
- setLoading(false);
- }
- };
- // 本地过滤组员数据
- const filterEmployeeData = (data: any[], keyword: string) => {
- if (!keyword.trim()) {
- return data;
- }
- return data.filter(
- (emp) =>
- emp.employeeId?.toString().includes(keyword) ||
- emp.employeeName?.includes(keyword),
- );
- };
- // 监听搜索关键词变化,进行本地过滤
- useEffect(() => {
- const filtered = filterEmployeeData(
- allEmployeeList,
- employeeSearchKeywords,
- );
- setFilteredEmployeeList(filtered);
- }, [allEmployeeList, employeeSearchKeywords]);
- // 监听选中项变化,加载组员数据和查核要点数据
- useEffect(() => {
- if (selectedGroup?.key) {
- loadEmployeeData();
- loadCheckpointData();
- } else {
- setAllEmployeeList([]);
- setFilteredEmployeeList([]);
- setAllCheckpointList([]);
- setFilteredCheckpointList([]);
- }
- }, [selectedGroup?.key]);
- // 加载组员数据
- const loadEmployeeData = async () => {
- if (!selectedGroup?.id) return;
- try {
- const result = await getCheckGroupEmployees({
- groupId: selectedGroup.id,
- });
- if (result.success) {
- setAllEmployeeList(result.data || []);
- }
- } catch (error) {
- console.error('加载组员数据失败:', error);
- setAllEmployeeList([]);
- }
- };
- // 本地过滤查核要点数据
- const filterCheckpointData = (data: any[], keyword: string) => {
- if (!keyword.trim()) {
- return data;
- }
- return data.filter(
- (item) =>
- item.pointName?.includes(keyword) ||
- item.checkItemName?.includes(keyword),
- );
- };
- // 监听查核要点搜索关键词变化,进行本地过滤
- useEffect(() => {
- const filtered = filterCheckpointData(
- allCheckpointList,
- checkpointSearchKeywords,
- );
- setFilteredCheckpointList(filtered);
- }, [allCheckpointList, checkpointSearchKeywords]);
- // 加载查核要点数据
- const loadCheckpointData = async () => {
- if (!selectedGroup?.id) return;
- try {
- const result = await getCheckGroupCheckpoints({
- groupId: selectedGroup.id,
- });
- if (result.success) {
- setAllCheckpointList(result.data || []);
- }
- } catch (error) {
- console.error('加载查核要点数据失败:', error);
- setAllCheckpointList([]);
- }
- };
- // 处理查核组搜索 - 点击搜索图标或回车时触发
- const handleGroupSearch = () => {
- setCurrentPage(1);
- setHasMore(true);
- fetchGroupData(1, false);
- };
- // 处理搜索输入框的变化
- const handleSearchInputChange = (value: string) => {
- setGroupSearchKeywords(value);
- // 如果清空搜索,立即重新加载数据,传入空字符串确保清空搜索
- if (!value.trim()) {
- setCurrentPage(1);
- setHasMore(true);
- // 直接传入空字符串,而不是依赖状态更新
- fetchGroupData(1, false, '');
- }
- };
- // 加载更多数据
- const loadMoreData = () => {
- if (!loading && hasMore) {
- const nextPage = currentPage + 1;
- fetchGroupData(nextPage, true);
- }
- };
- // 滚动事件处理
- const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
- const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
- // 当滚动到底部附近时加载更多数据
- if (scrollHeight - scrollTop <= clientHeight + 10) {
- loadMoreData();
- }
- };
- // 新增查核组弹窗组件
- const AddCheckGroupModal = () => {
- return (
- <ModalForm
- title="新增查核组"
- width={400}
- trigger={<Button icon={<PlusOutlined />} className="add-button" />}
- onFinish={async (val: any) => {
- try {
- await addData(val);
- message.success('新增成功!');
- fetchGroupData(1, false); // 重新获取数据
- return true;
- } catch (error) {
- message.error('新增失败');
- return false;
- }
- }}
- modalProps={{
- destroyOnClose: true,
- maskClosable: false,
- }}
- >
- <ProFormText
- name="name"
- label="查核组"
- placeholder="请输入"
- rules={[{ required: true, message: '查核组不能为空!' }]}
- />
- </ModalForm>
- );
- };
- // 更多操作菜单
- const getMoreMenu = (node: any) => (
- <Menu>
- <Menu.Item key="edit">
- <ModalForm
- title="编辑查核组"
- width={400}
- initialValues={{
- name: node.name,
- }}
- trigger={<span>编辑</span>}
- onFinish={async (val: any) => {
- try {
- await editData({
- id: node.id,
- name: val.name,
- });
- message.success('编辑成功!');
- fetchGroupData(1, false);
- return true;
- } catch (error) {
- message.error('编辑失败');
- return false;
- }
- }}
- modalProps={{ destroyOnClose: true }}
- >
- <ProFormText
- name="name"
- label="查核组"
- placeholder="请输入"
- rules={[{ required: true, message: '查核组不能为空!' }]}
- />
- </ModalForm>
- </Menu.Item>
- <Menu.Item key="delete">
- <a
- onClick={() => {
- // 删除确认
- Modal.confirm({
- title: '确认删除',
- content: `确定要删除查核组"${node.name}"吗?`,
- okText: '确定',
- cancelText: '取消',
- onOk: async () => {
- try {
- await delData(node.id);
- message.success('删除成功!');
- fetchGroupData(1, false);
- // 如果删除的是当前选中的组,清空选中状态
- if (selectedGroup?.id === node.id) {
- setSelectedGroup(null);
- }
- } catch (error) {
- message.error('删除失败');
- }
- },
- });
- }}
- >
- 删除
- </a>
- </Menu.Item>
- </Menu>
- );
- // 左侧查核组树形组件渲染
- const renderGroupTree = () => {
- return (
- <div className="dept-tree">
- <div className="search-wrapper">
- <Input
- placeholder="查核组"
- suffix={
- <SearchOutlined
- style={{ color: '#99A6BF', cursor: 'pointer' }}
- onClick={handleGroupSearch}
- />
- }
- className="search-input"
- value={groupSearchKeywords}
- onChange={(e) => handleSearchInputChange(e.target.value)}
- onPressEnter={handleGroupSearch}
- allowClear
- />
- <AddCheckGroupModal />
- </div>
- <div className="dept-list" onScroll={handleScroll}>
- {filteredGroupData.map((item) => (
- <div key={item.key} className="dept-group">
- <div
- className={`dept-leaf ${
- selectedGroup?.key === item.key ? 'selected' : ''
- }`}
- onClick={() => {
- setSelectedGroup(item);
- }}
- onMouseEnter={() => setHoveredNode(item.key)}
- onMouseLeave={() => setHoveredNode(null)}
- >
- <span>{item.title}</span>
- {(selectedGroup?.key === item.key ||
- hoveredNode === item.key ||
- openDropdownNode === item.key) && (
- <Dropdown
- overlay={getMoreMenu(item)}
- trigger={['click']}
- placement="bottomRight"
- overlayClassName="check-unit-add-dropdown"
- onVisibleChange={(visible) => {
- if (visible) {
- setOpenDropdownNode(item.key);
- } else {
- setOpenDropdownNode(null);
- }
- }}
- >
- <Button
- type="text"
- icon={<MoreOutlined />}
- className="more-button"
- onClick={(e) => e.stopPropagation()}
- />
- </Dropdown>
- )}
- </div>
- </div>
- ))}
- {/* 加载更多提示 */}
- {loading && (
- <div className="loading-more">
- <span>加载中...</span>
- </div>
- )}
- {!hasMore && filteredGroupData.length > 0 && (
- <div className="no-more-data">
- <span>已加载全部数据(共{totalCount}条)</span>
- </div>
- )}
- </div>
- </div>
- );
- };
- // 设置组长
- const handleSetManager = async (employeeId: number) => {
- if (!selectedGroup?.id) return;
- try {
- const result = await setGroupManager(selectedGroup.id, employeeId);
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success('设置组长成功!');
- // 只需要刷新组员表格数据
- loadEmployeeData();
- } catch (error) {
- message.error('设置组长失败');
- }
- };
- // 处理删除查核要点
- const handleDeleteCheckpoint = async (record: any) => {
- try {
- const result = await delCheckGroupCheckpoints([record.originalPointId]);
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success('删除要点成功');
- loadCheckpointData();
- } catch (error) {
- message.error('删除要点失败');
- }
- };
- // 计算全选状态
- const getAllSelectableKeys = () => {
- return filteredCheckpointList
- .filter((item) => item.rowSpan > 0) // 只获取合并行的ID
- .map((item) => item.id);
- };
- const allSelectableKeys = getAllSelectableKeys();
- const isAllSelected =
- allSelectableKeys.length > 0 &&
- allSelectableKeys.every((key) => selectedCheckpointKeys.includes(key));
- const isIndeterminate =
- selectedCheckpointKeys.length > 0 &&
- selectedCheckpointKeys.length < allSelectableKeys.length;
- // 处理全选/取消全选
- const handleSelectAll = (checked: boolean) => {
- if (checked) {
- setSelectedCheckpointKeys(allSelectableKeys);
- } else {
- setSelectedCheckpointKeys([]);
- }
- };
- // 查核要点表格列定义
- const checkpointColumns: ProColumns[] = [
- {
- title: (
- <div style={{ display: 'flex', alignItems: 'center' }}>
- <Checkbox
- checked={isAllSelected}
- indeterminate={isIndeterminate}
- onChange={(e) => handleSelectAll(e.target.checked)}
- />
- <span style={{ marginLeft: 8 }}>要点名称</span>
- </div>
- ),
- dataIndex: 'pointName',
- width: 200,
- align: 'left' as 'left',
- render: (dom: React.ReactNode, record: any) => {
- if (record.rowSpan > 0) {
- return {
- children: (
- <div style={{ display: 'flex', alignItems: 'center' }}>
- <Checkbox
- checked={selectedCheckpointKeys.includes(record.id)}
- onChange={() => {
- if (selectedCheckpointKeys.includes(record.id)) {
- setSelectedCheckpointKeys(
- selectedCheckpointKeys.filter((k) => k !== record.id),
- );
- } else {
- setSelectedCheckpointKeys([
- ...selectedCheckpointKeys,
- record.id,
- ]);
- }
- }}
- />
- <span style={{ marginLeft: 8 }}>{dom}</span>
- </div>
- ),
- 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: (
- <Popconfirm
- title="确认删除该要点及其所有查核项?"
- okText="确定"
- cancelText="取消"
- onConfirm={() => handleDeleteCheckpoint(record)}
- >
- <a style={{ color: '#3377ff' }}>删除</a>
- </Popconfirm>
- ),
- props: { rowSpan: record.rowSpan },
- };
- }
- return { children: null, props: { rowSpan: 0 } };
- },
- },
- ];
- // 人员表格列定义
- const employeeColumns: ProColumns[] = [
- {
- title: '工号',
- dataIndex: 'employeeCode',
- width: 120,
- },
- {
- title: '姓名',
- dataIndex: 'employeeName',
- width: 120,
- },
- {
- title: '组长',
- dataIndex: 'isManager',
- width: 100,
- render: (text: any, record: any) => (
- <Radio
- checked={text === 1}
- onChange={() => handleSetManager(record.employeeId)}
- />
- ),
- },
- {
- title: '操作',
- key: 'option',
- width: 100,
- valueType: 'option',
- render: (_: any, record: any) => [
- <a
- key="delete"
- style={{ color: '#3377FF' }}
- onClick={async () => {
- Modal.confirm({
- title: '确认移除该人员吗?',
- onOk: async () => {
- try {
- const result = await delCheckGroupEmployee([record.id]);
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success('移除成功!');
- loadEmployeeData();
- } catch (error) {
- message.error('移除失败');
- }
- },
- okText: '确定',
- cancelText: '取消',
- });
- }}
- >
- 删除
- </a>,
- ],
- },
- ];
- return (
- <KCIMPagecontainer title={false}>
- <div className="check-unit-mana">
- <div className="check-unit-container">
- {/* 左侧查核组树 */}
- {renderGroupTree()}
- {/* 右侧内容区 */}
- <div className="dept-content" ref={deptContentRef}>
- {selectedGroup && (
- <Card className="pfm-ant-card" bordered={false}>
- <Tabs defaultActiveKey="members">
- <Tabs.TabPane tab="组员" key="members">
- <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={employeeSearchKeywords}
- onChange={(e) => {
- setEmployeeSearchKeywords(e.target.value);
- }}
- />
- </div>
- <AddEmployeeModal
- selectedGroup={selectedGroup}
- onSuccess={() => loadEmployeeData()}
- />
- </div>
- <KCIMTable
- columns={employeeColumns as ProColumns[]}
- actionRef={employeeTableRef}
- rowKey="id"
- dataSource={filteredEmployeeList}
- search={false}
- options={false}
- pagination={false}
- scroll={{ y: 'calc(100vh - 232px)' }}
- rowSelection={{
- selectedRowKeys: selectedEmployeeKeys,
- onChange: (keys) =>
- setSelectedEmployeeKeys(keys as string[]),
- }}
- tableAlertRender={false}
- />
- </div>
- </Tabs.TabPane>
- <Tabs.TabPane tab="查核要点" key="checkpoints">
- <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) => {
- setCheckpointSearchKeywords(e.target.value);
- }}
- />
- </div>
- <AddCheckpointModal
- selectedGroup={selectedGroup}
- onSuccess={() => loadCheckpointData()}
- />
- </div>
- <KCIMTable
- columns={checkpointColumns as ProColumns[]}
- actionRef={checkpointTableRef}
- rowKey="id"
- dataSource={filteredCheckpointList}
- search={false}
- options={false}
- pagination={false}
- bordered
- scroll={{ y: 'calc(100vh - 236px)' }}
- tableAlertRender={false}
- />
- </div>
- </Tabs.TabPane>
- </Tabs>
- </Card>
- )}
- {!selectedGroup && (
- <div className="empty-state">
- <div className="empty-content">
- <div className="empty-text">请选择左侧查核组查看详情</div>
- </div>
- </div>
- )}
- </div>
- </div>
- </div>
- {/* 批量操作栏 - 固定在页面底部,宽度跟dept-content一致 */}
- {(selectedEmployeeKeys.length > 0 ||
- selectedCheckpointKeys.length > 0) && (
- <div
- style={{
- position: 'fixed',
- left: actionBarStyle.left + 16,
- width: actionBarStyle.width - 16,
- bottom: 16,
- height: 56,
- backgroundColor: '#fff',
- zIndex: 1000,
- 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 }}>
- 已选 {selectedEmployeeKeys.length + selectedCheckpointKeys.length}{' '}
- 项
- </span>
- <div style={{ display: 'flex', gap: 8 }}>
- <Button
- onClick={() => {
- setSelectedEmployeeKeys([]);
- setSelectedCheckpointKeys([]);
- }}
- style={{ height: 32, borderRadius: 4, fontSize: 14 }}
- size="small"
- >
- 取消选择
- </Button>
- {selectedEmployeeKeys.length > 0 && (
- <Popconfirm
- title="确认批量移除所选人员?"
- okText="确定"
- cancelText="取消"
- onConfirm={async () => {
- try {
- const result = await delCheckGroupEmployee(
- selectedEmployeeKeys,
- );
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success('批量移除成功');
- setSelectedEmployeeKeys([]);
- loadEmployeeData();
- } catch (e) {
- message.error('批量移除失败');
- }
- }}
- >
- <Button
- type="primary"
- style={{
- background: '#3377FF',
- borderColor: '#3377FF',
- height: 32,
- borderRadius: 4,
- fontSize: 14,
- }}
- size="small"
- >
- 批量移除人员
- </Button>
- </Popconfirm>
- )}
- {selectedCheckpointKeys.length > 0 && (
- <Popconfirm
- title="确认批量删除所选查核要点?"
- okText="确定"
- cancelText="取消"
- onConfirm={async () => {
- try {
- // 从选中的ID中提取原始要点ID(去重)
- const originalIds = Array.from(
- new Set(
- selectedCheckpointKeys
- .map((id) => {
- const record = filteredCheckpointList.find(
- (item: any) => item.id === id,
- );
- return record?.originalPointId;
- })
- .filter((id) => id !== undefined),
- ),
- );
- const result = await delCheckGroupCheckpoints(originalIds);
- // 检查返回值,如果是false说明业务失败
- if (result === false) {
- return; // 错误消息已在响应拦截器中显示,直接返回
- }
- message.success('批量删除成功');
- setSelectedCheckpointKeys([]);
- loadCheckpointData();
- } catch (e) {
- message.error('批量删除失败');
- }
- }}
- >
- <Button
- type="primary"
- style={{
- background: '#3377FF',
- borderColor: '#3377FF',
- height: 32,
- borderRadius: 4,
- fontSize: 14,
- }}
- size="small"
- >
- 批量删除要点
- </Button>
- </Popconfirm>
- )}
- </div>
- </div>
- )}
- </KCIMPagecontainer>
- );
- };
- export default CheckGroupMana;
|