dev_1.2
tangjie 2026-07-30 10:20:38 +08:00
parent 0616a469cd
commit 392df0bc53
1 changed files with 192 additions and 53 deletions

View File

@ -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 (
if (m.personnelId !== record.personnelId) return m; <Tag
const updated = m.roles.filter((role) => role.role !== r.role); key={r.role}
return updated.length > 0 ? { ...m, roles: updated } : null; color={inOnly ? "purple" : "blue"}
}).filter(Boolean)); closable={!readOnly}
}}> onClose={() => {
{ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name || r.role} setTeamMembers((prev) =>
</Tag> prev
))} .map((m) => {
if (m.personnelId !== record.personnelId) return m;
const updated = m.roles.filter(
(role) => role.role !== r.role,
);
return updated.length > 0
? { ...m, roles: updated }
: null;
})
.filter(Boolean),
);
}}
>
{ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name ||
r.role}
</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>
))} ))}