488 lines
17 KiB
JavaScript
488 lines
17 KiB
JavaScript
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);
|