tangjie 2026-08-11 18:12:22 +08:00
commit d2ce6e6b52
5 changed files with 321 additions and 67 deletions

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ yarn.lock
/.playwright-cli/ /.playwright-cli/
/.vscode/ /.vscode/
/.workbuddy/ /.workbuddy/
/.pnpm-store/

View File

@ -14,8 +14,8 @@ module.exports = {
// API_HOST: "http://192.168.0.134", // API_HOST: "http://192.168.0.134",
// API_HOST: "http://192.168.0.150", //太浅 // API_HOST: "http://192.168.0.150", //太浅
API_HOST: "https://gbs-gateway.qhdsafety.com", // API_HOST: "https://gbs-gateway.qhdsafety.com",
//API_HOST: "http://192.168.0.103", //huwei API_HOST: "http://192.168.0.103", //huwei
}, },
production: { production: {
// 应用后端分支名称,部署上线需要 // 应用后端分支名称,部署上线需要

View File

@ -317,6 +317,23 @@ export const evalReportSubmit = declareRequest(
"Post > @/safetyEval/institution/eval-report/submit", "Post > @/safetyEval/institution/eval-report/submit",
); );
/**
* 机构端报告库-批量报送监管端
*
* 变更时间2026-08-11
* 变更原因报告库需支持一次性把多份未报送报告报送至监管端原仅有单条 submit 接口
* 逐条点击效率低且无法给出统一的批量结果反馈
* 接口契约需后端按此新增不改动既有 submit 逻辑
* POST /safetyEval/institution/eval-report/submit/batch
* 入参{ ids: Long[] } 待报送的报告主键集合
* 出参SingleResponse<Boolean> 或含成功/失败明细的结构前端按 success 判定
* 兼容说明后端接口未上线前页面侧会自动降级为循环调用单条 submit保证功能可用
*/
export const evalReportBatchSubmit = declareRequest(
"evalReportBatchSubmitLoading",
"Post > @/safetyEval/institution/eval-report/submit/batch",
);
export const evalReportSpotcheckList = declareRequest( export const evalReportSpotcheckList = declareRequest(
"evalReportSpotcheckListLoading", "evalReportSpotcheckListLoading",
"Get > /safetyEval/institution/eval-report-spotcheck/list", "Get > /safetyEval/institution/eval-report-spotcheck/list",

View File

@ -26,6 +26,7 @@ import {
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm"; import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper"; import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import PreviewUrlButton from "~/components/PreviewUrlButton";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd"; import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { Connect } from "@cqsjjb/jjb-dva-runtime";
@ -33,6 +34,15 @@ import { tools } from "@cqsjjb/jjb-common-lib";
import dayjs from "dayjs"; import dayjs from "dayjs";
import "./index.less"; import "./index.less";
/**
* 变更时间2026-08-11
* 变更原因需求机构端报告库批量报送 + 查看详情样式优化
* 1) 列表支持勾选未报送报告并一次性批量报送调用 evalReportBatchSubmit接口未就绪时降级逐条 submit
* 2) 查看详情改为与上传历史报告表单一致的排版更贴近新增报告的录入样式附件支持图片放大预览
* 其他文件点击新窗口预览/下载
* 相关约束后端代码不修改仅作为接口/字段对照依据批量报送接口需后端按约定契约新增
*/
const { router } = tools; const { router } = tools;
const { TextArea } = Input; const { TextArea } = Input;
@ -159,6 +169,9 @@ const EvalReportLibrary = (props) => {
const [uploadedFile, setUploadedFile] = useState(null); const [uploadedFile, setUploadedFile] = useState(null);
const [contractFiles, setContractFiles] = useState([]); const [contractFiles, setContractFiles] = useState([]);
const [processDocFiles, setProcessDocFiles] = useState([]); const [processDocFiles, setProcessDocFiles] = useState([]);
// 变更时间2026-08-11 变更原因:批量报送需记录勾选的报告主键
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [batchSubmitting, setBatchSubmitting] = useState(false);
const { const {
evalReportPageLoading, evalReportPageLoading,
@ -407,6 +420,86 @@ const EvalReportLibrary = (props) => {
}); });
}; };
/**
* 批量报送变更时间2026-08-11
* 变更原因机构端报告库需支持一次性把多份未报送报告报送至监管端原仅支持单条报送
* 实现策略
* 1) 调用批量报送接口 evalReportBatchSubmitPOST /safetyEval/institution/eval-report/submit/batch入参 { ids: Long[] }
* 后端已实现逐条复用单条报送规则并返回 successCount/failCount/failList 明细
* 2) 若批量接口异常如未部署/网络错误降级为并发调用单条 submit保证功能可用
* 并对失败项汇总提示避免个别失败导致整体中断
*/
const handleBatchSubmit = async () => {
const ids = selectedRowKeys;
if (!ids || ids.length === 0) {
message.warning("请先勾选需要报送的报告");
return;
}
Modal.confirm({
title: "确认批量报送",
icon: <SendOutlined style={{ color: "#1677ff" }} />,
content: (
<div style={{ fontSize: 14, color: "#333", lineHeight: 1.8, padding: "8px 0" }}>
确定要将已勾选的 <strong>{ids.length}</strong>
</div>
),
okText: "确认批量报送",
cancelText: "取消",
onOk: async () => {
setBatchSubmitting(true);
try {
const res = await props.evalReportBatchSubmit({ ids });
if (res?.success !== false && res?.data) {
// 后端已返回成功/失败明细,精确展示
const { successCount = 0, failCount = 0, failList = [] } = res.data;
if (failCount === 0) {
message.success(`已批量报送 ${successCount} 份报告至监管端`);
} else {
const reasons = failList
.map((f) => `报告ID ${f.id}${f.reason}`)
.join("");
message.warning(
`批量报送完成:${successCount} 份成功,${failCount} 份失败(${reasons}`,
);
}
} else if (res?.success !== false) {
message.success(`已批量报送 ${ids.length} 份报告至监管端`);
} else {
throw new Error(res?.message || "批量报送失败");
}
} catch (err) {
// 批量接口未就绪(如 404/网络异常)时降级为逐条报送,保证功能可用
message.info("批量接口暂未就绪,正在逐条报送...");
let successCount = 0;
const failNames = [];
await Promise.all(
ids.map(async (id) => {
try {
const r = await props.evalReportSubmit({ id });
if (r?.success !== false) successCount += 1;
else failNames.push(id);
} catch {
failNames.push(id);
}
}),
);
if (failNames.length === 0) {
message.success(`已逐条报送 ${successCount} 份报告至监管端`);
} else {
message.warning(
`逐条报送完成:${successCount} 份成功,${failNames.length} 份失败(请重试或检查报送状态)`,
);
}
} finally {
setBatchSubmitting(false);
setSelectedRowKeys([]);
fetchSummary();
getData();
}
},
});
};
const handleViewSpotcheck = async (record) => { const handleViewSpotcheck = async (record) => {
setCurrentReport(record); setCurrentReport(record);
setSpotcheckVisible(true); setSpotcheckVisible(true);
@ -660,9 +753,26 @@ const EvalReportLibrary = (props) => {
<PageLayout <PageLayout
title="报告库管理" title="报告库管理"
extra={ extra={
/**
* 变更时间2026-08-11
* 变更原因新增批量报送入口上传历史报告并列仅在已勾选未报送报告时可点击
* 按钮上直接反馈已选数量降低误操作
*/
<Space>
<Button
type="primary"
ghost
icon={<SendOutlined />}
disabled={selectedRowKeys.length === 0}
loading={batchSubmitting}
onClick={handleBatchSubmit}
>
批量报送{selectedRowKeys.length > 0 ? `${selectedRowKeys.length}` : ""}
</Button>
<Button type="primary" icon={<PlusOutlined />} onClick={handleOpenAdd}> <Button type="primary" icon={<PlusOutlined />} onClick={handleOpenAdd}>
上传历史报告 上传历史报告
</Button> </Button>
</Space>
} }
> >
<div className="erl-page"> <div className="erl-page">
@ -735,6 +845,18 @@ const EvalReportLibrary = (props) => {
columns={columns} columns={columns}
dataSource={dataSource} dataSource={dataSource}
loading={evalReportPageLoading} loading={evalReportPageLoading}
/**
* 变更时间2026-08-11
* 变更原因支持批量报送需勾选功能未报送记录可勾选
* 已报送/抽查相关记录禁用勾选避免重复报送
*/
rowSelection={{
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
getCheckboxProps: (record) => ({
disabled: !isUnsubmitted(record),
}),
}}
scroll={{ y: props.scrollY, x: 1400 }} scroll={{ y: props.scrollY, x: 1400 }}
pagination={{ pagination={{
total, total,
@ -970,77 +1092,138 @@ const EvalReportLibrary = (props) => {
</div> </div>
</Modal> </Modal>
{/* 报告详情 */} {/* 2026-08-11
* 变更原因原型要求查看沿用新增报告的录入排版字段顺序分组与上传弹窗对齐降低阅读认知成本
* 附件主报告/合同/过程文档使用 PreviewUrlButton 图片点击放大预览其他文件点击新窗口预览/下载
*/}
<Modal <Modal
title="报告详情" title={
<div className="erl-modal-title">
<div>报告详情</div>
<div className="erl-modal-subtitle">报告库档案信息与上传录入样式一致</div>
</div>
}
open={detailVisible} open={detailVisible}
onCancel={() => setDetailVisible(false)} onCancel={() => setDetailVisible(false)}
footer={<Button onClick={() => setDetailVisible(false)}>关闭</Button>} footer={<Button onClick={() => setDetailVisible(false)}>关闭</Button>}
width={760} width={820}
destroyOnClose destroyOnClose
className="erl-upload-modal"
> >
{detailData ? ( {detailData ? (
<> <>
<Descriptions bordered column={{ xs: 1, sm: 1, md: 2 }} size="small"> {/* 正式报告文件:预览优先,与上传弹窗主文件展示一致 */}
<Descriptions.Item label="报告编号"> <div className="erl-upload-box erl-upload-box-view">
<span className="erl-report-no">{detailData.reportNo || "-"}</span> <div className="erl-upload-box-inner">
</Descriptions.Item> <div className="erl-upload-icon">{getFileExt(detailData.fileName || detailData.fileUrl)}</div>
<Descriptions.Item label="报告名称"> <div className="erl-upload-text">
{detailData.reportName || "-"} <strong>{detailData.reportName || "未命名报告"}</strong>
</Descriptions.Item> <p>{detailData.fileName || detailData.fileUrl || "暂无正式报告文件"}</p>
<Descriptions.Item label="报告年度"> {detailData.fileUrl ? (
{detailData.reportYear || "-"} <PreviewUrlButton
</Descriptions.Item> url={detailData.fileUrl}
<Descriptions.Item label="评价类型"> children={detailData.fileName || "预览报告"}
{detailData.evalTypeName || />
getOptionLabel(REPORT_EVAL_TYPE_OPTIONS, detailData.evalTypeCode)} ) : (
</Descriptions.Item> <span>尚未上传正式报告文件</span>
<Descriptions.Item label="所属行业"> )}
{detailData.industryName ||
getOptionLabel(REPORT_INDUSTRY_OPTIONS, detailData.industryCode)}
</Descriptions.Item>
<Descriptions.Item label="被评价单位">
{detailData.evaluatedUnitName || "-"}
</Descriptions.Item>
<Descriptions.Item label="签发日期">
{detailData.issueDate
? dayjs(detailData.issueDate).format("YYYY-MM-DD")
: "-"}
</Descriptions.Item>
<Descriptions.Item label="报告页数">
{detailData.pageCount ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="报送状态">
{renderDisplayStatus(detailData)}
</Descriptions.Item>
<Descriptions.Item label="报告状态">
{detailData.reportStatusName || "-"}
</Descriptions.Item>
<Descriptions.Item label="保密属性">
{detailData.secretLevelName ||
getOptionLabel(SECRET_LEVEL_OPTIONS, detailData.secretLevelCode)}
</Descriptions.Item>
<Descriptions.Item label="归档时间">
{detailData.archiveTime
? dayjs(detailData.archiveTime).format("YYYY-MM-DD HH:mm")
: "-"}
</Descriptions.Item>
<Descriptions.Item label="文件名称" span={2}>
<div className="erl-file-cell">
<i>{getFileExt(detailData.fileName || detailData.fileUrl)}</i>
<span>{detailData.fileName || "-"}</span>
</div> </div>
</Descriptions.Item> </div>
<Descriptions.Item label="合同文件" span={2}> </div>
{renderFileLinks(detailData.contractFileUrls)}
</Descriptions.Item> <Row gutter={16} className="erl-detail-form">
<Descriptions.Item label="过程文档" span={2}> <Col span={12}>
{renderFileLinks(detailData.processDocUrls)} <div className="erl-detail-item">
</Descriptions.Item> <label>报告编号</label>
<Descriptions.Item label="备注" span={2}> <span className="erl-report-no">{detailData.reportNo || "-"}</span>
{detailData.remarks || "-"} </div>
</Descriptions.Item> </Col>
</Descriptions> <Col span={12}>
<div className="erl-detail-item">
<label>报告名称</label>
<span>{detailData.reportName || "-"}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>报告年度</label>
<span>{detailData.reportYear || "-"}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>评价类型</label>
<span>{detailData.evalTypeName || getOptionLabel(REPORT_EVAL_TYPE_OPTIONS, detailData.evalTypeCode)}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>所属行业</label>
<span>{detailData.industryName || getOptionLabel(REPORT_INDUSTRY_OPTIONS, detailData.industryCode)}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>被评价单位</label>
<span>{detailData.evaluatedUnitName || "-"}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>报告签发日期</label>
<span>{detailData.issueDate ? dayjs(detailData.issueDate).format("YYYY-MM-DD") : "-"}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>报告页数</label>
<span>{detailData.pageCount ?? "-"}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>报送状态</label>
<span>{renderDisplayStatus(detailData)}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>报告状态</label>
<span>{detailData.reportStatusName || "-"}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>保密属性</label>
<span>{detailData.secretLevelName || getOptionLabel(SECRET_LEVEL_OPTIONS, detailData.secretLevelCode)}</span>
</div>
</Col>
<Col span={12}>
<div className="erl-detail-item">
<label>归档时间</label>
<span>{detailData.archiveTime ? dayjs(detailData.archiveTime).format("YYYY-MM-DD HH:mm") : "-"}</span>
</div>
</Col>
<Col span={24}>
<div className="erl-detail-item">
<label>合同文件</label>
<span>{renderFileLinks(detailData.contractFileUrls)}</span>
</div>
</Col>
<Col span={24}>
<div className="erl-detail-item">
<label>其他过程文档</label>
<span>{renderFileLinks(detailData.processDocUrls)}</span>
</div>
</Col>
<Col span={24}>
<div className="erl-detail-item">
<label>备注</label>
<span>{detailData.remarks || "-"}</span>
</div>
</Col>
</Row>
<div className="erl-detail-actions"> <div className="erl-detail-actions">
{isSpotcheckRelated(detailData) && ( {isSpotcheckRelated(detailData) && (
<Button <Button

View File

@ -276,6 +276,59 @@
margin-top: 16px; margin-top: 16px;
} }
/* 变更时间2026-08-11 查看详情改为与「上传历史报告」一致的排版样式 */
.erl-detail-form {
margin-top: 16px;
}
.erl-detail-item {
display: flex;
align-items: baseline;
padding: 8px 10px;
background: #f8fafc;
border: 1px solid #eef2f7;
border-radius: 8px;
min-height: 40px;
label {
flex: 0 0 84px;
color: #64748b;
font-size: 13px;
}
span {
flex: 1;
color: #1f2937;
font-size: 14px;
word-break: break-all;
}
}
/* 只读查看态的正式报告文件框:弱化边框,去掉上传交互 */
.erl-upload-box-view {
border-style: solid;
border-color: #e2e8f0;
background: #f8fafc;
}
.erl-detail-attachments {
margin-top: 16px;
.erl-detail-attachments-block {
margin-bottom: 12px;
}
.erl-detail-attachments-title {
font-size: 13px;
color: #64748b;
margin-bottom: 6px;
}
.ant-btn-link {
padding-left: 0;
}
}
.erl-loading { .erl-loading {
text-align: center; text-align: center;
padding: 40px 0; padding: 40px 0;