fix
parent
0616a469cd
commit
392df0bc53
|
|
@ -1,19 +1,31 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Tag, Button, Card, Row, Col, Table, Statistic, Flex, Modal, Radio, Checkbox, message, Spin,
|
Tag,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Table,
|
||||||
|
Statistic,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
Radio,
|
||||||
|
Checkbox,
|
||||||
|
message,
|
||||||
|
Spin,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||||
import {ROLE_DEFINITIONS} from '~/enumerate/constant'
|
import { ROLE_DEFINITIONS } from "~/enumerate/constant";
|
||||||
|
|
||||||
const { router } = tools;
|
const { router } = tools;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const TeamContent = ({ readOnly, ...props }) => {
|
const TeamContent = ({ readOnly, ...props }) => {
|
||||||
const { safetyEvalBusiness, evalProjectMemberPage, evalProjectMemberSave } = props;
|
const { safetyEvalBusiness, evalProjectMemberPage, evalProjectMemberSave } =
|
||||||
const { evalProjectDetailData, memberLoading, memberSaveLoading } = safetyEvalBusiness || {};
|
props;
|
||||||
|
const { evalProjectDetailData, memberLoading, memberSaveLoading } =
|
||||||
|
safetyEvalBusiness || {};
|
||||||
const participants = evalProjectDetailData?.participants || [];
|
const participants = evalProjectDetailData?.participants || [];
|
||||||
const projectId = router.query?.id;
|
const projectId = router.query?.id;
|
||||||
|
|
||||||
|
|
@ -31,7 +43,10 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
...item,
|
...item,
|
||||||
roles: (item.role || []).map((r) => ({
|
roles: (item.role || []).map((r) => ({
|
||||||
role: r,
|
role: r,
|
||||||
duty: item.responsibility || ROLE_DEFINITIONS.find((d) => d.value === r)?.summary || "",
|
duty:
|
||||||
|
item.responsibility ||
|
||||||
|
ROLE_DEFINITIONS.find((d) => d.value === r)?.summary ||
|
||||||
|
"",
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
@ -42,7 +57,9 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
if (evalProjectDetailData?.id) fetchData();
|
if (evalProjectDetailData?.id) fetchData();
|
||||||
}, [projectId, evalProjectDetailData?.id]);
|
}, [projectId, evalProjectDetailData?.id]);
|
||||||
|
|
||||||
const isUniqueRole = selectedRole && ROLE_DEFINITIONS.find((r) => r.value === selectedRole)?.unique;
|
const isUniqueRole =
|
||||||
|
selectedRole &&
|
||||||
|
ROLE_DEFINITIONS.find((r) => r.value === selectedRole)?.unique;
|
||||||
|
|
||||||
const handlePersonChange = (checkedValues) => {
|
const handlePersonChange = (checkedValues) => {
|
||||||
if (isUniqueRole && checkedValues.length > 1) {
|
if (isUniqueRole && checkedValues.length > 1) {
|
||||||
|
|
@ -59,13 +76,24 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
if (!selectedRole) { message.warning("请选择1个项目角色"); return; }
|
if (!selectedRole) {
|
||||||
if (selectedPersons.length === 0) { message.warning("请至少选择1名人员"); return; }
|
message.warning("请选择1个项目角色");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedPersons.length === 0) {
|
||||||
|
message.warning("请至少选择1名人员");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const role = ROLE_DEFINITIONS.find((r) => r.value === selectedRole);
|
const role = ROLE_DEFINITIONS.find((r) => r.value === selectedRole);
|
||||||
if (role?.unique && selectedPersons.length > 1) { message.warning("该角色为单人唯一角色,只能选择1名人员"); return; }
|
if (role?.unique && selectedPersons.length > 1) {
|
||||||
|
message.warning("该角色为单人唯一角色,只能选择1名人员");
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 唯一角色校验:检查是否已有人担任该角色
|
// 唯一角色校验:检查是否已有人担任该角色
|
||||||
if (role?.unique) {
|
if (role?.unique) {
|
||||||
const existing = teamMembers.some((m) => m.roles.some((r) => r.role === selectedRole));
|
const existing = teamMembers.some((m) =>
|
||||||
|
m.roles.some((r) => r.role === selectedRole),
|
||||||
|
);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
message.warning(`"${role.name}"已配置,不可重复添加`);
|
message.warning(`"${role.name}"已配置,不可重复添加`);
|
||||||
return;
|
return;
|
||||||
|
|
@ -88,7 +116,9 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
setTeamMembers((prev) => {
|
setTeamMembers((prev) => {
|
||||||
const merged = [...prev];
|
const merged = [...prev];
|
||||||
newEntries.forEach((entry) => {
|
newEntries.forEach((entry) => {
|
||||||
const existing = merged.find((m) => m.personnelId === entry.personnelId);
|
const existing = merged.find(
|
||||||
|
(m) => m.personnelId === entry.personnelId,
|
||||||
|
);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (!existing.roles.find((r) => r.role === entry.roles[0].role)) {
|
if (!existing.roles.find((r) => r.role === entry.roles[0].role)) {
|
||||||
existing.roles.push(entry.roles[0]);
|
existing.roles.push(entry.roles[0]);
|
||||||
|
|
@ -140,32 +170,63 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
{ title: "姓名", dataIndex: "personnelName", width: 120, ellipsis: true },
|
{ title: "姓名", dataIndex: "personnelName", width: 120, ellipsis: true },
|
||||||
{ title: "部门/岗位", dataIndex: "deptName", width: 200, ellipsis: true },
|
{ title: "部门/岗位", dataIndex: "deptName", width: 200, ellipsis: true },
|
||||||
{
|
{
|
||||||
title: "项目角色", dataIndex: "roles", width: 350,
|
title: "项目角色",
|
||||||
|
dataIndex: "roles",
|
||||||
|
width: 350,
|
||||||
render: (roles, record) => (
|
render: (roles, record) => (
|
||||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
||||||
{roles.map((r) => (
|
{roles.map((r) => {
|
||||||
<Tag key={r.role} color="blue" closable={!readOnly} onClose={() => {
|
const inOnly = r.role === "PROJECT_LEAD" || r.role === "PROCESS_CONTROL_LEAD";
|
||||||
setTeamMembers((prev) => prev.map((m) => {
|
return (
|
||||||
|
<Tag
|
||||||
|
key={r.role}
|
||||||
|
color={inOnly ? "purple" : "blue"}
|
||||||
|
closable={!readOnly}
|
||||||
|
onClose={() => {
|
||||||
|
setTeamMembers((prev) =>
|
||||||
|
prev
|
||||||
|
.map((m) => {
|
||||||
if (m.personnelId !== record.personnelId) return m;
|
if (m.personnelId !== record.personnelId) return m;
|
||||||
const updated = m.roles.filter((role) => role.role !== r.role);
|
const updated = m.roles.filter(
|
||||||
return updated.length > 0 ? { ...m, roles: updated } : null;
|
(role) => role.role !== r.role,
|
||||||
}).filter(Boolean));
|
);
|
||||||
}}>
|
return updated.length > 0
|
||||||
{ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name || r.role}
|
? { ...m, roles: updated }
|
||||||
|
: null;
|
||||||
|
})
|
||||||
|
.filter(Boolean),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name ||
|
||||||
|
r.role}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ title: "资质与专业", dataIndex: "capacity", width: 200, ellipsis: true },
|
{ title: "资质与专业", dataIndex: "capacity", width: 200, ellipsis: true },
|
||||||
{
|
{
|
||||||
title: "主要职责", dataIndex: "roles", ellipsis: true,
|
title: "主要职责",
|
||||||
|
dataIndex: "roles",
|
||||||
|
ellipsis: true,
|
||||||
render: (roles) => roles.map((r) => r.duty).join(";"),
|
render: (roles) => roles.map((r) => r.duty).join(";"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作", width: 80, fixed: "right",
|
title: "操作",
|
||||||
|
width: 80,
|
||||||
|
fixed: "right",
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Button type="link" danger size="small" disabled={readOnly} onClick={() => handleRemovePerson(record.personnelId)}>移除</Button>
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
size="small"
|
||||||
|
disabled={readOnly}
|
||||||
|
onClick={() => handleRemovePerson(record.personnelId)}
|
||||||
|
>
|
||||||
|
移除
|
||||||
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -174,7 +235,9 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
<Spin spinning={memberLoading}>
|
<Spin spinning={memberLoading}>
|
||||||
<div>
|
<div>
|
||||||
<div className="pf-permission">
|
<div className="pf-permission">
|
||||||
<Tag color="blue" className="pf-tag">评价部经理 / 机构主账号</Tag>
|
<Tag color="blue" className="pf-tag">
|
||||||
|
评价部经理 / 机构主账号
|
||||||
|
</Tag>
|
||||||
<span className="pf-desc">
|
<span className="pf-desc">
|
||||||
添加人员时可同时选择多个项目角色和多名人员;普通角色可配置多人。"项目负责人(项目组组长)"和"过程控制负责人"每个项目只能配置1人,选择任一唯一角色时,本次只能选择1名人员。
|
添加人员时可同时选择多个项目角色和多名人员;普通角色可配置多人。"项目负责人(项目组组长)"和"过程控制负责人"每个项目只能配置1人,选择任一唯一角色时,本次只能选择1名人员。
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -183,42 +246,83 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
<Row gutter={16} className="pf-stats-row">
|
<Row gutter={16} className="pf-stats-row">
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="已配置人员" value={teamMembers.length} suffix="人" />
|
<Statistic
|
||||||
|
title="已配置人员"
|
||||||
|
value={teamMembers.length}
|
||||||
|
suffix="人"
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="已覆盖角色" value={new Set(allRoles).size + " / 11"} />
|
<Statistic
|
||||||
|
title="已覆盖角色"
|
||||||
|
value={new Set(allRoles).size + " / 11"}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="项目负责人" value={allRoles.includes("PROJECT_LEAD") ? "已配置" : "待配置"}
|
<Statistic
|
||||||
valueStyle={{ color: allRoles.includes("PROJECT_LEAD") ? "#52c41a" : "#faad14" }} />
|
title="项目负责人"
|
||||||
|
value={allRoles.includes("PROJECT_LEAD") ? "已配置" : "待配置"}
|
||||||
|
valueStyle={{
|
||||||
|
color: allRoles.includes("PROJECT_LEAD")
|
||||||
|
? "#52c41a"
|
||||||
|
: "#faad14",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card size="small">
|
<Card size="small">
|
||||||
<Statistic title="过程控制负责人" value={allRoles.includes("PROCESS_CONTROL_LEAD") ? "已配置" : "待配置"}
|
<Statistic
|
||||||
valueStyle={{ color: allRoles.includes("PROCESS_CONTROL_LEAD") ? "#52c41a" : "#faad14" }} />
|
title="过程控制负责人"
|
||||||
|
value={
|
||||||
|
allRoles.includes("PROCESS_CONTROL_LEAD")
|
||||||
|
? "已配置"
|
||||||
|
: "待配置"
|
||||||
|
}
|
||||||
|
valueStyle={{
|
||||||
|
color: allRoles.includes("PROCESS_CONTROL_LEAD")
|
||||||
|
? "#52c41a"
|
||||||
|
: "#faad14",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Flex justify="flex-end" className="pf-add-bar">
|
<Flex justify="flex-end" className="pf-add-bar">
|
||||||
{!readOnly && <Button type="primary" size="small" onClick={handleOpenModal}>+ 添加人员与角色</Button>}
|
{!readOnly && (
|
||||||
|
<Button type="primary" size="small" onClick={handleOpenModal}>
|
||||||
|
+ 添加人员与角色
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Table dataSource={teamMembers} columns={columns} rowKey="personnelId" pagination={false} scroll={{ x: 1600, y: 320 }} />
|
<Table
|
||||||
|
dataSource={teamMembers}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="personnelId"
|
||||||
|
pagination={false}
|
||||||
|
scroll={{ x: 1600, y: 320 }}
|
||||||
|
/>
|
||||||
|
|
||||||
<details className="pf-role-guide">
|
<details className="pf-role-guide">
|
||||||
<summary className="pf-role-guide-title">查看11类项目角色职责说明</summary>
|
<summary className="pf-role-guide-title">
|
||||||
|
查看11类项目角色职责说明
|
||||||
|
</summary>
|
||||||
<div className="pf-role-guide-grid">
|
<div className="pf-role-guide-grid">
|
||||||
{ROLE_DEFINITIONS.map((role) => (
|
{ROLE_DEFINITIONS.map((role) => (
|
||||||
<div key={role.value} className="pf-role-card">
|
<div key={role.value} className="pf-role-card">
|
||||||
<strong className="pf-role-card-title">
|
<strong className="pf-role-card-title">
|
||||||
{role.name}
|
{role.name}
|
||||||
{role.unique && <Tag color="blue" className="pf-unique-tag">单人唯一</Tag>}
|
{role.unique && (
|
||||||
|
<Tag color="blue" className="pf-unique-tag">
|
||||||
|
单人唯一
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
<p className="pf-role-card-desc">{role.summary}</p>
|
<p className="pf-role-card-desc">{role.summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -227,38 +331,62 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
|
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
|
||||||
{!readOnly && <Button type="primary" loading={memberSaveLoading} onClick={handleSave}>确认项目组</Button>}
|
{!readOnly && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
loading={memberSaveLoading}
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
确认项目组
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Modal title="添加项目人员与角色" open={modalOpen} onCancel={() => setModalOpen(false)} width={700}
|
<Modal
|
||||||
|
title="添加项目人员与角色"
|
||||||
|
open={modalOpen}
|
||||||
|
onCancel={() => setModalOpen(false)}
|
||||||
|
width={700}
|
||||||
footer={
|
footer={
|
||||||
<Flex justify="flex-end" gap={8}>
|
<Flex justify="flex-end" gap={8}>
|
||||||
<Button onClick={() => setModalOpen(false)}>取消</Button>
|
<Button onClick={() => setModalOpen(false)}>取消</Button>
|
||||||
<Button type="primary" onClick={handleConfirm}>确认添加</Button>
|
<Button type="primary" onClick={handleConfirm}>
|
||||||
|
确认添加
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="pf-modal-hint">
|
<div className="pf-modal-hint">
|
||||||
{selectedRole && ROLE_DEFINITIONS.find((r) => r.value === selectedRole)?.unique
|
{selectedRole &&
|
||||||
|
ROLE_DEFINITIONS.find((r) => r.value === selectedRole)?.unique
|
||||||
? `已选择唯一角色,人员数量限制为1人。当前已选 ${selectedPersons.length} 人。`
|
? `已选择唯一角色,人员数量限制为1人。当前已选 ${selectedPersons.length} 人。`
|
||||||
: `已选择 ${selectedRole ? 1 : 0} 个角色、${selectedPersons.length} 名人员。`}
|
: `已选择 ${selectedRole ? 1 : 0} 个角色、${selectedPersons.length} 名人员。`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pf-modal-section">
|
<div className="pf-modal-section">
|
||||||
<h4 className="pf-modal-section-title">1. 选择项目角色</h4>
|
<h4 className="pf-modal-section-title">1. 选择项目角色</h4>
|
||||||
<Radio.Group value={selectedRole}
|
<Radio.Group
|
||||||
onChange={(e) => { setSelectedRole(e.target.value); setSelectedPersons([]); }}
|
value={selectedRole}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedRole(e.target.value);
|
||||||
|
setSelectedPersons([]);
|
||||||
|
}}
|
||||||
className="pf-w-full"
|
className="pf-w-full"
|
||||||
>
|
>
|
||||||
<div className="pf-choice-grid">
|
<div className="pf-choice-grid">
|
||||||
{ROLE_DEFINITIONS.map((role) => (
|
{ROLE_DEFINITIONS.map((role) => (
|
||||||
<label key={role.value}
|
<label
|
||||||
|
key={role.value}
|
||||||
className={`pf-choice-card ${selectedRole === role.value ? "pf-choice-card-active" : ""}`}
|
className={`pf-choice-card ${selectedRole === role.value ? "pf-choice-card-active" : ""}`}
|
||||||
>
|
>
|
||||||
<Radio value={role.value} className="pf-hidden-control" />
|
<Radio value={role.value} className="pf-hidden-control" />
|
||||||
<strong className="pf-choice-card-name">
|
<strong className="pf-choice-card-name">
|
||||||
{role.name}
|
{role.name}
|
||||||
{role.unique && <Tag color="blue" className="pf-unique-tag">单人唯一</Tag>}
|
{role.unique && (
|
||||||
|
<Tag color="blue" className="pf-unique-tag">
|
||||||
|
单人唯一
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
</strong>
|
</strong>
|
||||||
<span className="pf-choice-card-sub">{role.summary}</span>
|
<span className="pf-choice-card-sub">{role.summary}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -272,16 +400,27 @@ const TeamContent = ({ readOnly, ...props }) => {
|
||||||
{participants.length === 0 ? (
|
{participants.length === 0 ? (
|
||||||
<div className="pf-empty">暂无可用人员数据</div>
|
<div className="pf-empty">暂无可用人员数据</div>
|
||||||
) : (
|
) : (
|
||||||
<Checkbox.Group value={selectedPersons} onChange={handlePersonChange} className="pf-w-full">
|
<Checkbox.Group
|
||||||
|
value={selectedPersons}
|
||||||
|
onChange={handlePersonChange}
|
||||||
|
className="pf-w-full"
|
||||||
|
>
|
||||||
<div className="pf-choice-grid-full">
|
<div className="pf-choice-grid-full">
|
||||||
{participants.map((person) => (
|
{participants.map((person) => (
|
||||||
<label key={person.id}
|
<label
|
||||||
|
key={person.id}
|
||||||
className={`pf-choice-card ${selectedPersons.includes(person.id) ? "pf-choice-card-active" : ""}`}
|
className={`pf-choice-card ${selectedPersons.includes(person.id) ? "pf-choice-card-active" : ""}`}
|
||||||
>
|
>
|
||||||
<Checkbox value={person.id} className="pf-hidden-control" />
|
<Checkbox
|
||||||
<strong className="pf-choice-card-name">{person.name} · {person.deptName}</strong>
|
value={person.id}
|
||||||
|
className="pf-hidden-control"
|
||||||
|
/>
|
||||||
|
<strong className="pf-choice-card-name">
|
||||||
|
{person.name} · {person.deptName}
|
||||||
|
</strong>
|
||||||
<span className="pf-choice-card-sub">
|
<span className="pf-choice-card-sub">
|
||||||
{person.posName}{person.capacity ? ` · ${person.capacity}` : ""}
|
{person.posName}
|
||||||
|
{person.capacity ? ` · ${person.capacity}` : ""}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue