dev-tmp1
tangjie 2026-08-17 17:38:30 +08:00
parent d74349355a
commit f2c03e879e
21 changed files with 246 additions and 351 deletions

View File

@ -32,7 +32,7 @@ const CustomerDetailModal = ({ open, customerId, onCancel, ...props }) => {
loading={customerDetailLoading}
onCancel={onCancel}
footer={<Button onClick={onCancel}>关闭</Button>}
width={700}
width={900}
destroyOnHidden
>
{detail && (
@ -41,7 +41,9 @@ const CustomerDetailModal = ({ open, customerId, onCancel, ...props }) => {
column={2}
bordered
size="small"
style={{ marginBottom: 16 }}
labelStyle={{
width: '120px'
}}
>
<Descriptions.Item label="客户名称">
{detail.customerName}

View File

@ -0,0 +1,19 @@
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
/**
* 将节点传送到 PageLayout footer 插槽配合 footer={<div id="page-footer-slot" />} 使用
* @param {String} [slotId] - 插槽节点 id
* @param {ReactNode} children - 底部按钮等内容为空时不渲染
*/
const LayoutFooterPortal = ({ slotId = "page-footer-slot", children }) => {
const [slot, setSlot] = useState(null);
useEffect(() => {
setSlot(document.getElementById(slotId));
}, [slotId]);
return slot && children ? createPortal(children, slot) : null;
};
export default LayoutFooterPortal;

View File

@ -11,13 +11,14 @@
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 12px;
padding: 10px;
display: flex;
justify-content: space-between;
.stat-info {
.stat-label {
font-size: 16px;
color: #64748b;
}

View File

@ -7,6 +7,7 @@ import {
Input,
Select,
ConfigProvider,
Tag,
message,
Upload,
} from "antd";
@ -18,6 +19,7 @@ import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_INSP_NOTICE } from "~/enumerate/namespace";
import dayjs from "dayjs";
import StatCardGroup from "~/components/StatCardGroup";
import "./index.less";
const { TextArea } = Input;
@ -113,12 +115,12 @@ const PROCESS_TYPE_OPTIONS = [
{ label: "查看办结结果", value: "查看办结结果" },
];
const statusTagClass = (code) => {
if (code === 1) return "inst-tag-danger";
if (code === 2) return "inst-tag-warning";
if (code === 3) return "inst-tag-info";
if (code === 4) return "inst-tag-success";
return "";
const statusTagColor = (code) => {
if (code === 1) return "error";
if (code === 2) return "warning";
if (code === 3) return "processing";
if (code === 4) return "success";
return undefined;
};
const statusDisplayName = (code, name) => {
@ -135,11 +137,11 @@ const noticeTypeLabel = (record) => {
return record.inspTypeName || "监督检查通知";
};
const noticeTypeTagClass = (record) => {
if (record.statusCode === 4) return "inst-tag-success";
if (record.statusCode === 1) return "inst-tag-danger";
if (record.statusCode === 3) return "inst-tag-info";
return "inst-tag-warning";
const noticeTypeTagColor = (record) => {
if (record.statusCode === 4) return "success";
if (record.statusCode === 1) return "error";
if (record.statusCode === 3) return "processing";
return "warning";
};
const formatDate = (val) => {
@ -428,23 +430,23 @@ const InstitutionInspectionNotice = (props) => {
title: "通知编号",
dataIndex: "noticeNo",
width: 140,
render: (t) => <span className="inst-mono">{t || "-"}</span>,
},
{
title: "通知类型",
dataIndex: "inspTypeName",
width: 110,
render: (_, record) => (
<span className={`inst-tag ${noticeTypeTagClass(record)}`}>
<Tag color={noticeTypeTagColor(record)}>
{noticeTypeLabel(record)}
</span>
</Tag>
),
},
{
title: "来源预警",
dataIndex: "sourceRefNo",
width: 140,
render: (t) => t || "-",
},
{
title: "来源字段",
@ -452,13 +454,9 @@ const InstitutionInspectionNotice = (props) => {
width: 180,
render: (_, record) => (
<div className="inst-source-list">
{record.sourceTypeName ? (
<span className="inst-source-pill">{record.sourceTypeName}</span>
) : (
<span className="inst-source-pill">监督检查</span>
)}
<Tag>{record.sourceTypeName || "监督检查"}</Tag>
{record.inspTypeName ? (
<span className="inst-source-pill">{record.inspTypeName}</span>
<Tag>{record.inspTypeName}</Tag>
) : null}
</div>
),
@ -480,9 +478,9 @@ const InstitutionInspectionNotice = (props) => {
dataIndex: "statusCode",
width: 110,
render: (code, record) => (
<span className={`inst-tag ${statusTagClass(code)}`}>
<Tag color={statusTagColor(code)}>
{statusDisplayName(code, record.statusName)}
</span>
</Tag>
),
},
{
@ -531,40 +529,50 @@ const InstitutionInspectionNotice = (props) => {
</div>
</div>
<div className="inst-metrics">
<div className="inst-metric-card">
<div>
<div className="label">监管消息总数</div>
<div className="value">{metrics.total}</div>
<div className="hint">来源监管端风险预警中心</div>
</div>
<div className="icon"></div>
</div>
<div className="inst-metric-card">
<div>
<div className="label">待处理</div>
<div className="value danger">{metrics.pending}</div>
<div className="hint">需机构直接处理</div>
</div>
<div className="icon"></div>
</div>
<div className="inst-metric-card">
<div>
<div className="label">待监管复核</div>
<div className="value warning">{metrics.feedback}</div>
<div className="hint">材料已提交</div>
</div>
<div className="icon"></div>
</div>
<div className="inst-metric-card">
<div>
<div className="label">已办结</div>
<div className="value success">{metrics.closed}</div>
<div className="hint">办结消息可归档</div>
</div>
<div className="icon"></div>
</div>
</div>
<StatCardGroup
items={[
{
key: "total",
title: "监管消息总数",
value: metrics.total,
valueColor: "#1e293b",
hint: "来源:监管端风险预警中心",
iconBg: "#eef2ff",
iconColor: "#4f46e5",
iconText: "✉️",
},
{
key: "pending",
title: "待处理",
value: metrics.pending,
valueColor: "#ff4d4f",
hint: "需机构直接处理",
iconBg: "#fff1f0",
iconColor: "#ff4d4f",
iconText: "待",
},
{
key: "feedback",
title: "待监管复核",
value: metrics.feedback,
valueColor: "#faad14",
hint: "材料已提交",
iconBg: "#fffbe6",
iconColor: "#faad14",
iconText: "审",
},
{
key: "closed",
title: "已办结",
value: metrics.closed,
valueColor: "#52c41a",
hint: "办结消息可归档",
iconBg: "#f6ffed",
iconColor: "#52c41a",
iconText: "结",
},
]}
/>
<div className="inst-note">
<strong>消息来源</strong>

View File

@ -41,76 +41,9 @@
font-weight: 600;
}
.inst-metrics {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-bottom: 12px;
}
@media (max-width: 1100px) {
.inst-metrics {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.inst-metric-card {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
padding: 14px 16px;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #fff;
}
.inst-metric-card .label {
font-size: 16px;
color: #64748b;
}
.inst-metric-card .value {
font-size: 20px;
font-weight: 700;
color: #0f172a;
margin: 4px 0;
line-height: 1.2;
}
.inst-metric-card .value.danger {
color: #dc2626;
}
.inst-metric-card .value.warning {
color: #d97706;
}
.inst-metric-card .value.success {
color: #059669;
}
.inst-metric-card .hint {
font-size: 16px;
color: #94a3b8;
}
.inst-metric-card .icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: #f1f5f9;
color: #64748b;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.inst-note {
margin-bottom: 12px;
padding: 10px 12px;
padding: 6px 12px;
border-radius: 8px;
background: #f8fafc;
border: 1px solid #e2e8f0;
@ -124,7 +57,7 @@
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
margin-bottom: 6px;
flex-wrap: wrap;
}
@ -145,38 +78,6 @@
color: #999;
}
.inst-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
font-size: 14px;
font-weight: 500;
background: #f1f5f9;
color: #475569;
}
.inst-tag-success {
background: #d1fae5;
color: #065f46;
}
.inst-tag-info {
background: #dbeafe;
color: #1e40af;
}
.inst-tag-warning {
background: #fef3c7;
color: #92400e;
}
.inst-tag-danger {
background: #fee2e2;
color: #991b1b;
}
.inst-modal-section {
border: 1px solid #e2e8f0;
border-radius: 8px;
@ -272,16 +173,6 @@
gap: 4px;
}
.inst-source-pill {
display: inline-block;
padding: 0 6px;
border-radius: 999px;
font-size: 14px;
background: #eff6ff;
color: #1d4ed8;
border: 1px solid #bfdbfe;
}
.inst-upload-tip {
font-size: 14px;
color: #64748b;

View File

@ -39,7 +39,7 @@ export default function ChangeHistoryModal({ open, filingId, onCancel }) {
<Modal
open={open}
title="备案变更记录"
width={560}
width={790}
destroyOnHidden
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
@ -55,7 +55,7 @@ export default function ChangeHistoryModal({ open, filingId, onCancel }) {
columns={[
{ title: "序号", width: 60, render: (_, __, index) => index + 1 },
{ title: "变更事项", dataIndex: "changeItemName" },
{ title: "变更时间", dataIndex: "changeTime", width: 120 },
{ title: "变更时间", dataIndex: "changeTime", width: 200 },
{ title: "操作人", dataIndex: "operatorName", width: 100, render: (val) => val || "-" },
]}
/>

View File

@ -393,7 +393,7 @@ const CustomerManage = (props) => {
</Row>
<Form.Item label="经营地址">
<Flex>
<Flex align="center" gap={10}>
<Form.Item name="businessAddress" noStyle>
<Input placeholder="请输入经营地址" disabled maxLength={200} />
</Form.Item>

View File

@ -30,6 +30,7 @@ import { tools } from "@cqsjjb/jjb-common-lib";
import dayjs from "dayjs";
import {EVAL_TYPE_OPTIONS} from '~/enumerate/constant';
import {QUALIFICATION_INDUSTRY_OPTIONS} from '~/enumerate/enterpriseOptions';
import StatCardGroup from "~/components/StatCardGroup";
import "./index.less";
@ -719,29 +720,35 @@ const EvalReportLibrary = (props) => {
];
const summaryItems = [
{ key: "totalCount", label: "报告总数", value: statData.totalCount },
{
key: "totalCount",
title: "报告总数",
value: statData.totalCount,
valueColor: "#1e293b",
},
{
key: "recentThreeYearCount",
label: "近三年报告",
title: "近三年报告",
value: statData.recentThreeYearCount,
valueColor: "#1e293b",
},
{
key: "submittedCount",
label: "已报送监管",
title: "已报送监管",
value: statData.submittedCount,
color: "#059669",
valueColor: "#059669",
},
{
key: "incompleteCount",
label: "待完善分类",
title: "待完善分类",
value: statData.incompleteCount,
color: "#d97706",
valueColor: "#d97706",
},
{
key: "spotcheckingCount",
label: "监管抽查中",
title: "监管抽查中",
value: statData.spotcheckingCount,
color: "#2563eb",
valueColor: "#2563eb",
},
];
@ -784,16 +791,7 @@ const EvalReportLibrary = (props) => {
上传过去三年已执行项目的正式报告按年度评价类型行业和企业分类管理并可报送监管端抽查
</div>
<div className="erl-summary">
{summaryItems.map((item) => (
<div key={item.key} className="erl-summary-item">
<span>{item.label}</span>
<strong style={item.color ? { color: item.color } : undefined}>
{item.value ?? "-"}
</strong>
</div>
))}
</div>
<StatCardGroup items={summaryItems} />
<SearchForm
form={searchForm}

View File

@ -15,39 +15,6 @@
line-height: 1.6;
}
/* 顶栏汇总 - 原型 .report-library-summary>div { border-radius: 6px } */
.erl-summary {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 11px;
margin-bottom: 16px;
}
.erl-summary-item {
padding: 13px;
background: #fff;
border: 1px solid #dce4ec;
border-radius: 6px;
}
.erl-summary-item span,
.erl-summary-item strong {
display: block;
}
.erl-summary-item span {
font-size: 14px;
color: #64748b;
}
.erl-summary-item strong {
font-size: 19px;
margin-top: 2px;
color: #0f172a;
font-weight: 600;
line-height: 1.2;
}
/* 报告名称文件图标 - 原型 .report-file-cell i { border-radius: 4px } */
.erl-file-cell {
display: flex;
@ -350,18 +317,7 @@
border-radius: 12px;
}
@media (max-width: 1200px) {
.erl-summary {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
/* 原型 media.report-library-summary { grid-template-columns: repeat(2,1fr) } */
.erl-summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.erl-upload-box-inner {
grid-template-columns: 54px 1fr;
}
@ -372,9 +328,3 @@
justify-self: start;
}
}
@media (max-width: 480px) {
.erl-summary {
grid-template-columns: 1fr;
}
}

View File

@ -18,6 +18,7 @@ import dayjs from "dayjs";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { router } = tools;
@ -193,23 +194,20 @@ const ArchiveContent = ({ readOnly, ...props }) => {
</Row>
</Form>
<Flex
justify="space-between"
align="center"
style={{ marginTop: 12, paddingBottom: 16 }}
>
{!readOnly && (
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button
type="primary"
onClick={handleArchive}
loading={props.safetyEvalBusiness?.processControlArchiveLoading}
disabled={!canArchive || archiveFlag}
>
{archiveFlag ? '已归档' : '双方签字并确认入库'}
</Button>
)}
</Flex>
</LayoutFooterPortal>
)}
</Card>
);
};

View File

@ -22,6 +22,7 @@ import { district } from "~/enumerate/constant";
import { phoneRule } from "~/utils/validators";
import { tools } from "@cqsjjb/jjb-common-lib";
import AttachmentUpload from "~/components/AttachmentUpload";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { router } = tools;
const { TextArea } = Input;
@ -530,8 +531,9 @@ const ContractContent = ({ readOnly, ...props }) => {
</Form>
{!readOnly && (
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button onClick={() => props.history.goBack()}>取消</Button>
<Button
type="primary"
onClick={handleSave}
@ -540,6 +542,7 @@ const ContractContent = ({ readOnly, ...props }) => {
完成合同录入并保存
</Button>
</Flex>
</LayoutFooterPortal>
)}
</Card>
</div>

View File

@ -14,6 +14,7 @@ import { CheckCircleFilled } from "@ant-design/icons";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { TextArea } = Input;
const { router } = tools;
@ -266,18 +267,6 @@ const ControlContent = ({ readOnly, ...props }) => {
},
]}
/>
<Flex justify="flex-end" gap={8} style={{ padding: "12px 0" }}>
{!readOnly && (
<Button
type="primary"
disabled={ ["dispatched", 'completed'].includes(processControl?.reviewerSignatureTaskStatus)}
onClick={() => handleConfirm("passed")}
loading={props.safetyEvalBusiness?.processControlConfirmLoading}
>
{reviewerSignatureTaskStatusMap[processControl?.reviewerSignatureTaskStatus] || '过程控制签字提交'}
</Button>
)}
</Flex>
</Card>
)}
@ -373,6 +362,21 @@ const ControlContent = ({ readOnly, ...props }) => {
</div>
</Card>
)}
{step === 1 && !readOnly && (
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button
type="primary"
disabled={ ["dispatched", 'completed'].includes(processControl?.reviewerSignatureTaskStatus)}
onClick={() => handleConfirm("passed")}
loading={props.safetyEvalBusiness?.processControlConfirmLoading}
>
{reviewerSignatureTaskStatusMap[processControl?.reviewerSignatureTaskStatus] || '过程控制签字提交'}
</Button>
</Flex>
</LayoutFooterPortal>
)}
</div>
);
};

View File

@ -17,6 +17,7 @@ import {
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { TextArea } = Input;
const { router } = tools;
@ -317,7 +318,8 @@ const InternalReviewContent = ({ readOnly, ...props }) => {
]}
/>
{!readOnly && (
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button
loading={props.safetyEvalBusiness?.internalReviewConfirmLoading}
onClick={() => handleConfirm("not_passed")}
@ -332,6 +334,7 @@ const InternalReviewContent = ({ readOnly, ...props }) => {
确认修改并提交技审
</Button>
</Flex>
</LayoutFooterPortal>
)}
</div>
);

View File

@ -23,6 +23,7 @@ import {EVAL_TYPE_OPTIONS} from '~/enumerate/constant';
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
import { tools } from "@cqsjjb/jjb-common-lib";
import AttachmentUpload from "~/components/AttachmentUpload";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { router } = tools;
const { TextArea } = Input;
@ -543,7 +544,8 @@ const RiskAnalysisContent = ({ readOnly, ...props }) => {
</Modal>
{!readOnly && (
<Flex justify="flex-end" gap={8} style={{marginTop: '8px'}}>
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button
onClick={() => handleSave(1)}
loading={riskAnalysisSaveLoading}
@ -559,6 +561,7 @@ const RiskAnalysisContent = ({ readOnly, ...props }) => {
完成分析并保存
</Button>
</Flex>
</LayoutFooterPortal>
)}
</div>
</Spin>

View File

@ -6,6 +6,7 @@ import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import AttachmentUpload from "~/components/AttachmentUpload";
import {ROLE_DEFINITIONS_MAP} from '~/enumerate/constant'
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
import "./index.less";
import dayjs from "dayjs";
@ -353,7 +354,8 @@ const SurveyContent = ({ readOnly, ...props }) => {
</div>
</Form>
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
{!isFirst && <Button onClick={goPrev} disabled={evalSurveySaveAttachLoading}>上一步</Button>}
{isLast ? (
!readOnly && <Button type="primary" onClick={handleSave} loading={evalSurveySaveAttachLoading}>保存</Button>
@ -361,6 +363,7 @@ const SurveyContent = ({ readOnly, ...props }) => {
<Button type="primary" onClick={goNext} disabled={evalSurveySaveAttachLoading}>下一步</Button>
)}
</Flex>
</LayoutFooterPortal>
</div>
);
};

View File

@ -15,6 +15,7 @@ import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import { ROLE_DEFINITIONS } from "~/enumerate/constant";
import StatCardGroup from "~/components/StatCardGroup";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { router } = tools;
@ -342,8 +343,9 @@ const TeamContent = ({ readOnly, ...props }) => {
</div>
</details>
<Flex justify="flex-end" gap={8} className="pf-bottom-actions">
{!readOnly && (
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button
type="primary"
loading={memberSaveLoading}
@ -351,8 +353,9 @@ const TeamContent = ({ readOnly, ...props }) => {
>
确认项目组
</Button>
)}
</Flex>
</LayoutFooterPortal>
)}
<Modal
title="添加项目人员与角色"

View File

@ -17,6 +17,7 @@ import {
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib";
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
const { TextArea } = Input;
const { router } = tools;
@ -316,7 +317,8 @@ const TechnicalReviewContent = ({ readOnly, ...props }) => {
]}
/>
{!readOnly && (
<Flex justify="flex-end" gap={8} style={{ marginTop: 16 }}>
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button
loading={props.safetyEvalBusiness?.techReviewConfirmLoading}
onClick={() => handleConfirm("not_passed")}
@ -331,6 +333,7 @@ const TechnicalReviewContent = ({ readOnly, ...props }) => {
确认技审并提交过程控制
</Button>
</Flex>
</LayoutFooterPortal>
)}
</div>
);

View File

@ -247,6 +247,7 @@ const ProjectFlow = (props) => {
<PageLayout
history={props.history}
previous
footer={<div id="pf-footer-slot" />}
title={
<div>
<span>项目工作台</span>

View File

@ -3,6 +3,11 @@
padding-top: 0px;
}
}
// footer 插槽为空时隐藏 PageLayout 底栏
[class*="-lay-container-bottom"]:has(> #pf-footer-slot:empty) {
display: none;
}
.pf {
// 区块头部
@ -207,11 +212,6 @@
line-height: 1.55;
}
// 项目组 - 底部操作行
&-bottom-actions {
margin-top: 8px;
}
// 弹窗 - 提示文字
&-modal-hint {
margin-bottom: 8px;

View File

@ -46,7 +46,11 @@ export default class Container extends React.Component {
theme={{
token: this.token,
algorithm: this.algorithm,
components: {
Tag: {
fontSizeSM: 13,
},
},
}}
locale={language}
prefixCls={window.process.env.app.antd["ant-prefix"]}

View File

@ -2,6 +2,7 @@
color: rgba(0, 0, 0, 0.45);
font-size: 14px;
line-height: 22px;
font-weight: 400;
}
.micro-temp-page-header-heading-left{