dev-tmp1^2
tangjie 2026-08-28 18:43:13 +08:00
parent 6f19e24df4
commit a3e796831d
6 changed files with 90 additions and 27 deletions

View File

@ -0,0 +1,49 @@
评价人员任命及专家聘用书
根据市场部下达的评价项目为: {projectName} {evalTypeText} 评价任务,结合项目的特性,现任命 {projectLeader} 担任该安全评价项目组长(负责人), {compiler} 担任该项目报告编制人。
根据评价部项目组组建原则及风险分析记录情况,项目组成员如下:
人员组成
姓名
专业
项目负责人
{projectLeaderName}
{projectLeaderCapacity}
项目组成员
{firstMemberName}
{firstMemberMajor}
{#memberRows}{name}
{major}{/memberRows}
报告编制人
{firstCompilerName}
{firstCompilerCapacity}
{#compilerRows}{compilerName}
{compilerCapacity}{/compilerRows}
内审人员
{firstAuditorName}
{firstAuditorCapacity}
{#auditorRows}{auditorName}
{auditorCapacity}{/auditorRows}
技术负责人
{firstTechLeadName}
{firstTechLeadCapacity}
{#techLeadRows}{techLeadName}
{techLeadCapacity}{/techLeadRows}
过程控制负责人
{processControlLeadName}
{processControlLeadCapacity}
聘用专家组成员
评价部经理:
年 月 日

View File

@ -600,6 +600,17 @@ export const CAPABILITY_MAP = CAPABILITY_OPTIONS.reduce((acc, cur) => {
return acc;
}, {});
/** 业务范围与所需专业能力配备标准对照依据《安全评价机构资质条件》应急202660号附件 */
export const INDUSTRY_REQUIRED_CAPABILITIES = {
COAL_MINING: ["SAFETY", "MECHANICAL", "ELECTRICAL", "MINING", "VENTILATION", "GEOLOGY", "MINE_CONSTRUCTION"],
METAL_NONMETAL_MINING: ["SAFETY", "MECHANICAL", "ELECTRICAL", "MINING", "VENTILATION", "GEOLOGY", "HYDRAULIC_STRUCTURE"],
ONSHORE_OIL_GAS: ["SAFETY", "MECHANICAL", "ELECTRICAL", "OIL_PRODUCTION", "STORAGE_AND_TRANSPORT"],
ONSHORE_OIL_GAS_PIPELINE: ["OIL_GAS_STORAGE_TRANSPORT", "EQUIPMENT", "INSTRUMENT", "ELECTRICAL", "ANTICORROSION", "SAFETY"],
PETROCHEMICAL_CHEMICAL_PHARMACEUTICAL: ["CHEMICAL_PROCESS", "CHEMICAL_MECHANICAL", "AUTOMATION_INSTRUMENT", "ELECTRICAL", "SAFETY"],
FIREWORKS_FIRECRACKERS: ["EXPLOSIVES", "EXPLOSION_TECHNOLOGY", "MECHANICAL", "ELECTRICAL", "SAFETY"],
METAL_SMELTING: ["SAFETY", "MECHANICAL", "ELECTRICAL", "METALLURGY", "NONFERROUS_METAL"],
};
/** 专业(职称信息) */
export const PROFESSIONAL_MAJOR_OPTIONS = [
{ label: "安全工程", value: "SAFETY_ENGINEERING" },

View File

@ -21,7 +21,7 @@ import { Connect } from "@cqsjjb/jjb-dva-runtime";
import useIndustryOptions from "~/hooks/useIndustryOptions";
import BaiduMapPicker from "~/components/BaiduMapPicker";
import { district } from "~/enumerate/constant";
import {REGISTER_ENGINEER_CATEGORY_OPTIONS} from '~/enumerate/enterpriseOptions';
import {QUALIFICATION_INDUSTRY_OPTIONS} from '~/enumerate/enterpriseOptions';
import { phoneRule } from "~/utils/validators";
import { renderCapabilityList } from "~/utils";
import StaffPickerModal from "./StaffPickerModal";
@ -29,6 +29,7 @@ import StaffPickerModal from "./StaffPickerModal";
import {
EVAL_TYPE_OPTIONS,
CHECKIN_RADIUS_OPTIONS,
INDUSTRY_REQUIRED_CAPABILITIES,
} from "~/enumerate/constant";
const { router } = tools;
@ -102,9 +103,26 @@ const EvalProjectCreate = (props) => {
setMapPickerVisible(false);
};
/** 按所选业务范围(配备标准)筛选人员能力,仅提交范围内所需的专业能力条目 */
const filterCapacityByIndustry = (item, industryCodes) => {
const requiredCaps = industryCodes.flatMap(
(code) => INDUSTRY_REQUIRED_CAPABILITIES[code] || [],
);
if (!requiredCaps.length || !Array.isArray(item.capabilityAssessment)) {
return item.capabilityAssessment?.length
? renderCapabilityList(item.capabilityAssessment, {capFlag: true})
: item.capacity;
}
const matched = item.capabilityAssessment.filter((cap) =>
requiredCaps.includes(cap.professionalCapabilityCode),
);
return matched.length ? renderCapabilityList(matched, {capFlag: true}) : item.capacity;
};
const handleSubmit = () => {
form.validateFields().then(async (values) => {
const { planStartDate, planEndDate, ...rest } = values;
const industryCodes = String(values.industryCode || "").split(",");
const params = {
...rest,
planStartDate: planStartDate?.format("YYYY-MM-DD"),
@ -114,9 +132,7 @@ const EvalProjectCreate = (props) => {
name: item.userName,
deptName: item.deptName,
posName: item.postName,
capacity: item.capabilityAssessment
? renderCapabilityList(item.capabilityAssessment)
: item.capacity,
capacity: filterCapacityByIndustry(item, industryCodes),
})),
};
if (projectId) {
@ -357,18 +373,18 @@ const EvalProjectCreate = (props) => {
}
>
<Select
mode="multiple"
placeholder="请选择"
allowClear
options={isStatutory?industryOptions:REGISTER_ENGINEER_CATEGORY_OPTIONS}
options={isStatutory?industryOptions:QUALIFICATION_INDUSTRY_OPTIONS}
/>
</Form.Item>
</Col>
<Col span={12}>
{/* <Col span={12}>
<Form.Item name="controlledNumber" label="受控编号" rules={[{ required: true, message: "请选择受控编号" }]}>
<Input placeholder="请输入" maxLength={20} />
</Form.Item>
</Col>
</Col> */}
</Row>
</Card>

View File

@ -304,7 +304,7 @@ const RiskAnalysisContent = ({ readOnly, ...props }) => {
Array.isArray(value) ? value.join(",") : value
}
>
<Select mode="multiple" placeholder="请选择所属行业" allowClear>
<Select placeholder="请选择所属行业" allowClear>
{QUALIFICATION_INDUSTRY_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt.value}>
{opt.label}

View File

@ -25,7 +25,9 @@ import { fillDocxTemplate } from "~/utils/fillDocxTemplate";
const { router } = tools;
const TEAM_TEMPLATE_URL = "https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a86d2b2e4b072649bfb8a93.docx";
const TEAM_TEMPLATE_URL = encodeURI(
"https://gbs-cqaqpj.oss-cn-beijing.aliyuncs.com/jjb/6a9165e2e4b0b3767a786858.docx",
);
const TeamContent = ({ readOnly, ...props }) => {
const {
@ -268,28 +270,57 @@ const TeamContent = ({ readOnly, ...props }) => {
setSigners((prev) => prev.filter((s) => s.personnelId !== personnelId));
};
// 导出Word按当前项目组配置填充模板聘请所需技术专家、备注事项、部门经理或负责人、年月日保留空白)
// 导出Word按当前项目组配置填充模板任命语句用"姓名(能力)",表格姓名/专业分列展示;聘用专家、评价部经理、年月日留白)
const handleExport = async () => {
/** 人员展示文本:姓名后追加能力,如"张三(化工)" */
const personText = (m) =>
`${m.personnelName}${m.capacity ? `${m.capacity}` : ""}`;
const roleMembers = (role) =>
teamMembers.filter((m) => m.roles.some((r) => r.role === role));
const sentenceByRole = (role) =>
roleMembers(role).map(personText).join("、");
const namesByRole = (role) =>
teamMembers
.filter((m) => m.roles.some((r) => r.role === role))
.map(personText)
.join("、");
roleMembers(role).map((m) => m.personnelName).join("、");
const capsByRole = (role) =>
roleMembers(role).map((m) => m.capacity).filter(Boolean).join("、");
const auditorList = roleMembers("INTERNAL_AUDITOR").map((m) => ({
auditorName: m.personnelName,
auditorCapacity: m.capacity || "",
}));
const techLeadList = roleMembers("TECHNICAL_LEAD").map((m) => ({
techLeadName: m.personnelName,
techLeadCapacity: m.capacity || "",
}));
const compilerList = roleMembers("REPORT_COMPILER").map((m) => ({
compilerName: m.personnelName,
compilerCapacity: m.capacity || "",
}));
const memberList = teamMembers
.filter((m) => !m.roles.some((r) => r.role === "PROJECT_LEAD"))
.map((m) => ({ name: m.personnelName, major: m.capacity || "" }));
const data = {
projectName: evalProjectDetailData?.projectName || "",
evalTypeText: EVAL_TYPE_MAP[evalProjectDetailData?.evalTypeCode] || "",
projectLeader: namesByRole("PROJECT_LEAD"),
membersText: teamMembers
.filter((m) => !m.roles.some((r) => r.role === "PROJECT_LEAD"))
.map(personText)
.join("、"),
compiler: namesByRole("REPORT_COMPILER"),
auditor: namesByRole("INTERNAL_AUDITOR"),
techLead: namesByRole("TECHNICAL_LEAD"),
processControlLead: namesByRole("PROCESS_CONTROL_LEAD"),
projectLeader: sentenceByRole("PROJECT_LEAD"),
compiler: sentenceByRole("REPORT_COMPILER"),
projectLeaderName: namesByRole("PROJECT_LEAD"),
projectLeaderCapacity: capsByRole("PROJECT_LEAD"),
// 标签合并分组首行固定放第一人循环行continue逐人一行
firstAuditorName: auditorList[0]?.auditorName || "",
firstAuditorCapacity: auditorList[0]?.auditorCapacity || "",
auditorRows: auditorList.slice(1),
firstTechLeadName: techLeadList[0]?.techLeadName || "",
firstTechLeadCapacity: techLeadList[0]?.techLeadCapacity || "",
techLeadRows: techLeadList.slice(1),
processControlLeadName: namesByRole("PROCESS_CONTROL_LEAD"),
processControlLeadCapacity: capsByRole("PROCESS_CONTROL_LEAD"),
// 模板中标签列已纵向合并restart 行放第一名成员循环行continue逐人一行
firstMemberName: memberList[0]?.name || "",
firstMemberMajor: memberList[0]?.major || "",
memberRows: memberList.slice(1),
firstCompilerName: compilerList[0]?.compilerName || "",
firstCompilerCapacity: compilerList[0]?.compilerCapacity || "",
compilerRows: compilerList.slice(1),
};
try {
await fillDocxTemplate(
@ -613,7 +644,9 @@ const TeamContent = ({ readOnly, ...props }) => {
{ (
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button onClick={handleExport}>导出人员任命书</Button>
<Button onClick={handleExport}>
导出人员任命书
</Button>
{!readOnly &&<Button
type="primary"
loading={memberSaveLoading || memberParticipantSaveLoading}

View File

@ -3,20 +3,23 @@ import { CAPABILITY_MAP } from "~/enumerate/constant";
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP } from "~/enumerate/enterpriseOptions";
/** 渲染专业能力列表,供表格 column render 使用 */
export function renderCapabilityList(list) {
export function renderCapabilityList(list, option) {
if (!Array.isArray(list) || !list.length) return "-";
return (
list
.map((item) => {
const cap = CAPABILITY_MAP[item.professionalCapabilityCode];
if (!cap) return null;
if(option.capFlag){
return cap;
}
const industry = item.industryCode
? QUALIFICATION_INDUSTRY_OPTIONS_MAP[item.industryCode]
: null;
return industry ? `${industry}${cap}` : cap;
})
.filter(Boolean)
.join("") || "-"
.join(";") || "-"
);
}