feat
parent
12b5d7b7a6
commit
bbfb21730c
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": { "title": "人员打卡信息查询", "version": "1.0.0" },
|
||||
"paths": {
|
||||
"/safetyEval/regulator/eval-project-monitor/survey-clock-in": {
|
||||
"get": {
|
||||
"summary": "人员打卡信息查询",
|
||||
"tags": ["监管端-安评项目"],
|
||||
"parameters": [
|
||||
{ "name": "projectId", "in": "query", "required": true, "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/SingleResponseListRegulatorEvalSurveyClockInCO" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"SingleResponseListRegulatorEvalSurveyClockInCO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": { "type": "boolean" },
|
||||
"code": { "type": "string" },
|
||||
"message": { "type": "string" },
|
||||
"errMessage": { "type": "string" },
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/RegulatorEvalSurveyClockInCO" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"RegulatorEvalSurveyClockInCO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "integer" },
|
||||
"userName": { "type": "string" },
|
||||
"role": { "type": "string" },
|
||||
"faceState": { "type": "integer" },
|
||||
"gpsState": { "type": "integer" },
|
||||
"singInTime": { "type": "string" },
|
||||
"signOutTime": { "type": "string" },
|
||||
"sceneUrl": { "type": "string" },
|
||||
"signatureUrl": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
.api/safetyEval-regulator-eval-project-monitor-survey-clock-in-get/type.d.ts
vendored
Normal file
11
.api/safetyEval-regulator-eval-project-monitor-survey-clock-in-get/type.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export interface RegulatorEvalSurveyClockInCO {
|
||||
id: number;
|
||||
userName: string;
|
||||
role: string;
|
||||
faceState: number;
|
||||
gpsState: number;
|
||||
singInTime: string;
|
||||
signOutTime: string;
|
||||
sceneUrl: string;
|
||||
signatureUrl: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": { "title": "踏勘监管分页", "version": "1.0.0" },
|
||||
"paths": {
|
||||
"/safetyEval/regulator/eval-project-monitor/survey-page": {
|
||||
"get": {
|
||||
"summary": "项目踏勘监管分页",
|
||||
"tags": ["监管端-安评项目"],
|
||||
"parameters": [
|
||||
{ "name": "current", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||
{ "name": "size", "in": "query", "schema": { "type": "integer", "default": 10 } },
|
||||
{ "name": "startTime", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "endTime", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "projectNameOrNo", "in": "query", "schema": { "type": "string" } },
|
||||
{ "name": "orgId", "in": "query", "schema": { "type": "integer" } },
|
||||
{ "name": "clockInStatus", "in": "query", "schema": { "type": "string" } }
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PageResponseRegulatorEvalProjectSurveyPageCO" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"PageResponseRegulatorEvalProjectSurveyPageCO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": { "type": "boolean" },
|
||||
"code": { "type": "string" },
|
||||
"message": { "type": "string" },
|
||||
"errMessage": { "type": "string" },
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/RegulatorEvalProjectSurveyPageCO" }
|
||||
},
|
||||
"total": { "type": "integer" }
|
||||
}
|
||||
},
|
||||
"RegulatorEvalProjectSurveyPageCO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"orgId": { "type": "string" },
|
||||
"projectNo": { "type": "string" },
|
||||
"projectName": { "type": "string" },
|
||||
"orgName": { "type": "string" },
|
||||
"expectedCount": { "type": "integer" },
|
||||
"actualCount": { "type": "integer" },
|
||||
"signInCount": { "type": "integer" },
|
||||
"signOutCount": { "type": "integer" },
|
||||
"faceCount": { "type": "integer" },
|
||||
"scenePhotoCount": { "type": "integer" },
|
||||
"notificationFileUrl": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export interface RegulatorEvalProjectSurveyPageCO {
|
||||
id: string;
|
||||
orgId: string;
|
||||
projectNo: string;
|
||||
projectName: string;
|
||||
orgName: string;
|
||||
expectedCount: number;
|
||||
actualCount: number;
|
||||
signInCount: number;
|
||||
signOutCount: number;
|
||||
faceCount: number;
|
||||
scenePhotoCount: number;
|
||||
notificationFileUrl: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": { "title": "踏勘监管统计", "version": "1.0.0" },
|
||||
"paths": {
|
||||
"/safetyEval/regulator/eval-project-monitor/survey-stat": {
|
||||
"get": {
|
||||
"summary": "项目踏勘监管统计",
|
||||
"tags": ["监管端-安评项目"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/SingleResponseRegulatorEvalProjectSurveyStatCO" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"SingleResponseRegulatorEvalProjectSurveyStatCO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": { "type": "boolean" },
|
||||
"code": { "type": "string" },
|
||||
"message": { "type": "string" },
|
||||
"errMessage": { "type": "string" },
|
||||
"data": { "$ref": "#/components/schemas/RegulatorEvalProjectSurveyStatCO" }
|
||||
}
|
||||
},
|
||||
"RegulatorEvalProjectSurveyStatCO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"notificationArchivedCount": { "type": "integer", "description": "告知书已归档数" },
|
||||
"notificationAbnormalCount": { "type": "integer", "description": "告知异常数" },
|
||||
"clockInNormalCount": { "type": "integer", "description": "人员打卡正常数" },
|
||||
"clockInAbnormalCount": { "type": "integer", "description": "打卡异常数" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface RegulatorEvalProjectSurveyStatCO {
|
||||
notificationArchivedCount: number;
|
||||
notificationAbnormalCount: number;
|
||||
clockInNormalCount: number;
|
||||
clockInAbnormalCount: number;
|
||||
}
|
||||
|
|
@ -264,3 +264,22 @@ export const evalProjectMonitorPage = declareRequest(
|
|||
"evalProjectMonitorPageLoading",
|
||||
"Get > /safetyEval/regulator/eval-project-monitor/realtime-page",
|
||||
);
|
||||
|
||||
// ===== 监管端-项目踏勘监管 =====
|
||||
|
||||
export const evalSurveyMonitorStat = declareRequest(
|
||||
"evalSurveyMonitorStatLoading",
|
||||
"Get > /safetyEval/regulator/eval-project-monitor/survey-stat",
|
||||
"evalSurveyMonitorStat: {} | res.data || {}",
|
||||
);
|
||||
|
||||
export const evalSurveyMonitorPage = declareRequest(
|
||||
"evalSurveyMonitorPageLoading",
|
||||
"Get > /safetyEval/regulator/eval-project-monitor/survey-page",
|
||||
);
|
||||
|
||||
export const evalSurveyClockIn = declareRequest(
|
||||
"evalSurveyClockInLoading",
|
||||
"Get > /safetyEval/regulator/eval-project-monitor/survey-clock-in",
|
||||
"evalSurveyClockIn: [] | res.data || []",
|
||||
);
|
||||
|
|
@ -411,6 +411,23 @@ export const MONITOR_WARING_STATUS_OPTIONS = [
|
|||
{ label: "人员异常", value: "PERSONNEL_ABNORMAL" },
|
||||
];
|
||||
|
||||
/** 监管端-项目踏勘监管 - 打卡状态筛选选项 */
|
||||
export const CLOCK_IN_STATUS_OPTIONS = [
|
||||
{ label: "正常", value: "normal" },
|
||||
{ label: "异常", value: "abnormal" },
|
||||
];
|
||||
|
||||
export const PROJECT_TYPE_OPTIONS = [
|
||||
{ label: "新建项目", value: "NEW" },
|
||||
{ label: "续评项目", value: "RENEW" },
|
||||
{ label: "变更项目", value: "CHANGE" },
|
||||
];
|
||||
|
||||
export const PROJECT_TYPE_MAP= PROJECT_TYPE_OPTIONS.reduce((acc, cur) => {
|
||||
acc[cur.value] = cur.label;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
||||
export const ROLE_DEFINITIONS = [
|
||||
{ value: "MARKETING_BUSINESS_REP", name: "市场部业务员", unique: false, summary: "获取项目基础信息,形成风险分析记录;风险通过后签订合同并下达任务通知单。" },
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
CheckCircleOutlined,
|
||||
SyncOutlined,
|
||||
BellOutlined,
|
||||
EnvironmentOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
const menuItems = [
|
||||
|
|
@ -200,6 +201,11 @@ const menuItems = [
|
|||
label: "安评项目实时监控",
|
||||
icon: <FileTextOutlined />,
|
||||
},
|
||||
{
|
||||
key: "/safetyEval/container/MonitorManage/SurveyMonitor",
|
||||
label: "项目踏勘监管",
|
||||
icon: <EnvironmentOutlined />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,18 +15,13 @@ import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
|||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||
import { EVAL_TYPE_MAP, ROLE_DEFINITIONS_MAP } from "~/enumerate/constant";
|
||||
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP } from "~/enumerate/enterpriseOptions";
|
||||
import { PROJECT_TYPE_MAP } from "~/enumerate/constant";
|
||||
import "./index.less";
|
||||
|
||||
const { router } = tools;
|
||||
|
||||
const PROJECT_TYPE_MAP = {
|
||||
NEW: "新建项目",
|
||||
RENEW: "续评项目",
|
||||
CHANGE: "变更项目",
|
||||
};
|
||||
|
||||
const YES_NO_MAP = { 1: "是", 2: "否" };
|
||||
|
||||
|
||||
const ATTACH_TYPE_LABELS = {
|
||||
PRACTICE_NOTIFICATION: "从业告知书",
|
||||
|
|
@ -166,8 +161,36 @@ const TeamPanel = ({ data, loading }) => {
|
|||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
let leader=null;
|
||||
let processLeader=null;
|
||||
data.forEach((item) => {
|
||||
if (item.role.includes('PROJECT_LEAD')) {
|
||||
leader = item.personnelName;
|
||||
}
|
||||
if (item.role.includes('PROCESS_CONTROL_LEAD')) {
|
||||
processLeader = item.personnelName;
|
||||
}
|
||||
});
|
||||
return <>
|
||||
<div className="epm-detail-band">
|
||||
<div className="epm-detail-band-item">
|
||||
<span>项目组人数</span>
|
||||
<strong>
|
||||
{data.length || 0}人
|
||||
</strong>
|
||||
</div>
|
||||
<div className="epm-detail-band-item">
|
||||
<span>项目负责人</span>
|
||||
<strong>{leader || "-"}</strong>
|
||||
</div>
|
||||
<div className="epm-detail-band-item">
|
||||
<span>过程控制负责人</span>
|
||||
<strong>
|
||||
{processLeader || "-"}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
return (
|
||||
</div>
|
||||
<Table
|
||||
dataSource={data}
|
||||
columns={columns}
|
||||
|
|
@ -176,7 +199,7 @@ const TeamPanel = ({ data, loading }) => {
|
|||
size="small"
|
||||
scroll={{ x: 800 }}
|
||||
/>
|
||||
);
|
||||
</>
|
||||
};
|
||||
|
||||
// ===== 踏勘情况面板 =====
|
||||
|
|
@ -315,7 +338,7 @@ const EvalProjectMonitorDetail = (props) => {
|
|||
const [teamMembers, setTeamMembers] = useState([]);
|
||||
const [surveyPersonnel, setSurveyPersonnel] = useState([]);
|
||||
const [surveyAttachments, setSurveyAttachments] = useState({});
|
||||
const [activeKey, setActiveKey] = useState("contract");
|
||||
const [activeKey, setActiveKey] = useState("BUSINESS_CONTRACT");
|
||||
|
||||
const fetchContract = async () => {
|
||||
if (!projectId) return;
|
||||
|
|
@ -360,6 +383,24 @@ const EvalProjectMonitorDetail = (props) => {
|
|||
fetchSurvey();
|
||||
}, []);
|
||||
|
||||
const projectNode = JSON.parse(evalProjectDetailData?.projectNode || "[]") || [];
|
||||
const nodeMap = {};
|
||||
projectNode.forEach((n) => {
|
||||
nodeMap[n.type] = n;
|
||||
});
|
||||
|
||||
const renderTabLabel = (label, nodeType) => {
|
||||
const done = nodeMap[nodeType]?.state === 1;
|
||||
return (
|
||||
<span>
|
||||
{label}
|
||||
<Tag color={done ? "success" : "default"} style={{ marginLeft: 6 }}>
|
||||
{done ? "已完成" : "待办理"}
|
||||
</Tag>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const projectInfo = (
|
||||
<div className="epm-detail-band">
|
||||
<div className="epm-detail-band-item">
|
||||
|
|
@ -377,12 +418,16 @@ const EvalProjectMonitorDetail = (props) => {
|
|||
<div className="epm-detail-band-item">
|
||||
<span>评价机构</span>
|
||||
<strong>
|
||||
{contractData?.orgName || evalProjectDetailData?.orgName || "-"}
|
||||
{contractData?.orgName || "-"}
|
||||
</strong>
|
||||
</div>
|
||||
<div className="epm-detail-band-item">
|
||||
<span>项目负责人</span>
|
||||
<strong>{evalProjectDetailData?.leaderName || "-"}</strong>
|
||||
<strong>{evalProjectDetailData?.projectLeaderName || "-"}</strong>
|
||||
</div>
|
||||
<div className="epm-detail-band-item">
|
||||
<span>总体状态</span>
|
||||
<strong>{projectNode.filter((item) => item.state === 1).length}/{projectNode.length}</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -395,20 +440,20 @@ const EvalProjectMonitorDetail = (props) => {
|
|||
onChange={setActiveKey}
|
||||
items={[
|
||||
{
|
||||
key: "contract",
|
||||
label: "项目合同",
|
||||
key: "BUSINESS_CONTRACT",
|
||||
label: renderTabLabel("项目合同", "BUSINESS_CONTRACT"),
|
||||
children: (
|
||||
<ContractPanel data={contractData} loading={contractLoading} />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "team",
|
||||
label: "项目组人员",
|
||||
key: "PROJECT_GROUP",
|
||||
label: renderTabLabel("项目组人员", "PROJECT_GROUP"),
|
||||
children: <TeamPanel data={teamMembers} loading={memberLoading} />,
|
||||
},
|
||||
{
|
||||
key: "survey",
|
||||
label: "踏勘情况",
|
||||
key: "SITE_SURVEY",
|
||||
label: renderTabLabel("踏勘情况", "SITE_SURVEY"),
|
||||
children: (
|
||||
<SurveyPanel
|
||||
personnel={surveyPersonnel}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,354 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Card,
|
||||
Statistic,
|
||||
Table,
|
||||
Tag,
|
||||
Button,
|
||||
Form,
|
||||
Modal,
|
||||
Space,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
||||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||||
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
|
||||
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||
import {
|
||||
CLOCK_IN_STATUS_OPTIONS,
|
||||
ROLE_DEFINITIONS_MAP,
|
||||
} from "~/enumerate/constant";
|
||||
import "./index.less";
|
||||
|
||||
const { router } = tools;
|
||||
|
||||
const STAT_ITEMS = [
|
||||
{ key: "notificationArchivedCount", title: "告知书已归档", color: "#1677ff" },
|
||||
{ key: "clockInNormalCount", title: "人员打卡正常", color: "#52c41a" },
|
||||
{ key: "notificationAbnormalCount", title: "告知异常", color: "#faad14" },
|
||||
{ key: "clockInAbnormalCount", title: "打卡异常", color: "#ff4d4f" },
|
||||
];
|
||||
|
||||
/** 解析文件 JSON */
|
||||
const parseFileList = (raw) => {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 根据打卡数据计算状态 */
|
||||
const getClockInStatus = (record) => {
|
||||
if (record.actualCount < record.expectedCount) {
|
||||
return { color: "error", text: "人员未到齐" };
|
||||
}
|
||||
if (
|
||||
record.signInCount < record.actualCount ||
|
||||
record.signOutCount < record.actualCount
|
||||
) {
|
||||
return { color: "warning", text: "异常" };
|
||||
}
|
||||
return { color: "success", text: "正常" };
|
||||
};
|
||||
|
||||
const SurveyMonitor = (props) => {
|
||||
const [searchForm] = Form.useForm();
|
||||
const [dataSource, setDataSource] = useState([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [clockInModal, setClockInModal] = useState(false);
|
||||
const [clockInData, setClockInData] = useState([]);
|
||||
const [clockInProjectName, setClockInProjectName] = useState("");
|
||||
|
||||
const { safetyEvalBusiness, evalSurveyMonitorStat, evalSurveyMonitorPage,
|
||||
evalSurveyClockIn } = props;
|
||||
const { evalSurveyMonitorStatLoading, evalSurveyMonitorPageLoading,
|
||||
evalSurveyClockInLoading } = safetyEvalBusiness || {};
|
||||
const statData = safetyEvalBusiness?.evalSurveyMonitorStat || {};
|
||||
|
||||
const getStat = () => {
|
||||
evalSurveyMonitorStat();
|
||||
};
|
||||
|
||||
const getData = async () => {
|
||||
const params = {
|
||||
...router.query,
|
||||
current: router.query.current || 1,
|
||||
size: router.query.size || 10,
|
||||
};
|
||||
const res = await evalSurveyMonitorPage(params);
|
||||
if (res?.success !== false) {
|
||||
setDataSource(res?.data || []);
|
||||
setTotal(res?.total || 0);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
searchForm.setFieldsValue({
|
||||
...router.query,
|
||||
dateRange:
|
||||
router.query.startTime && router.query.endTime
|
||||
? [router.query.startTime, router.query.endTime]
|
||||
: undefined,
|
||||
});
|
||||
getStat();
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
const handleSearch = (values) => {
|
||||
const { dateRange, ...rest } = values;
|
||||
router.query = {
|
||||
...rest,
|
||||
startTime: dateRange?.[0]?.format?.("YYYY-MM-DD") || undefined,
|
||||
endTime: dateRange?.[1]?.format?.("YYYY-MM-DD") || undefined,
|
||||
current: 1,
|
||||
size: 10,
|
||||
};
|
||||
getData();
|
||||
};
|
||||
|
||||
const handleReset = (values) => {
|
||||
searchForm.resetFields();
|
||||
router.query = { ...values, current: 1, size: 10 };
|
||||
getData();
|
||||
};
|
||||
|
||||
const handlePageChange = (pagination) => {
|
||||
router.query = {
|
||||
...router.query,
|
||||
current: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
};
|
||||
getData();
|
||||
};
|
||||
|
||||
const handleViewClockIn = async (record) => {
|
||||
setClockInProjectName(record.projectName);
|
||||
setClockInModal(true);
|
||||
const res = await evalSurveyClockIn({ projectId: record.id });
|
||||
if (res?.success !== false) {
|
||||
setClockInData(res?.data || []);
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewNotice = (record) => {
|
||||
const files = parseFileList(record.notificationFileUrl);
|
||||
if (files.length > 0 && files[0].url) {
|
||||
window.open(files[0].url, "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
const clockInColumns = [
|
||||
{ title: "人员", dataIndex: "userName", width: 100 },
|
||||
{
|
||||
title: "项目角色",
|
||||
dataIndex: "role",
|
||||
width: 160,
|
||||
ellipsis: true,
|
||||
render: (v) =>
|
||||
Array.isArray(v)
|
||||
? v.map((r) => ROLE_DEFINITIONS_MAP[r] || r).join("、")
|
||||
: v || "-",
|
||||
},
|
||||
{
|
||||
title: "人脸识别",
|
||||
dataIndex: "faceState",
|
||||
width: 90,
|
||||
render: (v) => (
|
||||
<Tag color={v === 1 ? "success" : "error"}>
|
||||
{v === 1 ? "通过" : "未通过"}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "GPS定位",
|
||||
dataIndex: "gpsState",
|
||||
width: 90,
|
||||
render: (v) => (
|
||||
<Tag color={v === 1 ? "success" : "error"}>
|
||||
{v === 1 ? "正常" : "异常"}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{ title: "签到时间", dataIndex: "singInTime", width: 160 },
|
||||
{ title: "签退时间", dataIndex: "signOutTime", width: 160 },
|
||||
{
|
||||
title: "现场照片",
|
||||
dataIndex: "sceneUrl",
|
||||
width: 80,
|
||||
render: (v) => {
|
||||
const count = v ? v.split(",").filter(Boolean).length : 0;
|
||||
return count > 0 ? `${count}张` : "-";
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "本人签字",
|
||||
dataIndex: "signatureUrl",
|
||||
width: 90,
|
||||
render: (v) => (
|
||||
<Tag color={v ? "success" : "default"}>
|
||||
{v ? "已签字" : "未签字"}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{ title: "项目编号", dataIndex: "projectNo", width: 140 },
|
||||
{ title: "项目名称", dataIndex: "projectName", ellipsis: true, width: 220 },
|
||||
{ title: "评价机构", dataIndex: "orgName", ellipsis: true, width: 200 },
|
||||
{
|
||||
title: "告知书状态",
|
||||
dataIndex: "notificationFileUrl",
|
||||
width: 120,
|
||||
render: (v) => {
|
||||
const files = parseFileList(v);
|
||||
return files.length > 0 ? (
|
||||
<Tag color="success">已归档</Tag>
|
||||
) : (
|
||||
<Tag color="warning">待上传</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "应到/实到",
|
||||
width: 100,
|
||||
render: (_, r) => `${r.expectedCount} / ${r.actualCount}`,
|
||||
},
|
||||
{
|
||||
title: "签到/签退",
|
||||
width: 100,
|
||||
render: (_, r) => `${r.signInCount} / ${r.signOutCount}`,
|
||||
},
|
||||
{
|
||||
title: "现场照片",
|
||||
dataIndex: "scenePhotoCount",
|
||||
width: 90,
|
||||
render: (v) => (v ? `${v}张` : "-"),
|
||||
},
|
||||
{
|
||||
title: "打卡状态",
|
||||
width: 100,
|
||||
render: (_, r) => {
|
||||
const status = getClockInStatus(r);
|
||||
return <Tag color={status.color}>{status.text}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 160,
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<Space size={4}>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
disabled={!parseFileList(record.notificationFileUrl).length}
|
||||
onClick={() => handleViewNotice(record)}
|
||||
>
|
||||
告知书
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => handleViewClockIn(record)}>
|
||||
人员打卡
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageLayout title="项目踏勘监管">
|
||||
<Row gutter={16} className="sm-stat-row">
|
||||
{STAT_ITEMS.map((item) => (
|
||||
<Col span={6} key={item.key}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={item.title}
|
||||
value={statData[item.key] ?? "-"}
|
||||
valueStyle={{ color: item.color }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<SearchForm
|
||||
form={searchForm}
|
||||
loading={false}
|
||||
style={{ marginBottom: 16 }}
|
||||
formLine={[
|
||||
<Form.Item key="projectNameOrNo" name="projectNameOrNo">
|
||||
<ControlWrapper.Input
|
||||
label="项目名称/编号"
|
||||
placeholder="请输入"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>,
|
||||
<Form.Item key="dateRange" name="dateRange">
|
||||
<ControlWrapper.DatePicker.RangePicker
|
||||
label="踏勘日期"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Form.Item>,
|
||||
<Form.Item key="clockInStatus" name="clockInStatus">
|
||||
<ControlWrapper.Select
|
||||
label="打卡状态"
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
options={CLOCK_IN_STATUS_OPTIONS}
|
||||
/>
|
||||
</Form.Item>,
|
||||
]}
|
||||
onFinish={handleSearch}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
loading={evalSurveyMonitorPageLoading}
|
||||
scroll={{ y: props.scrollY, x: 1300 }}
|
||||
pagination={{
|
||||
total,
|
||||
current: Number(router.query.current) || 1,
|
||||
pageSize: Number(router.query.size) || 10,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (t) => `共 ${t} 条`,
|
||||
}}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title={`人员打卡详情 - ${clockInProjectName}`}
|
||||
open={clockInModal}
|
||||
onCancel={() => setClockInModal(false)}
|
||||
footer={null}
|
||||
width={1000}
|
||||
>
|
||||
<Table
|
||||
dataSource={clockInData}
|
||||
columns={clockInColumns}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
size="small"
|
||||
loading={evalSurveyClockInLoading}
|
||||
scroll={{ x: 900 }}
|
||||
/>
|
||||
</Modal>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Connect(
|
||||
[NS_SAFETY_EVAL_BUSINESS],
|
||||
true,
|
||||
)(AntdTableFuncControl(SurveyMonitor));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.sm-stat-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
Spin,
|
||||
} from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import {PROJECT_TYPE_OPTIONS ,EVAL_TYPE_OPTIONS} from '~/enumerate/constant';
|
||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { district } from "~/enumerate/constant";
|
||||
|
|
@ -25,17 +26,9 @@ import AttachmentUpload from "~/components/AttachmentUpload";
|
|||
const { router } = tools;
|
||||
const { TextArea } = Input;
|
||||
|
||||
const PROJECT_TYPE_OPTIONS = [
|
||||
{ label: "新建项目", value: "NEW" },
|
||||
{ label: "续评项目", value: "RENEW" },
|
||||
{ label: "变更项目", value: "CHANGE" },
|
||||
];
|
||||
|
||||
const EVAL_TYPE_OPTIONS = [
|
||||
{ label: "安全预评价", value: "PRE" },
|
||||
{ label: "安全验收评价", value: "ACCEPT" },
|
||||
{ label: "安全现状评价", value: "STATUS" },
|
||||
];
|
||||
|
||||
|
||||
|
||||
const YES_NO_OPTIONS = [
|
||||
{ label: "是", value: 1 },
|
||||
|
|
|
|||
|
|
@ -12,21 +12,13 @@ import dayjs from "dayjs";
|
|||
const { router } = tools;
|
||||
|
||||
const STAGES = [
|
||||
{ key: "notice", index: "01", label: "从业告知" },
|
||||
{ key: "inspection", index: "02", label: "现场勘查" },
|
||||
{ key: "rectification", index: "03", label: "整改通知" },
|
||||
{ key: "enterprise", index: "04", label: "企业整改" },
|
||||
{ key: "review", index: "05", label: "整改复查" },
|
||||
{ key: "PRACTICE_NOTIFICATION", index: "01", label: "从业告知" },
|
||||
{ key: "SITE_INSPECTION_FORM", index: "02", label: "现场勘查" },
|
||||
{ key: "RECTIFICATION_NOTICE", index: "03", label: "整改通知" },
|
||||
{ key: "ENTERPRISE_RECTIFICATION", index: "04", label: "企业整改" },
|
||||
{ key: "RECTIFICATION_REVIEW", index: "05", label: "整改复查" },
|
||||
];
|
||||
|
||||
const ATTACH_TYPE = {
|
||||
notice: "PRACTICE_NOTIFICATION",
|
||||
inspection: "SITE_INSPECTION_FORM",
|
||||
rectification: "RECTIFICATION_NOTICE",
|
||||
enterprise: "ENTERPRISE_RECTIFICATION",
|
||||
review: "RECTIFICATION_REVIEW",
|
||||
};
|
||||
|
||||
const downloadTemplate = async () => {
|
||||
try {
|
||||
const res = await fetch("https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a608cb30dc53ed98d0b13ae.pdf");
|
||||
|
|
@ -137,7 +129,7 @@ const SimpleUploadPanel = ({ index, title, desc }) => (
|
|||
|
||||
const SurveyContent = (props) => {
|
||||
const [form] = Form.useForm();
|
||||
const [activeStage, setActiveStage] = useState("notice");
|
||||
const [activeStage, setActiveStage] = useState("PRACTICE_NOTIFICATION");
|
||||
const [attachMap, setAttachMap] = useState({});
|
||||
const [surveyPersonnel, setSurveyPersonnel] = useState([]);
|
||||
const { evalSurveyListLoading, evalSurveySaveAttachLoading, evalProjectDetailData } = props.safetyEvalBusiness;
|
||||
|
|
@ -163,8 +155,8 @@ const SurveyContent = (props) => {
|
|||
setAttachMap(map);
|
||||
// 回填所有步骤的附件到表单
|
||||
const values = {};
|
||||
Object.entries(ATTACH_TYPE).forEach(([, typeCode]) => {
|
||||
values[typeCode] = map[typeCode] || [];
|
||||
STAGES.forEach((s) => {
|
||||
values[s.key] = map[s.key] || [];
|
||||
});
|
||||
form.setFieldsValue(values);
|
||||
}
|
||||
|
|
@ -195,17 +187,17 @@ const SurveyContent = (props) => {
|
|||
const handleSave = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
const payload = [];
|
||||
for (const [key, typeCode] of Object.entries(ATTACH_TYPE)) {
|
||||
const fileList = values[typeCode] || [];
|
||||
for (const s of STAGES) {
|
||||
const fileList = values[s.key] || [];
|
||||
const validFiles = fileList.filter((f) => f.status === "done" && f.url);
|
||||
if (validFiles.length === 0) {
|
||||
setActiveStage(key);
|
||||
message.warning(`请先在"${STAGES.find((s) => s.key === key).label}"步骤上传附件`);
|
||||
setActiveStage(s.key);
|
||||
message.warning(`请先在"${s.label}"步骤上传附件`);
|
||||
return;
|
||||
}
|
||||
payload.push({
|
||||
projectId,
|
||||
attachTypeCode: typeCode,
|
||||
attachTypeCode: s.key,
|
||||
fileUrl: JSON.stringify(fileList),
|
||||
});
|
||||
}
|
||||
|
|
@ -225,21 +217,21 @@ const SurveyContent = (props) => {
|
|||
items={STAGES.map((s) => ({ title: s.label }))}
|
||||
/>
|
||||
|
||||
{activeStage === "notice" && <NoticePanel />}
|
||||
{activeStage === "inspection" && <InspectionPanel dataSource={surveyPersonnel} evalProjectDetailData={evalProjectDetailData} />}
|
||||
{activeStage === "rectification" && (
|
||||
{activeStage === "PRACTICE_NOTIFICATION" && <NoticePanel />}
|
||||
{activeStage === "SITE_INSPECTION_FORM" && <InspectionPanel dataSource={surveyPersonnel} evalProjectDetailData={evalProjectDetailData} />}
|
||||
{activeStage === "RECTIFICATION_NOTICE" && (
|
||||
<SimpleUploadPanel
|
||||
index="03" title="整改通知单上传"
|
||||
desc="本节点不在线编制整改通知,只归档机构已出具的《整改通知单》文件;上传成功即完成,后续可替换修改。"
|
||||
/>
|
||||
)}
|
||||
{activeStage === "enterprise" && (
|
||||
{activeStage === "ENTERPRISE_RECTIFICATION" && (
|
||||
<SimpleUploadPanel
|
||||
index="04" title="企业整改报告上传"
|
||||
desc="企业线下提交《整改报告》后,由机构人员上传归档;本系统不提供企业在线提交和逐项整改流程。"
|
||||
/>
|
||||
)}
|
||||
{activeStage === "review" && (
|
||||
{activeStage === "RECTIFICATION_REVIEW" && (
|
||||
<SimpleUploadPanel
|
||||
index="05" title="整改复查单上传"
|
||||
desc="机构人员现场复查后,将经双方签字确认的《整改复查单》上传归档;上传后本节点自动标记完成。"
|
||||
|
|
@ -247,10 +239,10 @@ const SurveyContent = (props) => {
|
|||
)}
|
||||
|
||||
<Form form={form} layout="vertical">
|
||||
{Object.entries(ATTACH_TYPE).map(([key, typeCode]) => (
|
||||
<div key={key} style={{ display: key === activeStage ? "block" : "none" }}>
|
||||
{STAGES.map((s) => (
|
||||
<div key={s.key} style={{ display: s.key === activeStage ? "block" : "none" }}>
|
||||
<AttachmentUpload
|
||||
name={typeCode}
|
||||
name={s.key}
|
||||
label="上传附件"
|
||||
maxCount={10}
|
||||
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.xls,.xlsx"
|
||||
|
|
|
|||
Loading…
Reference in New Issue