v0.0.1-final
tangjie 2026-07-16 11:27:49 +08:00
parent aa37a77ebd
commit dacae8af48
8 changed files with 85 additions and 104 deletions

View File

@ -11,7 +11,8 @@ module.exports = {
// 本地联调 safetyEval-servicecontext-path: /safetyEval默认端口 8095
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
//API_HOST: "https://gbs-gateway.qhdsafety.com",
API_HOST: "http://192.168.0.150",
//API_HOST: "http://192.168.0.150",太浅
API_HOST: "http://192.168.0.152",
},
production: {
// 应用后端分支名称,部署上线需要

View File

@ -76,3 +76,9 @@ export const queryQualMonitorCondition = declareRequest(
"Get > /safetyEval/qual-monitor/condition-detail",
'qualMonitorCondition: {} | res.data || {}',
);
export const queryQualMonitoringPageStatistics = declareRequest(
"qualReviewLoading",
"Get > /safetyEval/qual-filing/qualificationMonitoringPageStatistics",
"monitorStats: {} | res.data || {}",
);

View File

@ -0,0 +1,7 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const queryRiskOverviewAction = declareRequest(
"riskOverviewLoading",
"Get > /safetyEval/regulator/risk-center/overview",
"overviewStats: [] | res.data.stats || [] & processGuide: '' | res.data.processGuide || ''",
);

View File

@ -118,3 +118,17 @@ export const QUAL_MONITOR_STATUS_OPTIONS = [
{ value: "normal", label: "正常" },
{ value: "person_rate", label: "人员比例预警" },
];
/** 证书类型选项 */
export const CERT_TYPE_OPTIONS = [
{ value: "registered_safety_engineer", label: "注册安全工程师" },
{ value: "senior_engineer", label: "高工" },
{ value: "evaluator", label: "评价师" },
];
/** 证书类型 value → label 映射 */
export const CERT_TYPE_MAP = {
registered_safety_engineer: "注册安全工程师",
senior_engineer: "高工",
evaluator: "评价师",
};

View File

@ -21,3 +21,4 @@ export const NS_QUAL_FILING = defineNamespace("qualFiling");
export const NS_QUAL_REVIEW = defineNamespace("qualReview");
export const NS_QUAL_EXPERT = defineNamespace("qualExpert");
export const NS_DRIVER = defineNamespace("driver");
export const NS_RISK_CENTER = defineNamespace("riskCenter");

View File

@ -13,6 +13,7 @@ import { NS_STAFF_CERTIFICATE } from "~/enumerate/namespace";
import CertPreviewImg from "~/components/CertPreviewImg";
import AttachmentUpload from "~/components/AttachmentUpload";
import { dateRangeRule } from "~/utils/validators";
import { CERT_TYPE_OPTIONS, CERT_TYPE_MAP } from "~/enumerate/constant";
const { router } = tools;
@ -115,8 +116,8 @@ function StaffCertificatePage(props) {
<Table
rowKey="id"
columns={[
{ title: "证照名称", dataIndex: "certName" },
{ title: "证书类型", dataIndex: "certTypeName" },
{ title: "证照名称", dataIndex: "certName" ,ellipsis:true},
{ title: "证书类型", dataIndex: "certTypeCode", render: (v) => CERT_TYPE_MAP[v] || v },
{ title: "证书类别", dataIndex: "certCategoryName" },
{ title: "证书作业类别", dataIndex: "operationCategoryName" },
{ title: "证书编号", dataIndex: "certNo" },
@ -332,17 +333,10 @@ function CertModal({
</Form.Item>
<Form.Item
name="certTypeCode"
label="证书类型编码"
rules={[{ max: 32, message: "证书类型编码不能超过32个字符" }]}
label="证书类型"
rules={[{ required: true, message: "请选择证书类型" }]}
>
<Input placeholder="请输入证书类型编码" maxLength={32} showCount />
</Form.Item>
<Form.Item
name="certTypeName"
label="证书类型名称"
rules={[{ max: 50, message: "证书类型名称不能超过50个字符" }]}
>
<Input placeholder="请输入证书类型名称" maxLength={50} showCount />
<Select placeholder="请选择证书类型" options={CERT_TYPE_OPTIONS} />
</Form.Item>
<Form.Item
name="certCategoryCode"
@ -435,8 +429,7 @@ function ViewModal({ open, currentId, staffCertificateInfo, onCancel }) {
>
<Descriptions bordered column={1} labelStyle={{ width: 160 }}>
<Descriptions.Item label="证照名称">{info.certName}</Descriptions.Item>
<Descriptions.Item label="证书类型编码">{info.certTypeCode}</Descriptions.Item>
<Descriptions.Item label="证书类型名称">{info.certTypeName}</Descriptions.Item>
<Descriptions.Item label="证书类型">{CERT_TYPE_MAP[info.certTypeCode] || info.certTypeCode}</Descriptions.Item>
<Descriptions.Item label="证书类别编码">{info.certCategoryCode}</Descriptions.Item>
<Descriptions.Item label="证书类别名称">{info.certCategoryName}</Descriptions.Item>
<Descriptions.Item label="证书作业类别编码">{info.operationCategoryCode}</Descriptions.Item>

View File

@ -108,26 +108,21 @@ function buildConditionDetails(record) {
};
}
/** 统计卡片数据 */
function computeStats(list) {
return {
total: list.length,
normal: list.filter((i) => i.alertStatus === "normal").length,
warning: list.filter((i) => i.alertStatus === "person_rate").length,
};
}
const QualMonitor = (props) => {
const [form] = Form.useForm();
const [detailVisible, setDetailVisible] = useState(false);
const [detailRecord, setDetailRecord] = useState(null);
const [data, setData] = useState([]);
const { qualReview, queryQualMonitorPage, queryQualMonitorCondition } = props;
const { qualMonitorList, qualMonitorTotal, qualReviewLoading } =
const { qualReview, queryQualMonitorPage, queryQualMonitorCondition, queryQualMonitoringPageStatistics } = props;
const { qualMonitorList, qualMonitorTotal, qualReviewLoading, monitorStats = {} } =
qualReview || {};
const stats = computeStats(data);
const stats = {
total: monitorStats.orgInfoSum ?? 0,
normal: monitorStats.normalSum ?? 0,
warning: monitorStats.earlyWarningSum ?? 0,
};
/** model 数据回填时同步到本地 data */
useEffect(() => {
@ -145,10 +140,7 @@ const QualMonitor = (props) => {
/** 调用接口 */
const handleSearch = () => {
const params = { ...router.query };
if (params.keyword) {
params.unitName = params.keyword;
delete params.keyword;
}
queryQualMonitorPage(params);
};
@ -160,6 +152,7 @@ const QualMonitor = (props) => {
useEffect(() => {
form.setFieldsValue(router.query);
handleSearch();
queryQualMonitoringPageStatistics();
}, []);
const openDetail = (record) => {

View File

@ -1,10 +1,12 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Button, Form, Table, Tag } from "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 { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_RISK_CENTER } from "~/enumerate/namespace";
import "./index.less";
const { router } = tools;
@ -129,17 +131,24 @@ const MOCK_DATA = [
},
];
/** 统计卡片样式主题 */
const CARD_THEMES = [
{ bg: "#fee2e2", color: "#dc2626" },
{ bg: "#fef3c7", color: "#d97706" },
{ bg: "#dbeafe", color: "#2563eb" },
{ bg: "#d1fae5", color: "#059669" },
];
const RiskCenter = (props) => {
const [form] = Form.useForm();
const [data] = useState(MOCK_DATA);
/** 统计卡片数据 */
const stats = {
qualRisk: data.filter((i) => i.type === "qual").length,
projRisk: data.filter((i) => i.type === "proj").length,
pendingFeedback: data.filter((i) => i.orgStatus === "submitted").length,
todayClosed: data.filter((i) => i.stage === "closed").length,
};
const { riskCenter, queryRiskOverviewAction } = props;
const { overviewStats = [], riskOverviewLoading } = riskCenter || {};
useEffect(() => {
queryRiskOverviewAction();
}, []);
const handleSearch = () => {
// 接口搜索
@ -232,70 +241,24 @@ const RiskCenter = (props) => {
<div className="risk-center">
{/* 统计卡片 */}
<div className="stat-grid cols-4">
<div className="stat-card">
{overviewStats.map((stat, idx) => {
const theme = CARD_THEMES[idx % CARD_THEMES.length];
return (
<div className="stat-card" key={idx}>
<div className="stat-info">
<div className="stat-label">资质预警</div>
<div className="stat-value stat-value--high">
{stats.qualRisk}
</div>
<div className="stat-hint">
条件保持人员比例属地材料
</div>
<div className="stat-label">{stat.label}</div>
<div className="stat-value">{stat.value}</div>
<div className="stat-hint">{stat.hint}</div>
</div>
<div
className="stat-icon"
style={{ background: "#fee2e2", color: "#dc2626" }}
style={{ background: theme.bg, color: theme.color }}
>
</div>
</div>
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">项目预警</div>
<div className="stat-value stat-value--mid">
{stats.projRisk}
</div>
<div className="stat-hint">
进度人员告知勘查质控
</div>
</div>
<div
className="stat-icon"
style={{ background: "#fef3c7", color: "#d97706" }}
>
</div>
</div>
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">待机构反馈</div>
<div className="stat-value stat-value--mid">
{stats.pendingFeedback}
</div>
<div className="stat-hint">整改说明或证明材料</div>
</div>
<div
className="stat-icon"
style={{ background: "#dbeafe", color: "#2563eb" }}
>
</div>
</div>
<div className="stat-card">
<div className="stat-info">
<div className="stat-label">今日闭环</div>
<div className="stat-value stat-value--ok">
{stats.todayClosed}
</div>
<div className="stat-hint">监管复核已完成</div>
</div>
<div
className="stat-icon"
style={{ background: "#d1fae5", color: "#059669" }}
>
{stat.label.charAt(0)}
</div>
</div>
);
})}
</div>
{/* 项目预警规则提示 */}
@ -397,4 +360,7 @@ const RiskCenter = (props) => {
);
};
export default AntdTableFuncControl(RiskCenter);
export default Connect(
[NS_RISK_CENTER],
true,
)(AntdTableFuncControl(RiskCenter));