250 lines
7.5 KiB
JavaScript
250 lines
7.5 KiB
JavaScript
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";
|
||
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP, TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions";
|
||
import { renderCapabilityList } from "~/utils";
|
||
|
||
|
||
|
||
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>
|
||
);
|
||
},
|
||
},
|
||
|
||
{
|
||
title: "能力",
|
||
dataIndex: "capabilityAssessment",
|
||
ellipsis: true,
|
||
render: renderCapabilityList,
|
||
},
|
||
|
||
|
||
];
|
||
|
||
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); |