H5页面
parent
9092691340
commit
fe2cdb82e6
|
|
@ -27,11 +27,14 @@
|
|||
"@cqsjjb/jjb-react-admin-component": "latest",
|
||||
"ahooks": "^3.9.5",
|
||||
"antd": "^5.27.6",
|
||||
"antd-mobile": "^5.42.3",
|
||||
"antd-mobile-icons": "^0.3.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"zy-react-library": "^1.1.41"
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"zy-react-library": "^1.1.42"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
|
|
|
|||
|
|
@ -43,4 +43,8 @@
|
|||
- `/primeport/container/stakeholder/personnelApplication/list` 人员申请
|
||||
- `/primeport/container/stakeholder/vehicleApplication/list` 车辆申请
|
||||
- `/primeport/container/stakeholder/personnelApplicationRecords/list` 人员申请记录
|
||||
- `/primeport/container/stakeholder/vehicleApplicationRecords/list` 车辆申请记录
|
||||
- `/primeport/container/stakeholder/vehicleApplicationRecords/list` 车辆申请记录
|
||||
|
||||
### H5端
|
||||
- `/primeport/container/mobile/firstLevelDoor/personnelApplication/apply` 一级口门管理/人员申请/申请
|
||||
- `/primeport/container/mobile/firstLevelDoor/personnelApplication/applyList` 一级口门管理/人员申请/申请记录
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
|
||||
|
||||
export {};
|
||||
|
||||
// export const riskList = declareRequest(
|
||||
// "loading",
|
||||
// "Post > @/xxx",
|
||||
// "dataSource: [] | res.data || [] & total: 0 | res.totalCount || 0 & pageIndex: 1 | res.pageIndex || 1 & pageSize: 10 | res.pageSize || 10",
|
||||
// );
|
||||
// export const riskDelete = declareRequest(
|
||||
// "loading",
|
||||
// "Delete > @/xxx/{id}",
|
||||
// );
|
||||
export const getDepartmentListTree = declareRequest(
|
||||
"Post > @/basicInfo/department/listAllTreeByCorpType",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
import { Image as AntdImage, Button, Popup, Toast } from "antd-mobile";
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
import { base642File } from "zy-react-library/utils";
|
||||
|
||||
const SignatureH5 = (props) => {
|
||||
const [signatureModalOpen, setSignatureModalOpen] = useState(false);
|
||||
const signatureCanvas = useRef(null);
|
||||
const [base64, setBase64] = useState("");
|
||||
const [signatureClientHeight, setSignatureClientHeight] = useState("");
|
||||
const [signatureClientWidth, setSignatureClientWidth] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (signatureModalOpen) {
|
||||
// setSignatureClientHeight(document.querySelector(".adm-popup-body").clientHeight - 50);
|
||||
// setSignatureClientWidth(document.querySelector(".adm-popup-body").clientWidth - 100);
|
||||
setSignatureClientHeight(window.innerHeight - 50);
|
||||
setSignatureClientWidth(window.innerWidth - 100);
|
||||
}
|
||||
}, [signatureModalOpen]);
|
||||
useEffect(() => {
|
||||
setBase64(props.url);
|
||||
}, [props.url]);
|
||||
|
||||
const rotateBase64Img = (src) => {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
let imgW;
|
||||
let imgH;
|
||||
let size;
|
||||
const cutCor = { sx: 0, sy: 0, ex: 0, ey: 0 };
|
||||
const image = new Image();
|
||||
image.crossOrigin = "anonymous";
|
||||
image.src = src;
|
||||
image.onload = function () {
|
||||
imgW = image.width;
|
||||
imgH = image.height;
|
||||
size = imgW > imgH ? imgW : imgH;
|
||||
canvas.width = size * 2;
|
||||
canvas.height = size * 2;
|
||||
cutCor.sx = size;
|
||||
cutCor.sy = size - imgW;
|
||||
cutCor.ex = size + imgH;
|
||||
cutCor.ey = size + imgW;
|
||||
ctx.translate(size, size);
|
||||
ctx.rotate((270 * Math.PI) / 180);
|
||||
// ctx.scale(0.16, 0.16);
|
||||
ctx.drawImage(image, 0, 0);
|
||||
const imgData = ctx.getImageData(
|
||||
cutCor.sx,
|
||||
cutCor.sy,
|
||||
cutCor.ex,
|
||||
cutCor.ey,
|
||||
);
|
||||
canvas.width = imgH;
|
||||
canvas.height = imgW;
|
||||
ctx.putImageData(imgData, 0, 0);
|
||||
resolve(canvas.toDataURL("image/png"));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
if (signatureCanvas.current.isEmpty()) {
|
||||
Toast.show({
|
||||
icon: "fail",
|
||||
content: "请签名",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const result = signatureCanvas.current.toDataURL();
|
||||
const base64 = await rotateBase64Img(result);
|
||||
setBase64(base64);
|
||||
props.onConfirm?.({
|
||||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||||
base64,
|
||||
file: base642File(base64),
|
||||
});
|
||||
signatureCanvas.current.clear();
|
||||
setSignatureModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setSignatureModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{base64 ? "重新签字" : "手写签字"}
|
||||
</Button>
|
||||
</div>
|
||||
{base64 && (
|
||||
<div style={{ border: "1px dashed #d9d9d9", width: "90%", height: "200px", marginTop: "16px", margin: "16px auto" }}>
|
||||
<AntdImage src={base64} style={{ width: "100%", height: "200px", objectFit: "contain" }} />
|
||||
</div>
|
||||
)}
|
||||
<Popup
|
||||
position="right"
|
||||
visible={signatureModalOpen}
|
||||
bodyStyle={{ width: "100vw", height: "100vh" }}
|
||||
>
|
||||
<div style={{ backgroundColor: "#f5f5f5", width: "100%", height: "100%" }}>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
left: "-200px",
|
||||
top: "50%",
|
||||
transform: "rotate(90deg)",
|
||||
display: "flex",
|
||||
gap: "30px",
|
||||
}}
|
||||
>
|
||||
<Button style={{ padding: "7px 50px" }} onClick={() => signatureCanvas.current.clear()}>重签</Button>
|
||||
<Button
|
||||
style={{ padding: "7px 50px" }}
|
||||
onClick={() => {
|
||||
setSignatureModalOpen(false);
|
||||
signatureCanvas.current.clear();
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button style={{ padding: "7px 50px" }} color="primary" onClick={onOk}>确定</Button>
|
||||
</div>
|
||||
<div style={{
|
||||
border: "1px dashed #d9d9d9",
|
||||
width: signatureClientWidth,
|
||||
height: signatureClientHeight,
|
||||
position: "absolute",
|
||||
left: "55px",
|
||||
top: "25px",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
<SignatureCanvas
|
||||
ref={signatureCanvas}
|
||||
penColor="black"
|
||||
canvasProps={{ width: signatureClientWidth, height: signatureClientHeight }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
right: "-18px",
|
||||
transform: "rotate(90deg)",
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
请横屏签字
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignatureH5;
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
import {
|
||||
Button,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Form,
|
||||
Image,
|
||||
ImageUploader,
|
||||
Input,
|
||||
Picker,
|
||||
Popup,
|
||||
Toast,
|
||||
} from "antd-mobile";
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useDictionary from "zy-react-library/hooks/useDictionary";
|
||||
import { ID_NUMBER, LICENSE_PLATE_NUMBER, PHONE } from "zy-react-library/regular";
|
||||
import { validatorEndTime } from "zy-react-library/utils";
|
||||
import SignatureH5 from "~/components/SignatureH5";
|
||||
import { NS_GLOBAL } from "~/enumerate/namespace";
|
||||
import "../../../index.less";
|
||||
|
||||
function Apply(props) {
|
||||
const [primeportAreaList, setPrimeportAreaList] = useState([]);
|
||||
const [vehicleTypeList, setVehicleTypeList] = useState([]);
|
||||
const [licensePlateTypeList, setLicensePlateTypeList] = useState([]);
|
||||
const [departmentList, setDepartmentList] = useState([]);
|
||||
const [noticePopupVisible, setNoticePopupVisible] = useState(false);
|
||||
|
||||
const [signatureUrl, setSignatureUrl] = useState("");
|
||||
|
||||
const checkboxRef = useRef(null);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const accessType = Form.useWatch("accessType", form);
|
||||
const doorType = Form.useWatch("doorType", form);
|
||||
const { getDictionary } = useDictionary();
|
||||
|
||||
const transformDepartmentList = (list) => {
|
||||
return list.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
children: item.childrenList ? transformDepartmentList(item.childrenList) : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const primeportAreaData = await getDictionary({ dictValue: "primeport_area" });
|
||||
setPrimeportAreaList(primeportAreaData);
|
||||
const vehicleTypeData = await getDictionary({ dictValue: "VEHICLE_TYPE" });
|
||||
setVehicleTypeList(vehicleTypeData);
|
||||
const licensePlateTypeData = await getDictionary({ dictValue: "LICENSE_PLATE_TYPE" });
|
||||
setLicensePlateTypeList(licensePlateTypeData);
|
||||
const { data: departmentList } = await props["getDepartmentListTree"]({ enterpriseType: [1, 2] });
|
||||
const transformedDepartmentList = transformDepartmentList(departmentList);
|
||||
setDepartmentList(transformedDepartmentList);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
const onFinish = (values) => {
|
||||
if (!values.safetyNoticeAgreed || !values.signature) {
|
||||
Toast.show({
|
||||
icon: "fail",
|
||||
content: "请勾选《安全进港须知》并签字",
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(values);
|
||||
props.history.push(`./success?accessType=${values.accessType}&text=${values.accessType === "1" ? values.licensePlateNumber : values.phoneNumber}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: "16px" }}>
|
||||
<Form
|
||||
form={form}
|
||||
preserve={false}
|
||||
layout="horizontal"
|
||||
onFinish={onFinish}
|
||||
footer={(
|
||||
<Button block type="submit" shape="rounded" color="primary">
|
||||
提交申请
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<Form.Item
|
||||
name="accessType"
|
||||
label="访问类型"
|
||||
rules={[{ required: true, message: "请选择访问类型" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "车辆", value: "1" },
|
||||
{ label: "人员", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择访问类型"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="doorType"
|
||||
label="选择申请口门"
|
||||
rules={[{ required: true, message: "请选择选择申请口门" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "一级口门", value: "1" },
|
||||
{ label: "封闭区域口门", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择选择申请口门"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
{
|
||||
accessType === "1"
|
||||
&& (
|
||||
<>
|
||||
<Form.Item name="driverName" label="驾驶人姓名" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入驾驶人姓名" />
|
||||
</Form.Item>
|
||||
<BasicInfoFields />
|
||||
<Form.Item
|
||||
name="vehicleType"
|
||||
label="车辆类型"
|
||||
rules={[{ required: true, message: "请选择车辆类型" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[vehicleTypeList.map(item => ({ label: item.dictLabel, value: item.dictValue }))]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择车辆类型"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="licensePlateType"
|
||||
label="车牌类型"
|
||||
rules={[{ required: true, message: "请选择车牌类型" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[licensePlateTypeList.map(item => ({ label: item.dictLabel, value: item.dictValue }))]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择车牌类型"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
<Form.Item name="licensePlateNumber" label="车牌号" rules={[{ required: true }, { pattern: LICENSE_PLATE_NUMBER, message: "请输入正确的车牌号" }]}>
|
||||
<Input placeholder="请输入车牌号" />
|
||||
</Form.Item>
|
||||
<VisitTime form={form} />
|
||||
<LocationFields primeportAreaList={primeportAreaList} doorType={doorType} />
|
||||
<Form.Item name="vehicleLicensePhoto" label="行驶证照片" rules={[{ required: true, message: "请上传行驶证照片" }]}>
|
||||
<ImageUploader
|
||||
upload={file => ({ url: URL.createObjectURL(file), file })}
|
||||
maxCount={2}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="vehiclePhoto" label="车辆照片" rules={[{ required: true, message: "请上传车辆照片" }]}>
|
||||
<ImageUploader
|
||||
upload={file => ({ url: URL.createObjectURL(file), file })}
|
||||
maxCount={4}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Approval departmentList={departmentList} />
|
||||
<SafetyNoticeAndSignature
|
||||
checkboxRef={checkboxRef}
|
||||
signatureUrl={signatureUrl}
|
||||
setNoticePopupVisible={setNoticePopupVisible}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
accessType === "2"
|
||||
&& (
|
||||
<>
|
||||
<Form.Item name="visitorName" label="访问人姓名" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入访问人姓名" />
|
||||
</Form.Item>
|
||||
<BasicInfoFields />
|
||||
<VisitTime form={form} />
|
||||
<LocationFields primeportAreaList={primeportAreaList} doorType={doorType} />
|
||||
<Form.Item name="facePhoto" label="人脸照片" rules={[{ required: true, message: "请上传人脸照片" }]}>
|
||||
<ImageUploader
|
||||
upload={file => ({ url: URL.createObjectURL(file), file })}
|
||||
maxCount={1}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Approval departmentList={departmentList} />
|
||||
<SafetyNoticeAndSignature
|
||||
checkboxRef={checkboxRef}
|
||||
signatureUrl={signatureUrl}
|
||||
setNoticePopupVisible={setNoticePopupVisible}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Form>
|
||||
<Popup
|
||||
position="right"
|
||||
visible={noticePopupVisible}
|
||||
showCloseButton
|
||||
onClose={() => {
|
||||
setNoticePopupVisible(false);
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "40px 20px", overflowY: "auto", height: "100vh" }}>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>欢迎您到访秦皇岛港。为保障您的人身安全及港口生产作业秩序,请注意港口属于重点安全监管区域,存在大型机械作业、货物装卸、车辆往来等生产场景,可能面临机械伤害、物体打击、车辆碰撞等安全风险。请您认真阅读以下须知内容,确认遵守后签字:</p>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>1. 入港时请主动出示有效身份证件,配合安保人员进行身份核验与信息登记,凭港口核发的《临时访客证》入港,自觉接受出港查验;不转借、冒用访客凭证,不将无关人员带入港口。</p>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>2. 入港后请严格在指定区域活动,未经陪同人员及港口负责人许可,绝不擅自进入标有"禁止入内""危险区域"等标识的场所,不靠近起重机械、输送设备、危险品存储点等高危部位,不跨越安全护栏、警戒线,不在作业区域逗留围观。</p>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>3. 遵守港口生产秩序,不干扰装卸、运输、检修等正常工作;不随意触摸、操作生产设备、仪器仪表及安全设施,不移动、遮挡安全警示标识;如需拍摄港口场景,须提前征得港口方同意,不拍摄涉及安全、商业秘密的内容。</p>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>4. 严格遵守消防安全规定,不在港口内吸烟,不携带火种、易燃易爆物品、管制器具等违禁物品入港;发现火灾、设备故障等隐患或突发情况,第一时间告知陪同人员或港口工作人员,配合应急处置,不擅自行动引发次生风险。</p>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>5. 注意自身安全防护,行走时主动避让作业车辆与机械,不擅自横穿作业通道;雨天、雾天等恶劣天气下,听从陪同人员安排,加强安全防范。</p>
|
||||
<p style={{ textAlign: "justify", textIndent: "2rem" }}>6. 已知晓港口所去区域应急逃生路线,遇紧急情况按港口指引有序疏散。</p>
|
||||
<p>本人确认已完整阅读并理解以上须知,承诺严格遵守。如因违反本须知及港口安全规定导致自身人身伤害或港口、他人财产损失,自愿承担全部责任。</p>
|
||||
<SignatureH5
|
||||
onConfirm={(value) => {
|
||||
setSignatureUrl(value.base64);
|
||||
form.setFieldValue("signature", value.file);
|
||||
}}
|
||||
url={signatureUrl}
|
||||
/>
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Button
|
||||
block
|
||||
shape="rounded"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
if (!signatureUrl) {
|
||||
Toast.show({
|
||||
icon: "fail",
|
||||
content: "请签名",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setNoticePopupVisible(false);
|
||||
checkboxRef.current.check();
|
||||
}}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 公共基础信息字段组件
|
||||
const BasicInfoFields = () => (
|
||||
<>
|
||||
<Form.Item name="phoneNumber" label="手机号" rules={[{ required: true }, { pattern: PHONE, message: "请输入正确的手机号" }]}>
|
||||
<Input placeholder="请输入手机号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="idNumber" label="身份证号" rules={[{ required: true }, { pattern: ID_NUMBER, message: "请输入正确的身份证号" }]}>
|
||||
<Input placeholder="请输入身份证号" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
// 公共访问时间组件
|
||||
const VisitTime = ({ form }) => (
|
||||
<>
|
||||
<Form.Item
|
||||
name="visitStartTime"
|
||||
label="访问起始时间"
|
||||
rules={[{ required: true, message: "请选择访问起始时间" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => dayjs(value).format("YYYY-MM-DD")}
|
||||
getValueProps={value => dayjs(value)}
|
||||
>
|
||||
<DatePicker>
|
||||
{value => value ? dayjs(value).format("YYYY-MM-DD") : "请选择访问起始时间"}
|
||||
</DatePicker>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="visitEndTime"
|
||||
label="访问结束时间"
|
||||
rules={[{ required: true, message: "请选择访问结束时间" }, (() => validatorEndTime(form.getFieldValue("visitStartTime")))()]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => dayjs(value).format("YYYY-MM-DD")}
|
||||
getValueProps={value => dayjs(value)}
|
||||
>
|
||||
<DatePicker>
|
||||
{value => value ? dayjs(value).format("YYYY-MM-DD") : "请选择访问结束时间"}
|
||||
</DatePicker>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
// 公共地点字段组件
|
||||
const LocationFields = ({ primeportAreaList, doorType }) => (
|
||||
<>
|
||||
<Form.Item
|
||||
name="portArea"
|
||||
label="港区"
|
||||
rules={[{ required: true, message: "请选择港区" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[primeportAreaList.map(item => ({ label: item.dictLabel, value: item.dictValue }))]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择港区"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="firstLevelDoor"
|
||||
label="一级口门"
|
||||
rules={[{ required: true, message: "请选择一级口门" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "一级口门1", value: "1" },
|
||||
{ label: "一级口门2", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择一级口门"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
{doorType === "2" && <EnclosedAreaFields />}
|
||||
</>
|
||||
);
|
||||
|
||||
// 封闭区域相关字段组件
|
||||
const EnclosedAreaFields = () => (
|
||||
<>
|
||||
<Form.Item
|
||||
name="areaCompany"
|
||||
label="区域所属公司"
|
||||
rules={[{ required: true, message: "请选择区域所属公司" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "区域所属公司1", value: "1" },
|
||||
{ label: "区域所属公司2", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择区域所属公司"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="enclosedArea"
|
||||
label="封闭区域"
|
||||
rules={[{ required: true, message: "请选择封闭区域" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "封闭区域1", value: "1" },
|
||||
{ label: "封闭区域2", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择封闭区域"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="enclosedAreaDoor"
|
||||
label="口门"
|
||||
rules={[{ required: true, message: "请选择口门" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "口门1", value: "1" },
|
||||
{ label: "口门2", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择口门"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
// 公共审批字段组件
|
||||
const Approval = ({ departmentList }) => (
|
||||
<>
|
||||
<Form.Item
|
||||
name="approvalEnterprise"
|
||||
label="审批企业"
|
||||
rules={[{ required: true, message: "请选择审批企业" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
>
|
||||
<Cascader options={departmentList}>
|
||||
{value => value.length > 0 ? value.map(item => item?.label).filter(Boolean).join("-") : "请选择审批企业"}
|
||||
</Cascader>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="approvalPerson"
|
||||
label="审批人"
|
||||
rules={[{ required: true, message: "请选择审批人" }]}
|
||||
trigger="onConfirm"
|
||||
onClick={(_, pickerRef) => {
|
||||
pickerRef.current?.open();
|
||||
}}
|
||||
getValueFromEvent={value => value[0]}
|
||||
getValueProps={value => [value]}
|
||||
>
|
||||
<Picker
|
||||
columns={[[
|
||||
{ label: "审批人1", value: "1" },
|
||||
{ label: "审批人2", value: "2" },
|
||||
]]}
|
||||
>
|
||||
{value => value?.[0]?.label || "请选择审批人"}
|
||||
</Picker>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
// 安全须知和签名组件
|
||||
const SafetyNoticeAndSignature = ({ checkboxRef, signatureUrl, setNoticePopupVisible }) => (
|
||||
<>
|
||||
<Form.Item
|
||||
name="safetyNoticeAgreed"
|
||||
required={false}
|
||||
label=""
|
||||
rules={[{ required: true, message: "请勾选" }]}
|
||||
>
|
||||
<Checkbox style={{ "--icon-size": "18px", "--font-size": "14px", "--gap": "6px" }} ref={checkboxRef}>
|
||||
<div style={{ fontSize: "14px" }}>
|
||||
<span>我已阅读并同意</span>
|
||||
<span
|
||||
style={{ color: "#1677ff" }}
|
||||
onClick={() => {
|
||||
checkboxRef.current.check();
|
||||
setNoticePopupVisible(true);
|
||||
}}
|
||||
>
|
||||
《安全进港须知》
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item name="signature" noStyle>
|
||||
<input type="hidden" />
|
||||
</Form.Item>
|
||||
<Form.Item noStyle>
|
||||
{signatureUrl && (
|
||||
<div style={{
|
||||
border: "1px dashed #d9d9d9",
|
||||
width: "90%",
|
||||
height: "200px",
|
||||
marginTop: "16px",
|
||||
margin: "16px auto",
|
||||
}}
|
||||
>
|
||||
<Image src={signatureUrl} style={{ width: "100%", height: "200px", objectFit: "contain" }} />
|
||||
</div>
|
||||
)}
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
export default Connect([NS_GLOBAL], true)(Apply);
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
import { DotLoading, InfiniteScroll } from "antd-mobile";
|
||||
import { useState } from "react";
|
||||
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
|
||||
|
||||
function ApplyList() {
|
||||
const query = useGetUrlQuery();
|
||||
|
||||
const [data, setData] = useState([]);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
|
||||
const loadMore = async () => {
|
||||
const data = [
|
||||
{ name: "张三", phone: "12345678901", area: "A区", doorTypeName: "1号门", startTime: "2021-01-01 00:00:00", endTime: "2021-01-01 01:00:00", status: "通过", reason: "" },
|
||||
];
|
||||
setData(val => [...val, ...data]);
|
||||
setHasMore(data.length > 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ backgroundColor: "#f9f9f9", padding: "10px" }}>
|
||||
{data.map((item, index) => (
|
||||
<div key={index} style={{ backgroundColor: "#fff", padding: "10px", marginBottom: "10px", borderRadius: "5px" }}>
|
||||
{
|
||||
query.accessType === "2" && (
|
||||
<div>
|
||||
<div style={{ fontSize: "16px", fontWeight: "bold" }}>临时访客申请</div>
|
||||
<div>
|
||||
申请人:
|
||||
{item.name}
|
||||
</div>
|
||||
<div>
|
||||
手机号:
|
||||
{item.phone}
|
||||
</div>
|
||||
<div>
|
||||
申请区域:
|
||||
{item.area}
|
||||
</div>
|
||||
<div>
|
||||
申请口门:
|
||||
{item.doorTypeName}
|
||||
</div>
|
||||
<div>
|
||||
时间范围:
|
||||
{item.startTime}
|
||||
至
|
||||
{item.endTime}
|
||||
</div>
|
||||
<div>
|
||||
审核状态:
|
||||
{item.status}
|
||||
</div>
|
||||
<div>
|
||||
驳回原因:
|
||||
{item.reason}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
query.accessType === "1" && (
|
||||
<div>
|
||||
<div style={{ fontSize: "16px", fontWeight: "bold" }}>临时访客车辆申请</div>
|
||||
<div>
|
||||
申请人:
|
||||
{item.name}
|
||||
</div>
|
||||
<div>
|
||||
手机号:
|
||||
{item.phone}
|
||||
</div>
|
||||
<div>
|
||||
申请区域:
|
||||
{item.area}
|
||||
</div>
|
||||
<div>
|
||||
申请口门:
|
||||
{item.doorTypeName}
|
||||
</div>
|
||||
<div>
|
||||
时间范围:
|
||||
{item.startTime}
|
||||
至
|
||||
{item.endTime}
|
||||
</div>
|
||||
<div>
|
||||
车辆类型:
|
||||
{item.carType}
|
||||
</div>
|
||||
<div>
|
||||
车牌号:
|
||||
{item.carNo}
|
||||
</div>
|
||||
<div>
|
||||
审核状态:
|
||||
{item.status}
|
||||
</div>
|
||||
<div>
|
||||
驳回原因:
|
||||
{item.reason}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<InfiniteScroll loadMore={loadMore} hasMore={hasMore}>
|
||||
{
|
||||
hasMore
|
||||
? (
|
||||
<>
|
||||
<span>Loading</span>
|
||||
<DotLoading />
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<span>--- 我是有底线的 ---</span>
|
||||
)
|
||||
}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ApplyList;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { QRCode } from "antd";
|
||||
import { CheckCircleFill } from "antd-mobile-icons";
|
||||
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
|
||||
|
||||
function Success() {
|
||||
const query = useGetUrlQuery();
|
||||
|
||||
return (
|
||||
<div style={{ padding: "20px" }}>
|
||||
<div style={{ textAlign: "center", paddingTop: "30px" }}>
|
||||
<CheckCircleFill fontSize={48} color="var(--adm-color-primary)" />
|
||||
<div style={{ paddingTop: "10px" }}>信息提交成功!</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "center", marginTop: "50px" }}>
|
||||
<QRCode value={`${window.location.origin}/primeport/container/mobile/firstLevelDoor/personnelApplication/applyList?accessType=${query.accessType}&text=${query.text}`} style={{ margin: "0 auto", marginBottom: 10 }} />
|
||||
<div style={{ paddingTop: "20px" }}>扫描识别二维码获取审批进度。</div>
|
||||
<div style={{ color: "red", fontSize: 12, paddingTop: "10px" }}>请长按或截屏保存二维码,关闭页面后将无法再获取二维码</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Success;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
function PersonnelApplication(props) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
export default PersonnelApplication;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
function firstLevelDoor(props) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
export default firstLevelDoor;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
function Mobile(props) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
export default Mobile;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
.adm-list, .adm-input {
|
||||
--font-size: var(--adm-font-size-6);
|
||||
}
|
||||
|
||||
.adm-button {
|
||||
font-size: var(--adm-font-size-6);
|
||||
}
|
||||
|
||||
.adm-list-item-content-prefix {
|
||||
width: 130px;
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ function Add(props) {
|
|||
const [addPersonnelModalVisible, setAddPersonnelModalVisible] = useState(false);
|
||||
const [needToKnowModalVisible, setNeedToKnowModalVisible] = useState(false);
|
||||
|
||||
const signatureUrl = useRef("");
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const onSubmit = async (values) => {
|
||||
|
|
@ -137,12 +139,13 @@ function Add(props) {
|
|||
{
|
||||
needToKnowModalVisible && (
|
||||
<NeedToKnowModal
|
||||
signatureUrl={form.getFieldValue("todo8")}
|
||||
signatureUrl={signatureUrl.current}
|
||||
onCancel={() => {
|
||||
setNeedToKnowModalVisible(false);
|
||||
}}
|
||||
onOk={(values) => {
|
||||
form.setFieldValue("todo8", values);
|
||||
signatureUrl.current = values.base64;
|
||||
setNeedToKnowModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -198,6 +201,7 @@ const AddPersonnelModalComponent = (props) => {
|
|||
|
||||
const NeedToKnowModal = (props) => {
|
||||
const signatureUrl = useRef("");
|
||||
const signatureFile = useRef({});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
@ -211,7 +215,10 @@ const NeedToKnowModal = (props) => {
|
|||
message.warning("请签名");
|
||||
return;
|
||||
}
|
||||
props.onOk(signatureUrl.current);
|
||||
props.onOk({
|
||||
base64: signatureUrl.current,
|
||||
file: signatureFile.current,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
|
@ -227,6 +234,7 @@ const NeedToKnowModal = (props) => {
|
|||
<Signature
|
||||
onConfirm={(value) => {
|
||||
signatureUrl.current = value.base64;
|
||||
signatureFile.current = value.file;
|
||||
}}
|
||||
url={props.signatureUrl}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { NS_VEHICLE_APPLICATION } from "~/enumerate/namespace";
|
|||
|
||||
function Add(props) {
|
||||
const [needToKnowModalVisible, setNeedToKnowModalVisible] = useState(false);
|
||||
const signatureUrl = useRef("");
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const todo7 = Form.useWatch("todo7", form);
|
||||
|
|
@ -83,12 +84,13 @@ function Add(props) {
|
|||
{
|
||||
needToKnowModalVisible && (
|
||||
<NeedToKnowModal
|
||||
signatureUrl={form.getFieldValue("todo8")}
|
||||
signatureUrl={signatureUrl.current}
|
||||
onCancel={() => {
|
||||
setNeedToKnowModalVisible(false);
|
||||
}}
|
||||
onOk={(values) => {
|
||||
form.setFieldValue("todo8", values);
|
||||
form.setFieldValue("todo8", values.file);
|
||||
signatureUrl.current = values.base64;
|
||||
setNeedToKnowModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -100,6 +102,7 @@ function Add(props) {
|
|||
|
||||
const NeedToKnowModal = (props) => {
|
||||
const signatureUrl = useRef("");
|
||||
const signatureFile = useRef({});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
|
@ -113,7 +116,10 @@ const NeedToKnowModal = (props) => {
|
|||
message.warning("请签名");
|
||||
return;
|
||||
}
|
||||
props.onOk(signatureUrl.current);
|
||||
props.onOk({
|
||||
base64: signatureUrl.current,
|
||||
file: signatureFile.current,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
|
|
@ -129,6 +135,7 @@ const NeedToKnowModal = (props) => {
|
|||
<Signature
|
||||
onConfirm={(value) => {
|
||||
signatureUrl.current = value.base64;
|
||||
signatureFile.current = value.file;
|
||||
}}
|
||||
url={props.signatureUrl}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue