safety-eval-service-frontend/src/pages/Container/SafetyEvalBusiness/EvalProject/Create/index.js

488 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useState, useEffect } from "react";
import {
Form,
Button,
Input,
Select,
Row,
Col,
Card,
message,
DatePicker,
Flex,
Tag,
Radio,
} from "antd";
import dayjs from "dayjs";
import { tools } from "@cqsjjb/jjb-common-lib";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import useIndustryOptions from "~/hooks/useIndustryOptions";
import BaiduMapPicker from "~/components/BaiduMapPicker";
import { district } from "~/enumerate/constant";
import {QUALIFICATION_INDUSTRY_OPTIONS} from '~/enumerate/enterpriseOptions';
import { phoneRule } from "~/utils/validators";
import { renderCapabilityList } from "~/utils";
import StaffPickerModal from "./StaffPickerModal";
import {
EVAL_TYPE_OPTIONS,
CHECKIN_RADIUS_OPTIONS,
INDUSTRY_REQUIRED_CAPABILITIES,
} from "~/enumerate/constant";
const { router } = tools;
const EvalProjectCreate = (props) => {
const [form] = Form.useForm();
const [mapPickerVisible, setMapPickerVisible] = useState(false);
const [staffPickerVisible, setStaffPickerVisible] = useState(false);
const [selectedParticipants, setSelectedParticipants] = useState([]);
const [detailLoading, setDetailLoading] = useState(false);
const projectId = router.query?.id;
// 编辑模式:仅可编辑项目参与人员,其余字段禁用
const isEditParticipant = !!projectId && router.query?.edit === "participant";
const isView = !!projectId && !isEditParticipant;
const {
evalProjectSaveLoading,
evalProjectUpdateLoading,
evalProjectDetailLoading,
customerPage: customerList,
} = props.safetyEvalBusiness;
const industryOptions = useIndustryOptions();
// 是否法定项目选“否”(仅机构内部管理)时,行业类型默认全选;编辑/查看模式不联动,避免覆盖回填数据
const isStatutory = Form.useWatch("isStatutory", form);
useEffect(() => {
props.customerPage({ current: 1, size: 999 });
}, []);
useEffect(() => {
if (!projectId) return;
(async () => {
setDetailLoading(true);
try {
const res = await props.evalProjectDetail({ id: projectId });
if (res?.success !== false && res?.data) {
const data = res.data;
form.setFieldsValue({
...data,
planStartDate: data.planStartDate
? dayjs(data.planStartDate)
: undefined,
planEndDate: data.planEndDate ? dayjs(data.planEndDate) : undefined,
});
if (data.participants?.length) {
setSelectedParticipants(
data.participants.map((p) => ({
id: p.id,
userName: p.name,
...p,
})),
);
}
}
} finally {
setDetailLoading(false);
}
})();
}, [projectId]);
const handleMapConfirm = (location) => {
const { lng, lat, address } = location;
form.setFieldsValue({
longitude: lng,
latitude: lat,
customerAddress: address,
});
setMapPickerVisible(false);
};
/** 按所选业务范围(配备标准)筛选人员能力,仅提交范围内所需的专业能力条目 */
const filterCapacityByIndustry = (item, industryCodes) => {
const requiredCaps = industryCodes.flatMap(
(code) => INDUSTRY_REQUIRED_CAPABILITIES[code] || [],
);
const capabilityAssessment= (item.capabilityAssessment || []).filter(item=>item.industryCode === industryCodes[0]) || [];
if (!requiredCaps.length || !Array.isArray(capabilityAssessment)) {
return capabilityAssessment?.length
? renderCapabilityList(capabilityAssessment, {capFlag: true})
: item.capacity;
}
const matched = 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"),
planEndDate: planEndDate?.format("YYYY-MM-DD"),
participants: selectedParticipants.map((item) => ({
id: item.id,
name: item.userName,
deptName: item.deptName,
posName: item.postName,
capacity: filterCapacityByIndustry(item, industryCodes),
})),
};
if (projectId) {
params.id = projectId;
}
const res = isEditParticipant
? await props.evalProjectUpdate(params)
: await props.evalProjectSave(params);
if (res?.success) {
message.success(isEditParticipant ? "编辑成功" : "创建成功");
props.history.goBack();
}
});
};
const handleStaffConfirm = (rows) => {
setSelectedParticipants(rows);
setStaffPickerVisible(false);
};
return (
<PageLayout
title={
isView
? "查看安全评价项目"
: isEditParticipant
? "编辑项目参与人员"
: "新建安全评价项目"
}
history={props.history}
previous
footer={
!isView && (
<Flex gap={24}>
<Button onClick={() => props.history.goBack()}>取消</Button>
<Button
type="primary"
onClick={handleSubmit}
loading={
detailLoading ||
evalProjectSaveLoading ||
evalProjectUpdateLoading
}
>
{isEditParticipant ? "保存并返回项目列表" : "提交并返回项目列表"}
</Button>
</Flex>
)
}
loading={detailLoading}
>
<Form
form={form}
layout="vertical"
disabled={!!projectId}
scrollToFirstError
>
<Card title="项目基本信息" style={{ marginBottom: 24 }}>
<Row gutter={24}>
<Col span={12}>
<Form.Item
name="projectName"
label="项目名称"
rules={[{ required: true, message: "请输入项目名称" }]}
>
<Input placeholder="请输入项目名称" maxLength={100} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="项目编号" name="projectNo">
<Input placeholder="提交后自动生成" disabled />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
name="evalTypeCode"
label="评价类型"
rules={[{ required: true, message: "请选择评价类型" }]}
>
<Select placeholder="请选择" allowClear>
{EVAL_TYPE_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt.value}>
{opt.label}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="customerId"
label="被评价客户"
rules={[{ required: true, message: "请选择被评价客户" }]}
>
<Select
placeholder="请选择"
allowClear
showSearch
optionFilterProp="label"
onChange={(value, option) => {
const customer = customerList?.find(
(item) => item.id === value,
);
console.log(customer);
form.setFieldsValue({
customerContactName: customer?.principalName || "",
customerContactPhone: customer?.principalPhone || "",
});
}}
>
{(customerList || []).map((item) => (
<Select.Option key={item.id} value={item.id}>
{item.customerName}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item name="customerContactName" label="客户负责人">
<Input placeholder="请输入客户负责人" maxLength={50} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="customerContactPhone"
label="联系电话"
rules={[phoneRule("联系电话", false)]}
>
<Input placeholder="请输入客户负责人联系电话" maxLength={20} />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item name="districtCode" label="项目所在地">
<Select
placeholder="请选择"
allowClear
showSearch
optionFilterProp="label"
>
{district.map((opt) => (
<Select.Option key={opt.code} value={opt.code}>
{opt.name}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="projectLeaderName" label="项目负责人">
<Input placeholder="请输入" maxLength={30} />
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
name="planStartDate"
label="合同开始时间"
rules={[
{ required: true, message: "请选择合同开始时间" },
{
validator: (_, value) => {
const end = form.getFieldValue("planEndDate");
if (value && end && value.isAfter(end)) {
return Promise.reject("合同开始时间不能晚于合同完成时间");
}
return Promise.resolve();
},
},
]}
>
<DatePicker
style={{ width: "100%" }}
placeholder="请选择"
allowClear
onChange={() => form.validateFields(["planEndDate"])}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="planEndDate"
label="合同完成时间"
rules={[
{ required: true, message: "请选择合同完成时间" },
{
validator: (_, value) => {
const start = form.getFieldValue("planStartDate");
if (value && start && value.isBefore(start)) {
return Promise.reject("合同完成时间不能早于合同开始时间");
}
return Promise.resolve();
},
},
]}
>
<DatePicker
style={{ width: "100%" }}
placeholder="请选择"
allowClear
onChange={() => form.validateFields(["planStartDate"])}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={24}>
<Col span={12}>
<Form.Item
name="isStatutory"
label="是否法定项目"
rules={[{ required: true, message: "请选择是否法定项目" }]}
>
<Radio.Group onChange={()=>{
form.setFieldValue('industryCode', '')
}}>
<Radio value={true}>纳入监管端监管</Radio>
<Radio value={false}>仅机构内部管理</Radio>
</Radio.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="industryCode"
label="项目行业类型"
rules={[{ required: true, message: "请选择项目行业类型" }]}
getValueProps={(value) => ({
value: value ? String(value).split(",") : [],
})}
getValueFromEvent={(value) =>
Array.isArray(value) ? value.join(",") : value
}
>
<Select
placeholder="请选择"
allowClear
options={isStatutory?industryOptions:QUALIFICATION_INDUSTRY_OPTIONS}
/>
</Form.Item>
</Col>
{/* <Col span={12}>
<Form.Item name="controlledNumber" label="受控编号" rules={[{ required: true, message: "请选择受控编号" }]}>
<Input placeholder="请输入" maxLength={20} />
</Form.Item>
</Col> */}
</Row>
</Card>
<Card title="客户所在地与打卡定位" style={{ marginBottom: 24 }}>
<Form.Item label="客户所在地" style={{ marginBottom: 16 }}>
<Flex>
<Form.Item name="customerAddress" noStyle rules={[{ required: true, message: "请选择客户或打开地图定位" }]}>
<Input
placeholder="请选择客户或打开地图定位"
disabled
maxLength={200}
style={{ width: 400 }}
/>
</Form.Item>
<Button
onClick={() => setMapPickerVisible(true)}
style={{ marginLeft: 8 }}
>
选择地图位置
</Button>
</Flex>
</Form.Item>
<Row gutter={24}>
<Col span={6}>
<Form.Item name="longitude" label="经度" rules={[{ required: true, message: "请输入经度" }]}>
<Input placeholder="自动获取" disabled />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="latitude" label="纬度" rules={[{ required: true, message: "请输入纬度" }]}>
<Input placeholder="自动获取" disabled />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="checkinRadiusMeters" label="打卡有效范围" rules={[{ required: true, message: "请选择打卡有效范围" }]}>
<Select placeholder="请选择" allowClear>
{CHECKIN_RADIUS_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt.value}>
{opt.label}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
</Card>
<Card title="项目参与人员">
<Form.Item label="参与人员" required>
<Input
placeholder="点击选择参与人员"
readOnly
disabled={isView}
value={
selectedParticipants.length
? `已选 ${selectedParticipants.length}`
: ""
}
onClick={() => setStaffPickerVisible(true)}
style={{ cursor: "pointer", width: 400 }}
/>
{selectedParticipants.length > 0 && (
<Flex wrap style={{ marginTop: 8, gap: 4 }}>
{selectedParticipants.map((item) => (
<Tag
key={item.id}
closable={!isView}
disabled={isView}
onClose={() =>
setSelectedParticipants((prev) =>
prev.filter((r) => r.id !== item.id),
)
}
>
{item.userName}
</Tag>
))}
</Flex>
)}
</Form.Item>
</Card>
</Form>
<StaffPickerModal
open={staffPickerVisible}
onCancel={() => setStaffPickerVisible(false)}
onConfirm={handleStaffConfirm}
selectedIds={selectedParticipants.map((item) => item.id)}
selectedParticipants={selectedParticipants}
/>
<BaiduMapPicker
visible={mapPickerVisible}
onCancel={() => setMapPickerVisible(false)}
onConfirm={handleMapConfirm}
/>
</PageLayout>
);
};
export default Connect([NS_SAFETY_EVAL_BUSINESS], true)(EvalProjectCreate);