diff --git a/src/api/safetyEvalBusiness/index.js b/src/api/safetyEvalBusiness/index.js index a170469..417c2a5 100644 --- a/src/api/safetyEvalBusiness/index.js +++ b/src/api/safetyEvalBusiness/index.js @@ -123,4 +123,14 @@ export const evalContractSave = declareRequest( export const evalRiskAnalysisPeopleSave = declareRequest( "riskAnalysisSaveLoading", "Post > @/safetyEval/institution/eval-risk-analysis/participant/save", +); + +export const evalProjectMemberPage = declareRequest( + "memberLoading", + "Get > /safetyEval/institution/eval-project-member/page", +); + +export const evalProjectMemberSave = declareRequest( + "memberSaveLoading", + "Post > @/safetyEval/institution/eval-project-member/save", ); \ No newline at end of file diff --git a/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js b/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js index 87b92d9..26ad460 100644 --- a/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js +++ b/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js @@ -101,6 +101,9 @@ const EvalProjectCreate = (props) => { participants: selectedParticipants.map((item) => ({ id: item.id, name: item.userName, + deptName: item.deptName, + posName: item.postName, + capacity: '龟派气功波', })), }; if (isView) { diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js index ac0fed6..edd1e85 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/TeamContent.js @@ -1,100 +1,287 @@ -import React from "react"; -import { Tag, Button, Card, Row, Col, Table, Statistic, Flex } from "antd"; +import React, { useEffect, useState } from "react"; +import { + Tag, Button, Card, Row, Col, Table, Statistic, Flex, Modal, Radio, Checkbox, message, Spin, +} from "antd"; +import { Connect } from "@cqsjjb/jjb-dva-runtime"; +import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; +import { tools } from "@cqsjjb/jjb-common-lib"; -const TeamContent = () => ( -
-
- 评价部经理 / 机构主账号 - - 添加人员时可同时选择多个项目角色和多名人员;普通角色可配置多人。"项目负责人(项目组组长)"和"过程控制负责人"每个项目只能配置1人,选择任一唯一角色时,本次只能选择1名人员。 - -
+const { router } = tools; - - - - - - - - - - - - - - - - - - - - - - - - - - - +const ROLE_DEFINITIONS = [ + { value: "MARKETING_BUSINESS_REP", name: "市场部业务员", unique: false, summary: "获取项目基础信息,形成风险分析记录;风险通过后签订合同并下达任务通知单。" }, + { value: "TECHNICAL_LEAD", name: "技术负责人", unique: false, summary: "评估人员专业匹配与技术可行性,审核检查表并开展评价报告技术审核。" }, + { value: "PROCESS_CONTROL_LEAD", name: "过程控制负责人", unique: true, summary: "核查利益冲突、资质范围和人员配置,负责受控管理、程序合规审核及归档前校验。" }, + { value: "FINANCE_STAFF", name: "财务人员", unique: false, summary: "核算人工、费用、税金和利润等全成本,评估项目经济性及合同金额合理性。" }, + { value: "EVALUATION_AGENCY_HEAD", name: "评价机构负责人", unique: false, summary: "综合多方意见决定是否承接项目,并在全部审核通过后终审签发评价报告。" }, + { value: "EVALUATION_DEPT_MANAGER", name: "评价部经理", unique: false, summary: "签发项目组任命文件,审核组长资格、行业经验及成员专业配置。" }, + { value: "PROJECT_LEAD", name: "项目负责人(项目组组长)", unique: true, summary: "制定实施计划并组织启动、初勘、检查表、现场勘查、整改复查及报告编制。" }, + { value: "PROJECT_TEAM_MEMBERS", name: "项目组各专业组员", unique: false, summary: "按专业分工开展资料收集、检查表编制、现场检查、危险辨识和评价报告专业内容编写。" }, + { value: "INTERNAL_AUDITOR", name: "内审人", unique: false, summary: "审核报告初稿及修改结果,重点核查依据、危险辨识、单元划分、评价方法和结论。" }, + { value: "REPORT_COMPILER", name: "报告编制人", unique: false, summary: "整合各专业内容形成报告初稿,根据各级审核意见修改并整理全过程档案。" }, + { value: "ARCHIVES_ADMIN", name: "档案管理员", unique: false, summary: "办理档案交接、编号、分类、装订和入库,并按规定开展报告信息公开。" }, +]; - - - - +const TeamContent = (props) => { + const { safetyEvalBusiness, evalProjectMemberPage, evalProjectMemberSave } = props; + const { evalProjectDetailData, memberLoading, memberSaveLoading } = safetyEvalBusiness || {}; + const participants = evalProjectDetailData?.participants || []; + const projectId = router.query?.id; - }, - ]} - rowKey="id" - pagination={false} - /> + const [modalOpen, setModalOpen] = useState(false); + const [selectedRole, setSelectedRole] = useState(null); + const [selectedPersons, setSelectedPersons] = useState([]); + const [teamMembers, setTeamMembers] = useState([]); -
- 查看11类项目角色职责说明 -
- {[ - { name: "市场部业务员", unique: false, summary: "获取项目基础信息,形成风险分析记录;风险通过后签订合同并下达任务通知单。" }, - { name: "技术负责人", unique: false, summary: "评估人员专业匹配与技术可行性,审核检查表并开展评价报告技术审核。" }, - { name: "过程控制负责人", unique: true, summary: "核查利益冲突、资质范围和人员配置,负责受控管理、程序合规审核及归档前校验。" }, - { name: "财务人员", unique: false, summary: "核算人工、费用、税金和利润等全成本,评估项目经济性及合同金额合理性。" }, - { name: "评价机构负责人", unique: false, summary: "综合多方意见决定是否承接项目,并在全部审核通过后终审签发评价报告。" }, - { name: "评价部经理", unique: false, summary: "签发项目组任命文件,审核组长资格、行业经验及成员专业配置。" }, - { name: "项目负责人(项目组组长)", unique: true, summary: "制定实施计划并组织启动、初勘、检查表、现场勘查、整改复查及报告编制。" }, - { name: "项目组各专业组员", unique: false, summary: "按专业分工开展资料收集、检查表编制、现场检查、危险辨识和评价报告专业内容编写。" }, - { name: "内审人", unique: false, summary: "审核报告初稿及修改结果,重点核查依据、危险辨识、单元划分、评价方法和结论。" }, - { name: "报告编制人", unique: false, summary: "整合各专业内容形成报告初稿,根据各级审核意见修改并整理全过程档案。" }, - { name: "档案管理员", unique: false, summary: "办理档案交接、编号、分类、装订和入库,并按规定开展报告信息公开。" }, - ].map((role) => ( -
- - {role.name} - {role.unique && 单人唯一} - -

{role.summary}

-
+ const fetchData = async () => { + if (!projectId) return; + const res = await evalProjectMemberPage({ projectId }); + if (res?.success !== false && res?.data) { + setTeamMembers( + res.data.map((item) => ({ + ...item, + roles: (item.role || []).map((r) => ({ + role: r, + duty: item.responsibility || ROLE_DEFINITIONS.find((d) => d.value === r)?.summary || "", + })), + })), + ); + } + }; + + useEffect(() => { + if (evalProjectDetailData?.id) fetchData(); + }, [projectId, evalProjectDetailData?.id]); + + const isUniqueRole = selectedRole && ROLE_DEFINITIONS.find((r) => r.value === selectedRole)?.unique; + + const handlePersonChange = (checkedValues) => { + if (isUniqueRole && checkedValues.length > 1) { + setSelectedPersons([checkedValues[checkedValues.length - 1]]); + return; + } + setSelectedPersons(checkedValues); + }; + + const handleOpenModal = () => { + setSelectedRole(null); + setSelectedPersons([]); + setModalOpen(true); + }; + + const handleConfirm = () => { + if (!selectedRole) { message.warning("请选择1个项目角色"); return; } + if (selectedPersons.length === 0) { message.warning("请至少选择1名人员"); return; } + const role = ROLE_DEFINITIONS.find((r) => r.value === selectedRole); + if (role?.unique && selectedPersons.length > 1) { message.warning("该角色为单人唯一角色,只能选择1名人员"); return; } + + const newEntries = selectedPersons.map((personId) => { + const p = participants.find((item) => item.id === personId); + const existing = teamMembers.find((m) => m.personnelId === personId); + return { + personnelId: p.id, + personnelName: p.name, + deptName: p.deptName, + capacity: p.capacity, + ...(existing?.id ? { id: existing.id } : {}), + roles: [{ role: selectedRole, duty: role?.summary || "" }], + }; + }); + + setTeamMembers((prev) => { + const merged = [...prev]; + newEntries.forEach((entry) => { + const existing = merged.find((m) => m.personnelId === entry.personnelId); + if (existing) { + if (!existing.roles.find((r) => r.role === entry.roles[0].role)) { + existing.roles.push(entry.roles[0]); + } + } else { + merged.push(entry); + } + }); + return merged; + }); + setModalOpen(false); + }; + + const handleRemovePerson = (personnelId) => { + setTeamMembers((prev) => prev.filter((m) => m.personnelId !== personnelId)); + }; + + const handleSave = async () => { + const payload = teamMembers.map((m) => ({ + id: m.id, + projectId, + personnelId: m.personnelId, + personnelName: m.personnelName, + deptName: m.deptName, + capacity: m.capacity, + role: m.roles.map((r) => r.role), + responsibility: m.roles[0]?.duty, + })); + const res = await evalProjectMemberSave(payload); + if (res?.success !== false) { + message.success("保存成功"); + fetchData(); + } + }; + + const allRoles = teamMembers.flatMap((m) => m.roles.map((r) => r.role)); + + const columns = [ + { title: "姓名", dataIndex: "personnelName", width: 120, ellipsis: true }, + { title: "部门/岗位", dataIndex: "deptName", width: 200, ellipsis: true }, + { + title: "项目角色", dataIndex: "roles", width: 350, + render: (roles) => ( +
+ {roles.map((r) => ( + + {ROLE_DEFINITIONS.find((d) => d.value === r.role)?.name || r.role} + ))}
-
+ ), + }, + { title: "资质与专业", dataIndex: "capacity", width: 200, ellipsis: true }, + { + title: "主要职责", dataIndex: "roles", ellipsis: true, + render: (roles) => roles.map((r) => r.duty).join(";"), + }, + { + title: "操作", width: 80, fixed: "right", + render: (_, record) => ( + + ), + }, + ]; - - - - - - - -); + return ( + +
+
+ 评价部经理 / 机构主账号 + + 添加人员时可同时选择多个项目角色和多名人员;普通角色可配置多人。"项目负责人(项目组组长)"和"过程控制负责人"每个项目只能配置1人,选择任一唯一角色时,本次只能选择1名人员。 + +
-export default TeamContent; \ No newline at end of file + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ 查看11类项目角色职责说明 +
+ {ROLE_DEFINITIONS.map((role) => ( +
+ + {role.name} + {role.unique && 单人唯一} + +

{role.summary}

+
+ ))} +
+
+ + + + + + + + setModalOpen(false)} width={700} + footer={ + + + + + } + > +
+ {selectedRole && ROLE_DEFINITIONS.find((r) => r.value === selectedRole)?.unique + ? `已选择唯一角色,人员数量限制为1人。当前已选 ${selectedPersons.length} 人。` + : `已选择 ${selectedRole ? 1 : 0} 个角色、${selectedPersons.length} 名人员。`} +
+ +
+

1. 选择项目角色

+ { setSelectedRole(e.target.value); setSelectedPersons([]); }} + className="pf-w-full" + > +
+ {ROLE_DEFINITIONS.map((role) => ( + + ))} +
+
+
+ +
+

2. 选择人员(可多选)

+ {participants.length === 0 ? ( +
暂无可用人员数据
+ ) : ( + +
+ {participants.map((person) => ( + + ))} +
+
+ )} +
+
+ + + ); +}; + +export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(TeamContent); \ No newline at end of file diff --git a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.less b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.less index a636dc9..f59e03f 100644 --- a/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.less +++ b/src/pages/Container/SafetyEvalBusiness/ProjectFlow/index.less @@ -107,4 +107,129 @@ &-w-full { width: 100%; } + + // 项目组 - 统计行 + &-stats-row { + margin-bottom: 8px; + } + + // 项目组 - 添加按钮行 + &-add-bar { + margin-bottom: 8px; + } + + // 项目组 - 角色职责说明折叠区 + &-role-guide { + margin-top: 8px; + cursor: pointer; + } + + &-role-guide-title { + color: #1677ff; + font-size: 13px; + padding: 6px 0; + } + + &-role-guide-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + padding: 8px 0; + } + + &-role-card { + border: 1px solid #e8e8e8; + border-radius: 6px; + padding: 8px 10px; + background: #f8fafc; + } + + &-role-card-title { + font-size: 13px; + } + + &-role-card-desc { + font-size: 12px; + color: #666; + margin: 4px 0 0; + line-height: 1.55; + } + + // 项目组 - 底部操作行 + &-bottom-actions { + margin-top: 8px; + } + + // 弹窗 - 提示文字 + &-modal-hint { + margin-bottom: 8px; + font-size: 13px; + color: #666; + } + + // 弹窗 - 区块 + &-modal-section { + margin-bottom: 24px; + } + + &-modal-section-title { + margin-bottom: 8px; + } + + // 弹窗 - 选择卡片网格 + &-choice-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + } + + &-choice-grid-full { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; + width: 100%; + } + + // 弹窗 - 选择卡片 + &-choice-card { + border: 1px solid #e8e8e8; + border-radius: 6px; + padding: 8px 10px; + cursor: pointer; + background: #f8fafc; + } + + &-choice-card-active { + background: #e6f4ff; + border-color: #1677ff; + } + + &-choice-card-name { + font-size: 13px; + } + + &-choice-card-sub { + font-size: 12px; + color: #666; + display: block; + margin-top: 2px; + } + + // 弹窗 - 空状态 + &-empty { + color: #999; + padding: 16px; + text-align: center; + } + + // 弹窗 - 唯一标签 + &-unique-tag { + margin-left: 6px; + font-size: 11px; + } + + // 隐藏 radio / checkbox 原生控件 + &-hidden-control { + display: none; + } } \ No newline at end of file