safety-eval-service-frontend/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/StaffPickerModal.js

250 lines
7.5 KiB
JavaScript
Raw Normal View History

2026-07-21 18:03:20 +08:00
import React, { useState, useEffect } from "react";
import {
Modal,
TreeSelect,
Input,
Table,
Button,
Tag,
Row,
Col,
Space,
} from "antd";
import { CloseOutlined } from "@ant-design/icons";
import { NS_STAFF_INFO, NS_ORG_DEPARTMENT } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
2026-07-31 16:59:40 +08:00
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP, TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions";
2026-08-12 11:50:26 +08:00
import { renderCapabilityList } from "~/utils";
2026-07-21 18:03:20 +08:00
const StaffPickerModal = (props) => {
const { open, onCancel, onConfirm, selectedIds = [] } = props;
const { staffInfoList: dataSource, staffInfoTotal: total, staffInfoLoading: loading } = props.staffInfo || {};
const { orgDepartmentTreeData } = props.orgDepartment || {};
const [deptId, setDeptId] = useState(undefined);
const [keyword, setKeyword] = useState("");
const [page, setPage] = useState({ current: 1, size: 10 });
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
// 用 Map 存储所有已选行,翻页后不丢失
const [selectedRowMap, setSelectedRowMap] = useState({});
useEffect(() => {
if (open) {
props.orgDepartmentTree();
// 从父组件恢复已选数据
const initialIds = selectedIds;
const initialRows = props.selectedParticipants || [];
setSelectedRowKeys(initialIds);
const map = {};
initialRows.forEach((r) => { map[r.id] = r; });
setSelectedRowMap(map);
fetchData({ current: 1, size: 10 });
}
}, [open]);
const fetchData = (pagination) => {
const params = {
current: pagination?.current || page.current,
size: pagination?.size || page.size,
employmentStatusCode: 1,
};
if (deptId) params.deptId = deptId;
if (keyword) params.userName = keyword;
props.staffInfoList(params);
setPage(params);
};
const handleSearch = () => {
fetchData({ current: 1, size: 10 });
};
const handleTableChange = (pagination) => {
fetchData(pagination);
};
const handleSelectChange = (rowKeys, rows) => {
setSelectedRowKeys(rowKeys);
// rows 仅包含当前页的行,用 Map 合并新旧数据
setSelectedRowMap((prev) => {
const next = { ...prev };
// 移除取消勾选的项
Object.keys(next).forEach((key) => {
if (!rowKeys.includes(key)) {
delete next[key];
}
});
// 添加/更新当前页勾选的行
rows.forEach((row) => {
next[row.id] = row;
});
return next;
});
};
const handleRemoveSelected = (record) => {
const newKeys = selectedRowKeys.filter((id) => id !== record.id);
setSelectedRowKeys(newKeys);
setSelectedRowMap((prev) => {
const next = { ...prev };
delete next[record.id];
return next;
});
};
const handleConfirm = () => {
onConfirm(Object.values(selectedRowMap));
};
const selectedRows = Object.values(selectedRowMap);
const columns = [
{
title: "用户名称",
dataIndex: "userName",
width: 200,
render: (text, record) => {
const name = record.userName || "-";
return (
<Space size={4} wrap align="start">
<span style={{ maxWidth: 100, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", display: "inline-block" }} title={name}>{name}</span>
{record.technicalDirectorFlag && (
<Tag color="blue" style={{ marginRight: 0, fontSize: 11 }}>技术</Tag>
)}
{record.processControlLeaderFlag && (
<Tag color="purple" style={{ marginRight: 0, fontSize: 11 }}>过程</Tag>
)}
</Space>
);
},
},
{
2026-08-06 11:23:43 +08:00
title: "能力",
2026-08-12 11:50:26 +08:00
dataIndex: "capabilityAssessment",
2026-07-21 18:03:20 +08:00
ellipsis: true,
2026-08-12 11:50:26 +08:00
render: renderCapabilityList,
2026-07-21 18:03:20 +08:00
},
2026-07-31 16:59:40 +08:00
2026-08-06 11:23:43 +08:00
2026-07-21 18:03:20 +08:00
];
return (
<Modal
title="选择项目参与人员"
open={open}
onCancel={onCancel}
onOk={handleConfirm}
width={1000}
destroyOnHidden
>
<Row gutter={16}>
<Col span={18}>
<Space style={{ marginBottom: 12, width: "100%" }} size={12}>
<TreeSelect
placeholder="请选择部门"
allowClear
treeData={orgDepartmentTreeData}
fieldNames={{ label: "deptName", value: "id", key: "id" }}
treeDefaultExpandAll
showSearch
style={{ width: 240 }}
value={deptId}
onChange={(value) => {
setDeptId(value);
setTimeout(() => handleSearch(), 0);
}}
/>
<Input
placeholder="搜索用户名称"
allowClear
style={{ width: 240 }}
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onPressEnter={handleSearch}
/>
<Button type="primary" onClick={handleSearch}>搜索</Button>
</Space>
<div style={{ marginBottom: 8, fontSize: 12, color: "#666" }}>
<Tag color="blue" style={{ fontSize: 11, marginRight: 4 }}>技术</Tag>
<Tag color="purple" style={{ fontSize: 11, margin: "0 4px 0 12px" }}>过程</Tag>
</div>
<Table
rowKey="id"
columns={columns}
dataSource={dataSource}
loading={loading}
size="small"
rowSelection={{
selectedRowKeys,
preserveSelectedRowKeys: true,
onChange: handleSelectChange,
}}
pagination={{
total,
current: page.current,
pageSize: page.size,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
onChange: (cur, size) => handleTableChange({ current: cur, size }),
}}
scroll={{ y: 400, x: 600 }}
/>
</Col>
<Col span={6}>
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8 }}>
已选人员{selectedRows.length}
</div>
<div
style={{
border: "1px solid #d9d9d9",
borderRadius: 6,
padding: 8,
minHeight: 300,
maxHeight: 460,
overflow: "auto",
}}
>
{selectedRows.length === 0 && (
<div style={{ color: "#999", fontSize: 13, textAlign: "center", paddingTop: 120 }}>
请从左侧勾选人员
</div>
)}
{selectedRows.map((item) => (
<div
key={item.id}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "6px 8px",
borderBottom: "1px solid #f0f0f0",
}}
>
<div style={{ fontSize: 13, maxWidth: 140, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={item.userName}>{item.userName}</div>
<Button
type="text"
size="small"
danger
icon={<CloseOutlined />}
onClick={() => handleRemoveSelected(item)}
/>
</div>
))}
</div>
</Col>
</Row>
</Modal>
);
};
export default Connect(
[NS_STAFF_INFO, NS_ORG_DEPARTMENT],
true,
)(StaffPickerModal);