Merge remote-tracking branch 'origin/dev' into dev

v0.0.1-final
huwei 2026-06-30 16:08:22 +08:00
commit ecd3573a78
18 changed files with 1972 additions and 3 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
/node_modules
# production
/prototype
/dist
/demo
/.trae/

View File

@ -2,4 +2,51 @@
* 全局常量定义
*/
export {};
/** 证书状态 */
export const CERT_STATUS_MAP = {
normal: { label: "正常", color: "success" },
expiring: { label: "即将到期", color: "warning" },
expired: { label: "已过期", color: "error" },
};
/** 核验状态 */
export const VERIFY_STATUS_MAP = {
pending: { label: "待确认", color: "warning" },
passed: { label: "确认通过", color: "success" },
rejected: { label: "打回", color: "error" },
};
/** 审核状态 */
export const REVIEW_STATUS_MAP = {
pending: { label: "待审核", color: "warning" },
reviewing: { label: "审核中", color: "processing" },
rejected: { label: "打回", color: "error" },
};
/** 专家核验状态 */
export const EXPERT_STATUS_MAP = {
pending: { label: "待核验", color: "blue" },
arranging: { label: "待安排", color: "warning" },
passed: { label: "已核验", color: "success" },
};
/** 公示状态 */
export const PUBLICITY_STATUS_MAP = {
pending: { label: "待公示", color: "warning" },
published: { label: "已公示", color: "success" },
};
/** 变更状态 */
export const CHANGE_STATUS_MAP = {
stuck: { label: "停滞中", color: "error" },
pending: { label: "待审核", color: "warning" },
approved: { label: "已审核", color: "success" },
};
/** 专家等级 */
export const EXPERT_LEVEL_MAP = {
1: "一级",
2: "二级",
3: "三级",
};

View File

@ -6,8 +6,7 @@ import { getFileUrlFromServer } from "zy-react-library/utils";
import "dayjs/locale/zh-cn";
import "../blessed_by_buddha";
require("antd/dist/reset.css");
require("zy-react-library/css/common.less");
dayjs.locale("zh-cn");
setJJBCommonAntdMessage(message);

View File

@ -0,0 +1,213 @@
import React, { useState, useEffect } from "react";
import { Button, Form, Select, Space, 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 { VERIFY_STATUS_MAP } from "~/enumerate/constant";
// 静态模拟数据
const MOCK_DATA = [
{
id: 1,
orgName: "重庆安评技术研究院有限公司",
orgType: "本地机构",
certNo: "API-2026-001",
businessScope: "化工, 石油加工",
confirmStatus: "pending",
},
{
id: 2,
orgName: "北京中安评价中心(重庆分公司)",
orgType: "异地机构",
certNo: "API-2026-015",
businessScope: "矿山",
confirmStatus: "passed",
},
{
id: 3,
orgName: "重庆华安评价有限公司",
orgType: "异地机构",
certNo: "API-2025-088",
businessScope: "石油加工",
confirmStatus: "pending",
},
{
id: 4,
orgName: "重庆安评科技有限公司",
orgType: "本地机构",
certNo: "API-2024-056",
businessScope: "化工",
confirmStatus: "rejected",
},
{
id: 5,
orgName: "重庆恒安安全评价有限公司",
orgType: "本地机构",
certNo: "API-2025-032",
businessScope: "矿山, 石油加工",
confirmStatus: "passed",
},
];
const {router} = tools;
const ExpertVerification = (props) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleSearch = () => {
setLoading(true);
console.log(router.query);
setTimeout(() => setLoading(false), 500);
};
const handleReset = (values) => {
setLoading(true);
router.query={
...router.query,
...values,
current: 1,
pageSize: 10,
}
handleSearch();
};
useEffect(() => {
form.setFieldsValue(router.query);
}, []);
const columns = [
{
title: "序号",
dataIndex: "id",
width: 60,
fixed: "left",
render: (text, record, index) => index + 1,
},
{ title: "机构名称", dataIndex: "orgName", width: 220, ellipsis: true },
{ title: "机构类型", dataIndex: "orgType", width: 100 },
{ title: "证书编号", dataIndex: "certNo", width: 140, ellipsis: true },
{ title: "安全评价业务范围", dataIndex: "businessScope", width: 180, ellipsis: true },
{
title: "确认状态",
dataIndex: "confirmStatus",
width: 100,
render: (status) => {
const config = VERIFY_STATUS_MAP[status];
return config ? <Tag color={config.color}>{config.label}</Tag> : status;
},
},
{
title: "操作",
width: 140,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" size="small" onClick={() => props.history.push(`FilingDetail?id=${record.id}`)}>
查看
</Button>
{record.confirmStatus === "pending" && (
<Button type="link" size="small" onClick={() => props.history.push(`QualReviewForm?id=${record.id}`)}>
核验
</Button>
)}
</Space>
),
},
];
return (
<PageLayout title="专家资料核验">
<SearchForm
style={{ marginBottom: 24 }}
form={form}
loading={loading}
formLine={[
<Form.Item key="orgName" name="orgName">
<ControlWrapper.Input label="机构名称" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="orgType" name="orgType">
<ControlWrapper.Select
label="机构类型"
placeholder="请输入"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="本地机构">本地机构</Select.Option>
<Select.Option value="异地机构">异地机构</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item
key="businessScope"
name="businessScope"
>
<ControlWrapper.Select
label="安全评价业务范围"
placeholder="请输入"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="石油加工">石油加工</Select.Option>
<Select.Option value="化工">化工</Select.Option>
<Select.Option value="矿山">矿山</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="confirmStatus" name="confirmStatus">
<ControlWrapper.Select
label="确认状态"
placeholder="请输入"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="pending">待确认</Select.Option>
<Select.Option value="passed">确认通过</Select.Option>
<Select.Option value="rejected">打回</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
]}
onReset={handleReset}
onFinish={(values) => {
router.query={
...router.query,
...values,
current: 1,
pageSize: 10,
}
handleSearch();
}}
/>
<Table
rowKey="id"
columns={columns}
dataSource={MOCK_DATA}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: MOCK_DATA.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: router.query.current,
pageSize: router.query.pageSize,
onChange: (page, pageSize) => {
router.query={
...router.query,
current: page,
pageSize,
};
handleSearch();
},
}}
/>
</PageLayout>
);
};
export default AntdTableFuncControl(ExpertVerification);

View File

@ -0,0 +1,34 @@
import React, { useMemo } from "react";
import { Tag, Space } from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import { REVIEW_STATUS_MAP } from "~/enumerate/constant";
import { MOCK_DETAIL } from "../mockData";
import FilingTabs from "../FilingTabs";
function parseQueryId(location) {
const search = location?.search || window.location.search || "";
return new URLSearchParams(search).get("id");
}
const FilingDetail = (props) => {
const id = useMemo(() => parseQueryId(props.location), [props.location?.search]);
const detail = MOCK_DETAIL;
const statusConfig = REVIEW_STATUS_MAP[detail.reviewStatus];
return (
<PageLayout
title={
<Space>
<span>备案申请详情</span>
{statusConfig && <Tag color={statusConfig.color}>{statusConfig.label}</Tag>}
</Space>
}
history={props.history}
previous
>
<FilingTabs detail={detail} />
</PageLayout>
);
};
export default FilingDetail;

View File

@ -0,0 +1,255 @@
import React, { useState } from "react";
import { Button, Descriptions, Table, Tag, Tabs, Select, Modal, Space } from "antd";
import {
MOCK_MATERIALS,
MOCK_PERSONNEL,
MOCK_EQUIPMENT,
MOCK_EQUIP_DETAIL,
MOCK_PERSONNEL_DETAIL,
MOCK_CERTIFICATES,
CALIBRATION_MAP,
} from "../mockData";
/**
* 共享组件资质备案 5 个标签页详情 / 审核共用
* @param {Object} props
* @param {Object} props.detail - 备案详情数据
* @param {boolean} [props.isReview=false] - 审核模式开启后材料上传显示"上传附件"+"符合性"
* @param {Object} [props.compliance={}] - 审核模式下的符合性状态
* @param {Function} [props.onComplianceChange] - 符合性变动回调
* @param {Function} [props.onPersonnelView] - 查看人员详情回调覆盖默认弹窗
* @param {Function} [props.onEquipView] - 查看设备详情回调 (record) => void覆盖默认弹窗
*/
const FilingTabs = ({
detail,
isReview = false,
compliance = {},
onComplianceChange,
onPersonnelView,
onEquipView,
}) => {
const [personnelVisible, setPersonnelVisible] = useState(false);
const [equipVisible, setEquipVisible] = useState(false);
const [selectedEquip, setSelectedEquip] = useState(null);
const baseCols = [
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "材料名称", dataIndex: "name" },
];
const materialCols = isReview
? [
...baseCols,
{ title: "上传附件", dataIndex: "attachment", width: 120 },
{ title: "格式", dataIndex: "format", width: 80 },
{ title: "状态", dataIndex: "status", width: 100, render: () => <Tag color="success">已上传</Tag> },
{ title: "符合性", width: 100,
render: (_, record) => (
<Select
style={{ width: "100%", fontSize: "0.75rem" }}
size="small"
placeholder="请选择"
value={compliance[record.id]}
onChange={(v) => onComplianceChange?.(record.id, v)}
>
<Select.Option value="pass">符合</Select.Option>
<Select.Option value="fail">不符合</Select.Option>
</Select>
),
},
]
: [
...baseCols,
{ title: "格式", dataIndex: "format", width: 80 },
{ title: "状态", dataIndex: "status", width: 100, render: () => <Tag color="success">已上传</Tag> },
{ title: "操作", width: 80, render: () => <Button type="link" size="small">预览</Button> },
];
const items = [
{
key: "info",
label: "1.备案基本信息",
children: (
<Descriptions column={2} bordered size="small" style={{ background: "#fff" }}>
<Descriptions.Item label="申请的业务范围" span={2}>{detail.scopeName}</Descriptions.Item>
<Descriptions.Item label="备案属地">{detail.filingRegion}</Descriptions.Item>
<Descriptions.Item label="备案单位">{detail.orgName}</Descriptions.Item>
<Descriptions.Item label="备案单位类型">{detail.orgType}</Descriptions.Item>
<Descriptions.Item label="统一社会信用代码">{detail.creditCode}</Descriptions.Item>
<Descriptions.Item label="办公地址" span={2}>{detail.officeAddress}</Descriptions.Item>
<Descriptions.Item label="注册地址" span={2}>{detail.registerAddress}</Descriptions.Item>
<Descriptions.Item label="资质证书编号">{detail.certNo}</Descriptions.Item>
<Descriptions.Item label="信息公开网址">{detail.website}</Descriptions.Item>
<Descriptions.Item label="法人代表及电话">{detail.legalPerson} / {detail.legalPhone}</Descriptions.Item>
<Descriptions.Item label="联系人及电话">{detail.contactPerson} / {detail.contactPhone}</Descriptions.Item>
<Descriptions.Item label="固定资产总值(万元)">{detail.fixedAssets}</Descriptions.Item>
<Descriptions.Item label="工作场所建筑面积">{detail.officeArea}</Descriptions.Item>
<Descriptions.Item label="档案室面积">{detail.archiveArea}</Descriptions.Item>
<Descriptions.Item label="专职安全评价师数量">{detail.evaluatorCount}</Descriptions.Item>
<Descriptions.Item label="注册安全工程师数量">{detail.registerEngineerCount}</Descriptions.Item>
<Descriptions.Item label="单位基本情况介绍" span={2}>{detail.description}</Descriptions.Item>
</Descriptions>
),
},
{
key: "materials",
label: "2.备案材料上传",
children: (
<Table size="small" bordered rowKey="id" pagination={false} dataSource={MOCK_MATERIALS} columns={materialCols} />
),
},
{
key: "promise",
label: "3.法定代表人承诺书",
children: (
<div style={{ border: "1px solid #d9d9d9", borderRadius: 6, padding: "1.25rem 1.5rem", background: "#fafafa", fontSize: "0.85rem", lineHeight: 1.9 }}>
<p style={{ textAlign: "center", fontWeight: 600, fontSize: "0.95rem", marginBottom: "0.75rem" }}>申请单位法定代表人承诺书</p>
<p>本人{detail.legalPerson}{detail.orgName}法定代表人现代表我单位承诺如下</p>
<p>我单位自愿申请安全评价机构资质本人已经认真学习了解并掌握安全生产法行政许可法行政处罚法安全评价检测检验机构管理办法等法律法规的相关规定知悉开展安全评价工作的法律责任义务权力和风险</p>
<p>本人承诺本单位满足安全评价检测检验机构管理办法所规定的资质条件要求本人及单位三年内无重大违法失信行为申请资质所提交的有关材料真实合法有效并对其真实性合法性承担相应法律责任接受并配合有关部门对本单位开展的专业能力审查</p>
<p>如能获准资质本单位将严格按照法律法规规章标准的要求开展安全评价活动遵守执业准则和职业道德并对作出的安全评价报告结果承担法律责任自觉接受应急管理部门煤矿安全监督管理部门等行政执法部门的监督检查</p>
<p style={{ marginTop: "0.75rem" }}>法定代表人{detail.legalPerson}已签名</p>
<p>单位盖章</p>
<p style={{ textAlign: "right" }}>2026年06月25日</p>
</div>
),
},
{
key: "personnel",
label: "4.备案人员管理",
children: (
<Table size="small" bordered rowKey="id" pagination={false} dataSource={MOCK_PERSONNEL}
columns={[
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "人员姓名", dataIndex: "name", width: 100 },
{ title: "类型", dataIndex: "type", width: 100 },
{ title: "职务", dataIndex: "position", width: 120 },
{ title: "职称", dataIndex: "title", width: 100 },
{ title: "专业能力", dataIndex: "ability" },
{ title: "操作", width: 80,
render: () => (
<Button type="link" size="small" onClick={onPersonnelView || (() => setPersonnelVisible(true))}>
查看
</Button>
),
},
]}
/>
),
},
{
key: "equipment",
label: "5.装备清单",
children: (
<Table size="small" bordered rowKey="id" pagination={false} dataSource={MOCK_EQUIPMENT}
columns={[
{ title: "序号", width: 60, render: (_, __, i) => i + 1 },
{ title: "装备名称", dataIndex: "name", width: 140 },
{ title: "规格型号", dataIndex: "model", width: 120 },
{ title: "生产厂家", dataIndex: "manufacturer", width: 140 },
{ title: "计量检定情况", dataIndex: "calibration", width: 120,
render: (s) => {
const cfg = CALIBRATION_MAP[s];
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : s;
},
},
{ title: "操作", width: 80,
render: (_, record) => (
<Button type="link" size="small" onClick={() => onEquipView ? onEquipView(record) : (setSelectedEquip(record), setEquipVisible(true))}>
查看
</Button>
),
},
]}
/>
),
},
];
return (
<>
<Tabs items={items} />
{/* 人员详细信息弹窗 */}
<Modal
title="人员详细信息"
open={personnelVisible}
onCancel={() => setPersonnelVisible(false)}
width={720}
footer={(originNode, { CancelBtn }) => <CancelBtn />}
>
<Descriptions column={2} bordered size="small">
<Descriptions.Item label="姓名">{MOCK_PERSONNEL_DETAIL.name}</Descriptions.Item>
<Descriptions.Item label="性别">{MOCK_PERSONNEL_DETAIL.gender}</Descriptions.Item>
<Descriptions.Item label="资质范围">{MOCK_PERSONNEL_DETAIL.qualScope}</Descriptions.Item>
<Descriptions.Item label="出生日期">{MOCK_PERSONNEL_DETAIL.birthDate}</Descriptions.Item>
<Descriptions.Item label="办公地址" span={2}>{MOCK_PERSONNEL_DETAIL.officeAddress}</Descriptions.Item>
<Descriptions.Item label="专业">{MOCK_PERSONNEL_DETAIL.major}</Descriptions.Item>
<Descriptions.Item label="职业等级及证书编号">{MOCK_PERSONNEL_DETAIL.certLevel}</Descriptions.Item>
<Descriptions.Item label="邮箱">{MOCK_PERSONNEL_DETAIL.email}</Descriptions.Item>
<Descriptions.Item label="人员类型">{MOCK_PERSONNEL_DETAIL.personnelType}</Descriptions.Item>
<Descriptions.Item label="身份证号">{MOCK_PERSONNEL_DETAIL.idCard}</Descriptions.Item>
<Descriptions.Item label="现住地址" span={2}>{MOCK_PERSONNEL_DETAIL.currentAddress}</Descriptions.Item>
<Descriptions.Item label="学历">{MOCK_PERSONNEL_DETAIL.education}</Descriptions.Item>
<Descriptions.Item label="毕业院校">{MOCK_PERSONNEL_DETAIL.graduateSchool}</Descriptions.Item>
<Descriptions.Item label="职务">{MOCK_PERSONNEL_DETAIL.position}</Descriptions.Item>
<Descriptions.Item label="是否注册安全工程师">
<Tag color={MOCK_PERSONNEL_DETAIL.isRegisterEngineer === "是" ? "success" : "error"}>
{MOCK_PERSONNEL_DETAIL.isRegisterEngineer}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="出版学术专著、专利、获奖、发表学术论文等" span={2}>{MOCK_PERSONNEL_DETAIL.publications}</Descriptions.Item>
<Descriptions.Item label="自我申报的专业能力及认定方式" span={2}>{MOCK_PERSONNEL_DETAIL.selfDeclaration}</Descriptions.Item>
<Descriptions.Item label="主要学习工作经历" span={2}>{MOCK_PERSONNEL_DETAIL.workExperience}</Descriptions.Item>
<Descriptions.Item label="申报专业能力证明材料" span={2}>
<Space><Button type="link" size="small">查看附件</Button><Tag color="success">.pdf</Tag></Space>
</Descriptions.Item>
</Descriptions>
<h4 style={{ fontSize: "0.85rem", color: "#1890ff", margin: "1.25rem 0 0.6rem", paddingBottom: "0.3rem", borderBottom: "2px solid #e6f7ff" }}>证照信息</h4>
<Table size="small" bordered rowKey="id" pagination={false} dataSource={MOCK_CERTIFICATES} scroll={{ x: 920 }}
columns={[
{ title: "证照名称", dataIndex: "name", ellipsis: true },
{ title: "证书编号", dataIndex: "certNo", width: 120 },
{ title: "证书类别", dataIndex: "category", width: 120 },
{ title: "发证机关", dataIndex: "issuer", width: 150, ellipsis: true },
{ title: "有效开始日期", dataIndex: "startDate", width: 120 },
{ title: "有效结束日期", dataIndex: "endDate", width: 120 },
{ title: "操作", fixed: "right", width: 80, render: () => <Button type="link" size="small">查看证书</Button> },
]}
/>
</Modal>
{/* 设备详细信息弹窗 */}
<Modal
title="设备详细信息"
open={equipVisible}
onCancel={() => setEquipVisible(false)}
width={600}
footer={<Button onClick={() => setEquipVisible(false)}>关闭</Button>}
>
{selectedEquip && (
<Descriptions column={1} size="small">
<Descriptions.Item label="设备名称">{MOCK_EQUIP_DETAIL.name}</Descriptions.Item>
<Descriptions.Item label="型号">{MOCK_EQUIP_DETAIL.model}</Descriptions.Item>
<Descriptions.Item label="仪器分类">{MOCK_EQUIP_DETAIL.category}</Descriptions.Item>
<Descriptions.Item label="设备类型">{MOCK_EQUIP_DETAIL.type}</Descriptions.Item>
<Descriptions.Item label="厂家">{MOCK_EQUIP_DETAIL.manufacturer}</Descriptions.Item>
<Descriptions.Item label="现场校验类型">{MOCK_EQUIP_DETAIL.calibrationType}</Descriptions.Item>
<Descriptions.Item label="设备流量说明">{MOCK_EQUIP_DETAIL.flowDesc}</Descriptions.Item>
<Descriptions.Item label="最大流量">{MOCK_EQUIP_DETAIL.maxFlow}</Descriptions.Item>
<Descriptions.Item label="最小流量">{MOCK_EQUIP_DETAIL.minFlow}</Descriptions.Item>
<Descriptions.Item label="是否双路">{MOCK_EQUIP_DETAIL.dualChannel}</Descriptions.Item>
<Descriptions.Item label="校准初始值" span={2}>{MOCK_EQUIP_DETAIL.calibInitValue}</Descriptions.Item>
<Descriptions.Item label="校准单位" span={2}>{MOCK_EQUIP_DETAIL.calibUnit}</Descriptions.Item>
<Descriptions.Item label="设备状态">
<Tag color={CALIBRATION_MAP[MOCK_EQUIP_DETAIL.status]?.color}>{CALIBRATION_MAP[MOCK_EQUIP_DETAIL.status]?.label}</Tag>
</Descriptions.Item>
</Descriptions>
)}
</Modal>
</>
);
};
export default FilingTabs;

View File

@ -0,0 +1,184 @@
import React, { useState, useEffect } from "react";
import { Button, Form, Select, Space, 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 { CHANGE_STATUS_MAP } from "~/enumerate/constant";
const MOCK_DATA = [
{
id: 1,
orgName: "重庆安评技术研究院有限公司",
orgType: "本地机构",
filingNo: "BA-2026-001",
businessScope: "化工",
changeItem: "法定代表人变更",
changeStatus: "pending",
},
{
id: 2,
orgName: "重庆恒安安全评价有限公司",
orgType: "本地机构",
filingNo: "BA-2025-032",
businessScope: "化工, 建筑施工",
changeItem: "注册地址变更",
changeStatus: "stuck",
},
{
id: 3,
orgName: "北京中安评价中心(重庆分公司)",
orgType: "异地机构",
filingNo: "BA-2026-015",
businessScope: "矿山",
changeItem: "业务范围变更",
changeStatus: "approved",
},
];
const { router } = tools;
const QualChange = (props) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleSearch = () => {
setLoading(true);
setTimeout(() => setLoading(false), 500);
};
const handleReset = (values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
};
useEffect(() => {
form.setFieldsValue(router.query);
}, []);
const columns = [
{
title: "序号",
dataIndex: "id",
width: 60,
fixed: "left",
render: (text, record, index) => index + 1,
},
{ title: "机构名称", dataIndex: "orgName", width: 220, ellipsis: true },
{ title: "机构类型", dataIndex: "orgType", width: 100 },
{ title: "备案编号", dataIndex: "filingNo", width: 140, ellipsis: true },
{ title: "安全评价业务范围", dataIndex: "businessScope", width: 180, ellipsis: true },
{ title: "变更项", dataIndex: "changeItem", width: 140, ellipsis: true },
{
title: "变更状态",
dataIndex: "changeStatus",
width: 100,
render: (status) => {
const config = CHANGE_STATUS_MAP[status];
return config ? <Tag color={config.color}>{config.label}</Tag> : status;
},
},
{
title: "操作",
width: 140,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" size="small" onClick={() => props.history.push(`FilingDetail?id=${record.id}`)}>
查看
</Button>
{record.changeStatus !== "approved" && (
<Button type="link" size="small" onClick={() => props.history.push(`QualReviewForm?id=${record.id}`)}>
审核
</Button>
)}
</Space>
),
},
];
return (
<PageLayout title="备案变更管理">
<SearchForm
style={{ marginBottom: 24 }}
form={form}
loading={loading}
formLine={[
<Form.Item key="orgName" name="orgName">
<ControlWrapper.Input label="机构名称" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="filingNo" name="filingNo">
<ControlWrapper.Input label="备案编号" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="businessScope" name="businessScope">
<ControlWrapper.Select
label="安全评价业务范围"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="石油加工">石油加工</Select.Option>
<Select.Option value="化工">化工</Select.Option>
<Select.Option value="矿山">矿山</Select.Option>
<Select.Option value="建筑施工">建筑施工</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="changeStatus" name="changeStatus">
<ControlWrapper.Select
label="变更状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="stuck">停滞中</Select.Option>
<Select.Option value="pending">待审核</Select.Option>
<Select.Option value="approved">已审核</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
]}
onReset={handleReset}
onFinish={(values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
}}
/>
<Table
rowKey="id"
columns={columns}
dataSource={MOCK_DATA}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: MOCK_DATA.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: router.query.current,
pageSize: router.query.pageSize,
onChange: (page, pageSize) => {
router.query = {
...router.query,
current: page,
pageSize,
};
handleSearch();
},
}}
/>
</PageLayout>
);
};
export default AntdTableFuncControl(QualChange);

View File

@ -0,0 +1,188 @@
import React, { useState, useEffect } from "react";
import { Button, Form, Select, Space, 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 { VERIFY_STATUS_MAP } from "~/enumerate/constant";
const MOCK_DATA = [
{
id: 1,
orgName: "重庆恒安安全评价有限公司",
orgType: "本地机构",
certNo: "API-2025-032",
businessScope: "化工, 建筑施工",
confirmStatus: "pending",
},
{
id: 2,
orgName: "重庆安环检测技术有限公司",
orgType: "本地机构",
certNo: "API-2025-028",
businessScope: "矿山",
confirmStatus: "passed",
},
{
id: 3,
orgName: "上海安信评价中心(重庆办)",
orgType: "异地机构",
certNo: "API-2025-045",
businessScope: "石油加工",
confirmStatus: "rejected",
},
];
const { router } = tools;
const QualConfirm = (props) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleSearch = () => {
setLoading(true);
setTimeout(() => setLoading(false), 500);
};
const handleReset = (values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
};
useEffect(() => {
form.setFieldsValue(router.query);
}, []);
const columns = [
{
title: "序号",
dataIndex: "id",
width: 60,
fixed: "left",
render: (text, record, index) => index + 1,
},
{ title: "机构名称", dataIndex: "orgName", width: 220, ellipsis: true },
{ title: "机构类型", dataIndex: "orgType", width: 100 },
{ title: "证书编号", dataIndex: "certNo", width: 140, ellipsis: true },
{ title: "安全评价业务范围", dataIndex: "businessScope", width: 180, ellipsis: true },
{
title: "确认状态",
dataIndex: "confirmStatus",
width: 100,
render: (status) => {
const config = VERIFY_STATUS_MAP[status];
return config ? <Tag color={config.color}>{config.label}</Tag> : status;
},
},
{
title: "操作",
width: 140,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" size="small" onClick={() => props.history.push(`FilingDetail?id=${record.id}`)}>
查看
</Button>
{record.confirmStatus !== "passed" && (
<Button type="link" size="small" onClick={() => props.history.push(`QualConfirmForm?id=${record.id}`)}>
确认
</Button>
)}
</Space>
),
},
];
return (
<PageLayout title="资质备案确认">
<SearchForm
style={{ marginBottom: 24 }}
form={form}
loading={loading}
formLine={[
<Form.Item key="orgName" name="orgName">
<ControlWrapper.Input label="机构名称" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="orgType" name="orgType">
<ControlWrapper.Select
label="机构类型"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="本地机构">本地机构</Select.Option>
<Select.Option value="异地机构">异地机构</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="businessScope" name="businessScope">
<ControlWrapper.Select
label="安全评价业务范围"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="石油加工">石油加工</Select.Option>
<Select.Option value="化工">化工</Select.Option>
<Select.Option value="矿山">矿山</Select.Option>
<Select.Option value="建筑施工">建筑施工</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="confirmStatus" name="confirmStatus">
<ControlWrapper.Select
label="确认状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="pending">待确认</Select.Option>
<Select.Option value="passed">确认通过</Select.Option>
<Select.Option value="rejected">打回</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
]}
onReset={handleReset}
onFinish={(values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
}}
/>
<Table
rowKey="id"
columns={columns}
dataSource={MOCK_DATA}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: MOCK_DATA.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: router.query.current,
pageSize: router.query.pageSize,
onChange: (page, pageSize) => {
router.query = {
...router.query,
current: page,
pageSize,
};
handleSearch();
},
}}
/>
</PageLayout>
);
};
export default AntdTableFuncControl(QualConfirm);

View File

@ -0,0 +1,101 @@
.qual-confirm-form {
.summary-card {
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 0.75rem 1rem;
margin-bottom: 0.75rem;
}
.summary-card-title {
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.summary-card-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 0.5rem;
}
.summary-label {
font-size: 0.72rem;
color: rgba(0, 0, 0, 0.45);
}
.summary-value {
font-size: 0.88rem;
font-weight: 500;
margin-top: 0.15rem;
}
.analysis-card {
background: #f8fafc;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 0.75rem 1rem;
margin-bottom: 0.75rem;
}
.analysis-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 0.5rem;
}
.analysis-item {
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 0.5rem;
text-align: center;
}
.analysis-number {
font-size: 1.1rem;
font-weight: 700;
margin: 0.15rem 0;
&.green {
color: #52c41a;
}
}
.analysis-warning {
margin-top: 0.5rem;
padding: 0.4rem 0.6rem;
background: #fffbeb;
border: 1px solid #fde68a;
border-radius: 6px;
font-size: 0.78rem;
color: rgba(0, 0, 0, 0.45);
}
.opinion-card {
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 0.75rem 1rem;
margin-bottom: 0.75rem;
}
.opinion-grid {
display: grid;
gap: 0.5rem;
}
.opinion-label {
font-size: 0.8rem;
margin-bottom: 4px;
}
.footer-bar {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 0.75rem;
border-top: 1px solid #d9d9d9;
gap: 0.5rem;
}
}

View File

@ -0,0 +1,129 @@
import React, { useState } from "react";
import { Button, Select, Tag, Space, Input } from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import { MOCK_DETAIL, MOCK_MATERIALS, MOCK_PERSONNEL } from "../mockData";
import "./indes.less";
const { TextArea } = Input;
const LIST_PATH = "./../";
const QualConfirmForm = (props) => {
const [result, setResult] = useState(undefined);
const [opinion, setOpinion] = useState("");
const detail = MOCK_DETAIL;
const totalCount = MOCK_MATERIALS.length;
const qualifiedCount = 8; // mock 符合数
const isPersonnelMet = MOCK_PERSONNEL.length >= 25;
const handleSubmit = () => {
console.log("确认提交:", { result, opinion });
// TODO: 调用接口
};
return (
<PageLayout
title={
<Space>
<span>资质备案确认</span>
<Tag color="warning">待确认</Tag>
</Space>
}
history={props.history}
previous
>
<div className="qual-confirm-form">
{/* 基本信息 */}
<div className="summary-card">
<div className="summary-card-title">基本信息</div>
<div className="summary-card-grid">
<div>
<div className="summary-label">机构名称</div>
<div className="summary-value">{detail.orgName}</div>
</div>
<div>
<div className="summary-label">证书编号</div>
<div className="summary-value">{detail.certNo}</div>
</div>
<div>
<div className="summary-label">业务范围</div>
<div className="summary-value">{detail.scopeName}</div>
</div>
<div>
<div className="summary-label">提交时间</div>
<div className="summary-value">2026-06-20</div>
</div>
</div>
</div>
{/* 分析结果 */}
<div className="analysis-card">
<div className="summary-card-title">分析结果</div>
<div className="analysis-grid">
<div className="analysis-item">
<div className="summary-label">备案材料核验</div>
<div className="analysis-number green">{totalCount}/{totalCount}</div>
<div className="summary-label">全部已上传</div>
</div>
<div className="analysis-item">
<div className="summary-label">材料符合性</div>
<div className="analysis-number green">{qualifiedCount}/{totalCount}</div>
<div className="summary-label">符合要求</div>
</div>
<div className="analysis-item">
<div className="summary-label">人员配备</div>
<div className="analysis-number" style={{ color: isPersonnelMet ? "#52c41a" : "#faad14" }}>
{MOCK_PERSONNEL.length}/25
</div>
<div className="summary-label">{isPersonnelMet ? "满足要求" : "不满足要求"}</div>
</div>
</div>
{!isPersonnelMet && (
<div className="analysis-warning">
当前备案人员{MOCK_PERSONNEL.length}需满足专职技术人员不少于25人的要求建议机构补充人员后再确认
</div>
)}
</div>
{/* 确认意见 */}
<div className="opinion-card">
<div className="summary-card-title">确认意见</div>
<div className="opinion-grid">
<div>
<div className="opinion-label">确认结果</div>
<Select
style={{ width: "100%" }}
placeholder="请选择"
value={result}
onChange={setResult}
>
<Select.Option value="passed">确认通过</Select.Option>
<Select.Option value="rejected">打回</Select.Option>
</Select>
</div>
<div>
<div className="opinion-label">确认意见</div>
<TextArea
rows={4}
placeholder="请输入确认意见..."
value={opinion}
onChange={(e) => setOpinion(e.target.value)}
/>
</div>
</div>
</div>
{/* 操作按钮 */}
<div className="footer-bar">
<Button onClick={() => props.history?.push(LIST_PATH)}>取消</Button>
<Button type="primary" onClick={handleSubmit}>提交确认</Button>
</div>
</div>
</PageLayout>
);
};
export default QualConfirmForm;

View File

@ -0,0 +1,292 @@
import React, { useState, useEffect } from "react";
import { Button, Form, Select, Space, Table, Tag, Modal, Input, DatePicker } 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 { PUBLICITY_STATUS_MAP } from "~/enumerate/constant";
const { TextArea } = Input;
const MOCK_DATA = [
{
id: 1,
orgName: "重庆安评技术研究院有限公司",
orgType: "本地机构",
certNo: "API-2026-001",
businessScope: "化工, 石油加工",
reviewDept: "市应急管理局",
reviewTime: "2026-06-20",
status: "pending",
},
{
id: 2,
orgName: "重庆恒安安全评价有限公司",
orgType: "本地机构",
certNo: "API-2025-032",
businessScope: "化工",
reviewDept: "市应急管理局",
reviewTime: "2026-05-15",
status: "published",
},
{
id: 3,
orgName: "北京中安评价中心(重庆分公司)",
orgType: "异地机构",
certNo: "API-2026-015",
businessScope: "矿山, 建筑施工",
reviewDept: "市应急管理局",
reviewTime: "2026-06-18",
status: "pending",
},
];
const { router } = tools;
const QualPublicity = (props) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [publicityVisible, setPublicityVisible] = useState(false);
const [publicityTarget, setPublicityTarget] = useState(null);
const [publicityDept, setPublicityDept] = useState(undefined);
const [publicityContent, setPublicityContent] = useState("");
const [publicityStartDate, setPublicityStartDate] = useState(null);
const [publicityEndDate, setPublicityEndDate] = useState(null);
const openPublicity = (record) => {
setPublicityTarget(record);
setPublicityDept(undefined);
setPublicityContent(
"依据《安全评价机构管理规定》等相关法规,经审核,该机构符合安全评价机构资质条件,现予以公示,接受社会监督。"
);
setPublicityStartDate(null);
setPublicityEndDate(null);
setPublicityVisible(true);
};
const handlePublicitySubmit = () => {
console.log("公示提交:", {
target: publicityTarget,
dept: publicityDept,
content: publicityContent,
startDate: publicityStartDate,
endDate: publicityEndDate,
});
setPublicityVisible(false);
};
const handleSearch = () => {
setLoading(true);
setTimeout(() => setLoading(false), 500);
};
const handleReset = (values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
};
useEffect(() => {
form.setFieldsValue(router.query);
}, []);
const columns = [
{
title: "序号",
dataIndex: "id",
width: 60,
fixed: "left",
render: (text, record, index) => index + 1,
},
{ title: "机构名称", dataIndex: "orgName", width: 220, ellipsis: true },
{ title: "机构类型", dataIndex: "orgType", width: 100 },
{ title: "证书编号", dataIndex: "certNo", width: 140, ellipsis: true },
{ title: "安全评价业务范围", dataIndex: "businessScope", width: 180, ellipsis: true },
{ title: "审核部门", dataIndex: "reviewDept", width: 120, ellipsis: true },
{ title: "审核通过时间", dataIndex: "reviewTime", width: 120 },
{
title: "公示状态",
dataIndex: "status",
width: 100,
render: (status) => {
const config = PUBLICITY_STATUS_MAP[status];
return config ? <Tag color={config.color}>{config.label}</Tag> : status;
},
},
{
title: "操作",
width: 180,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" size="small" onClick={() => props.history.push(`FilingDetail?id=${record.id}`)}>
查看
</Button>
{record.status === "pending" && (
<Button type="link" size="small" onClick={() => openPublicity(record)}>
公示
</Button>
)}
<Button type="link" size="small">
公示信息
</Button>
</Space>
),
},
];
return (
<PageLayout title="备案公示管理">
<SearchForm
style={{ marginBottom: 24 }}
form={form}
loading={loading}
formLine={[
<Form.Item key="orgName" name="orgName">
<ControlWrapper.Input label="机构名称" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="orgType" name="orgType">
<ControlWrapper.Select
label="机构类型"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="本地机构">本地机构</Select.Option>
<Select.Option value="异地机构">异地机构</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="businessScope" name="businessScope">
<ControlWrapper.Select
label="安全评价业务范围"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="石油加工">石油加工</Select.Option>
<Select.Option value="化工">化工</Select.Option>
<Select.Option value="矿山">矿山</Select.Option>
<Select.Option value="建筑施工">建筑施工</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="status" name="status">
<ControlWrapper.Select
label="公示状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="pending">待公示</Select.Option>
<Select.Option value="published">已公示</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
]}
onReset={handleReset}
onFinish={(values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
}}
/>
<Table
rowKey="id"
columns={columns}
dataSource={MOCK_DATA}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: MOCK_DATA.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: router.query.current,
pageSize: router.query.pageSize,
onChange: (page, pageSize) => {
router.query = {
...router.query,
current: page,
pageSize,
};
handleSearch();
},
}}
/>
<Modal
title="备案公示"
open={publicityVisible}
onCancel={() => setPublicityVisible(false)}
width={560}
footer={
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
<Button onClick={() => setPublicityVisible(false)}>取消</Button>
<Button type="primary" onClick={handlePublicitySubmit}>提交公示</Button>
</div>
}
>
<div style={{ display: "grid", gap: "0.8rem" }}>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>公示信息</div>
</div>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>机构名称</div>
<div style={{ fontSize: "0.88rem", fontWeight: 500 }}>{publicityTarget?.orgName || "—"}</div>
</div>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>公示部门</div>
<Select
style={{ width: "100%" }}
placeholder="请选择"
value={publicityDept}
onChange={setPublicityDept}
>
<Select.Option value="city">市应急管理局</Select.Option>
<Select.Option value="district">县区应急管理局</Select.Option>
</Select>
</div>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>公示内容</div>
<TextArea
rows={4}
placeholder="请输入公示内容..."
value={publicityContent}
onChange={(e) => setPublicityContent(e.target.value)}
/>
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.5rem" }}>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>公示开始时间</div>
<DatePicker
style={{ width: "100%" }}
placeholder="请选择"
value={publicityStartDate}
onChange={setPublicityStartDate}
/>
</div>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>公示结束时间</div>
<DatePicker
style={{ width: "100%" }}
placeholder="请选择"
value={publicityEndDate}
onChange={setPublicityEndDate}
/>
</div>
</div>
</div>
</Modal>
</PageLayout>
);
};
export default AntdTableFuncControl(QualPublicity);

View File

@ -0,0 +1,212 @@
import React, { useState, useEffect } from "react";
import { Button, Form, Select, Space, 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 { REVIEW_STATUS_MAP, EXPERT_STATUS_MAP } from "~/enumerate/constant";
// 静态模拟数据
const MOCK_DATA = [
{
id: 1,
orgName: "重庆安评技术研究院有限公司",
orgType: "本地机构",
certNo: "API-2026-001",
businessScope: "化工, 石油加工",
expertStatus: "pending",
reviewStatus: "pending",
},
{
id: 2,
orgName: "北京中安评价中心(重庆分公司)",
orgType: "异地机构",
certNo: "API-2026-015",
businessScope: "矿山, 建筑施工",
expertStatus: "arranging",
reviewStatus: "reviewing",
},
{
id: 3,
orgName: "重庆渝安风险评估中心",
orgType: "本地机构",
certNo: "API-2026-008",
businessScope: "化工",
expertStatus: "none",
reviewStatus: "rejected",
},
];
const { router } = tools;
const QualReview = (props) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleSearch = () => {
setLoading(true);
setTimeout(() => setLoading(false), 500);
};
const handleReset = (values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
};
useEffect(() => {
form.setFieldsValue(router.query);
}, []);
const columns = [
{
title: "序号",
dataIndex: "id",
width: 60,
fixed: "left",
render: (text, record, index) => index + 1,
},
{ title: "机构名称", dataIndex: "orgName", width: 220, ellipsis: true },
{ title: "机构类型", dataIndex: "orgType", width: 100 },
{ title: "证书编号", dataIndex: "certNo", width: 140, ellipsis: true },
{ title: "安全评价业务范围", dataIndex: "businessScope", width: 180, ellipsis: true },
{
title: "专家核验",
dataIndex: "expertStatus",
width: 100,
render: (status) => {
const config = EXPERT_STATUS_MAP[status];
return config ? <Tag color={config.color}>{config.label}</Tag> : '--';
},
},
{
title: "审核状态",
dataIndex: "reviewStatus",
width: 100,
render: (status) => {
const config = REVIEW_STATUS_MAP[status];
return config ? <Tag color={config.color}>{config.label}</Tag> : '--';
},
},
{
title: "操作",
width: 140,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" size="small" onClick={() => props.history.push(`FilingDetail?id=${record.id}`)}>
查看
</Button>
{record.reviewStatus === "pending" && (
<Button type="link" size="small" onClick={() => props.history.push(`QualReviewForm?id=${record.id}`)}>
审核
</Button>
)}
</Space>
),
},
];
return (
<PageLayout title="资质备案审核">
<SearchForm
style={{ marginBottom: 24 }}
form={form}
loading={loading}
formLine={[
<Form.Item key="orgName" name="orgName">
<ControlWrapper.Input label="机构名称" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="orgType" name="orgType">
<ControlWrapper.Select
label="机构类型"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="本地机构">本地机构</Select.Option>
<Select.Option value="异地机构">异地机构</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="businessScope" name="businessScope">
<ControlWrapper.Select
label="安全评价业务范围"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="石油加工">石油加工</Select.Option>
<Select.Option value="化工">化工</Select.Option>
<Select.Option value="矿山">矿山</Select.Option>
<Select.Option value="建筑施工">建筑施工</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="needExpertCheck" name="needExpertCheck">
<ControlWrapper.Select
label="是否专家核验"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="yes"></Select.Option>
<Select.Option value="no"></Select.Option>
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="reviewStatus" name="reviewStatus">
<ControlWrapper.Select
label="审核状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
<Select.Option value="pending">待审核</Select.Option>
<Select.Option value="reviewing">审核中</Select.Option>
<Select.Option value="rejected">打回</Select.Option>
</ControlWrapper.Select>
</Form.Item>,
]}
onReset={handleReset}
onFinish={(values) => {
router.query = {
...router.query,
...values,
current: 1,
pageSize: 10,
};
handleSearch();
}}
/>
<Table
rowKey="id"
columns={columns}
dataSource={MOCK_DATA}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: MOCK_DATA.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: router.query.current,
pageSize: router.query.pageSize,
onChange: (page, pageSize) => {
router.query = {
...router.query,
current: page,
pageSize,
};
handleSearch();
},
}}
/>
</PageLayout>
);
};
export default AntdTableFuncControl(QualReview);

View File

@ -0,0 +1,46 @@
.qual-review-form {
// 法律规定内容
.legal-check-bar {
background: #f8fafc;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 0.6rem 0.75rem;
margin-bottom: 0.75rem;
}
.legal-check-title {
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.4rem;
}
.legal-check-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.35rem;
}
.legal-check-item {
display: flex;
align-items: flex-start;
gap: 0.4rem;
padding: 0.3rem 0.5rem;
border-radius: 4px;
font-size: 0.78rem;
}
.legal-check-item-desc {
font-size: 0.72rem;
color: rgba(0, 0, 0, 0.45);
}
// 底部操作栏
.footer-bar {
display: flex;
justify-content: flex-end;
align-items: center;
padding-top: 0.75rem;
border-top: 1px solid #d9d9d9;
gap: 0.5rem;
}
}

View File

@ -0,0 +1,122 @@
import React, { useState } from "react";
import { Button, Tag, Space, Modal, Select, Input } from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import { REVIEW_STATUS_MAP } from "~/enumerate/constant";
import { MOCK_DETAIL, MOCK_MATERIALS, LEGAL_CHECK_ITEMS, LEGAL_STATUS_STYLE } from "../mockData";
import FilingTabs from "../FilingTabs";
import "./indes.less";
const { TextArea } = Input;
const QualReviewForm = (props) => {
const [compliance, setCompliance] = useState({});
const [reviewVisible, setReviewVisible] = useState(false);
const [reviewResult, setReviewResult] = useState(undefined);
const [expert, setExpert] = useState(undefined);
const [remark, setRemark] = useState("");
const detail = MOCK_DETAIL;
const statusConfig = REVIEW_STATUS_MAP[detail.reviewStatus];
const passCount = Object.values(compliance).filter((v) => v === "pass").length;
const failCount = Object.values(compliance).filter((v) => v === "fail").length;
const evaluatedCount = passCount + failCount;
const unmetCount = MOCK_MATERIALS.length - evaluatedCount;
const handleSubmit = (action) => {
console.log("审核提交:", { action, reviewResult, expert, remark, compliance });
setReviewVisible(false);
};
return (
<PageLayout
title={
<Space>
<span>资质备案审核</span>
{statusConfig && <Tag color={statusConfig.color}>{statusConfig.label}</Tag>}
</Space>
}
history={props.history}
previous
footer={<Button type="primary" onClick={() => setReviewVisible(true)}>审核操作</Button>}
>
<div className="qual-review-form">
<div className="legal-check-bar">
<div className="legal-check-title">法律规定内容</div>
<div className="legal-check-grid">
{LEGAL_CHECK_ITEMS.map((item) => {
const style = LEGAL_STATUS_STYLE[item.status];
return (
<div key={item.key} className="legal-check-item" style={{ borderLeft: `3px solid ${style.borderColor}`, background: style.bg }}>
<span>{style.icon}</span>
<div>
<div style={{ fontWeight: 500 }}>{item.title}</div>
<div className="legal-check-item-desc">{item.desc}</div>
</div>
</div>
);
})}
</div>
</div>
<FilingTabs
detail={detail}
isReview
compliance={compliance}
onComplianceChange={(id, v) => setCompliance((prev) => ({ ...prev, [id]: v }))}
/>
<Modal
title="审核操作"
open={reviewVisible}
onCancel={() => setReviewVisible(false)}
width={580}
footer={
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
<Button onClick={() => setReviewVisible(false)}>取消</Button>
<Button type="primary" onClick={() => handleSubmit("submit")}>提交</Button>
<Button type="primary" style={{ background: "#52c41a", borderColor: "#52c41a" }} onClick={() => handleSubmit("pass")}>通过</Button>
</div>
}
>
<div style={{ background: "#f8fafc", border: "1px solid #d9d9d9", borderRadius: 6, padding: "0.75rem", marginBottom: "1rem" }}>
<div style={{ fontSize: "0.88rem", fontWeight: 600, marginBottom: "0.5rem" }}>评价分析</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: "0.4rem" }}>
<div style={statStyle}><div style={statLabelStyle}>已核验材料</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#1677ff" }}>{evaluatedCount}</div></div>
<div style={statStyle}><div style={statLabelStyle}>符合</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#52c41a" }}>{passCount}</div></div>
<div style={statStyle}><div style={statLabelStyle}>不符合</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#ff4d4f" }}>{failCount}</div></div>
<div style={statStyle}><div style={statLabelStyle}>条件未满足</div><div style={{ fontSize: "1.1rem", fontWeight: 700, color: "#faad14" }}>{unmetCount}</div></div>
</div>
</div>
<div style={{ display: "grid", gap: "0.6rem" }}>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>审核结果</div>
<Select style={{ width: "100%" }} placeholder="请选择" value={reviewResult} onChange={setReviewResult}>
<Select.Option value="pass">通过</Select.Option>
<Select.Option value="pending">待定</Select.Option>
<Select.Option value="reject">不通过</Select.Option>
</Select>
</div>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>指派专家</div>
<Select style={{ width: "100%" }} placeholder="— 请选择 —" value={expert} onChange={setExpert}>
<Select.Option value="chen">陈教授化工安全</Select.Option>
<Select.Option value="li">李教授矿山安全</Select.Option>
<Select.Option value="wang">王高工建筑施工</Select.Option>
</Select>
</div>
<div>
<div style={{ fontSize: "0.8rem", marginBottom: 4 }}>综合审核意见</div>
<TextArea rows={3} placeholder="请输入综合审核意见..." value={remark} onChange={(e) => setRemark(e.target.value)} />
</div>
</div>
</Modal>
</div>
</PageLayout>
);
};
const statStyle = { background: "#fff", border: "1px solid #d9d9d9", borderRadius: 6, padding: "0.4rem", textAlign: "center" };
const statLabelStyle = { fontSize: "0.7rem", color: "rgba(0,0,0,0.45)" };
export default QualReviewForm;

View File

@ -0,0 +1,9 @@
import React from "react";
export default (props)=>{
return (
<div style={{height: "100%"}}>
{props.children}
</div>
)
}

View File

@ -0,0 +1,118 @@
// 资质备案审核相关 mock 数据FilingDetail 和 QualReviewForm 共享
export const MOCK_DETAIL = {
id: 1,
scopeName: "化工、石油加工",
filingRegion: "重庆市渝北区",
orgName: "重庆安评技术研究院有限公司",
orgType: "本地单位",
creditCode: "915001072028699512",
officeAddress: "重庆市沙坪坝区大学城东路20号重庆科技学院第39栋西南角实习用房三楼",
registerAddress: "同上",
certNo: "API-2026-001",
website: "www.cqap.com",
legalPerson: "郑远平",
legalPhone: "023-68705577",
contactPerson: "陈芳",
contactPhone: "139****9012",
fixedAssets: "1200",
officeArea: "1200 ㎡",
archiveArea: "80 ㎡",
evaluatorCount: "25 人",
registerEngineerCount: "12 人",
description: "重庆安评技术研究院成立于2015年是一家专业从事安全评价、安全咨询、安全技术开发与转让的综合性安全技术服务机构。",
reviewStatus: "pending",
};
export const MOCK_MATERIALS = [
{ id: 1, name: "申请材料目录(原件)", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 2, name: "申请书(原件)", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 3, name: "法人证明(复印件)", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 4, name: "三年内无重大违法失信记录查询证明", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 5, name: "法定代表人承诺书(原件)", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 6, name: "固定资产法定证明材料", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 7, name: "工作场所及档案室面积证明", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 8, name: "安全评价师专业能力证明", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 9, name: "相关负责人证明材料", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
{ id: 10, name: "机构内部管理制度", attachment: "纸质版扫描件", format: "pdf", status: "uploaded" },
];
export const MOCK_PERSONNEL = [
{ id: 1, name: "张建国", type: "评价师", position: "高级评价师", title: "高级工程师", ability: "化工工艺安全风险评估" },
{ id: 2, name: "李明华", type: "评价师", position: "评价师", title: "工程师", ability: "矿山安全评价" },
{ id: 3, name: "王丽萍", type: "管理人员", position: "人事主管", title: "—", ability: "人力资源管理" },
{ id: 4, name: "陈志强", type: "评价师", position: "评价师", title: "工程师", ability: "建筑施工安全评估" },
{ id: 5, name: "赵磊", type: "管理人员", position: "综合部主任", title: "—", ability: "安全管理体系" },
];
export const MOCK_EQUIPMENT = [
{ id: 1, name: "气相色谱仪", model: "GC-2018", manufacturer: "岛津", calibration: "done" },
{ id: 2, name: "噪声计", model: "HS-6288", manufacturer: "杭州爱华", calibration: "done" },
{ id: 3, name: "粉尘采样器", model: "FC-5A", manufacturer: "北京劳保所", calibration: "pending" },
];
export const MOCK_EQUIP_DETAIL = {
name: "气相色谱仪",
model: "GC-2018",
category: "分析仪器",
type: "色谱检测类",
manufacturer: "岛津",
calibrationType: "定期检定",
flowDesc: "载气流量 30mL/min",
maxFlow: "100 mL/min",
minFlow: "1 mL/min",
dualChannel: "否",
calibInitValue: "0.0001 mg/m³",
calibUnit: "重庆市计量质量检测研究院",
status: "done",
};
export const MOCK_PERSONNEL_DETAIL = {
name: "张建国",
gender: "男",
qualScope: "化工",
birthDate: "1985-03-15",
officeAddress: "重庆市渝北区龙山街道100号",
major: "安全工程",
certLevel: "安全评价师一级 / SJP-2020-****",
email: "zhangjg@cqap.com",
personnelType: "专职评价师",
idCard: "500***********1234",
currentAddress: "重庆市渝北区新牌坊某路某号",
education: "全日制 · 本科",
graduateSchool: "重庆大学",
position: "高级评价师",
isRegisterEngineer: "是",
publications: "发表安全评价相关论文3篇参与编制行业标准1项",
selfDeclaration: "化工工艺安全风险评估,通过注册安全工程师资格考试认定",
workExperience: "2008-2012 重庆大学 安全工程专业 本科2012-至今 重庆安评技术研究院有限公司 评价师",
};
export const MOCK_CERTIFICATES = [
{ id: 1, name: "危险化学品经营许可证", certNo: "AQSC001", category: "安全生产证书", issuer: "重庆市应急管理局", startDate: "2025-03-29", endDate: "2028-03-28" },
{ id: 2, name: "安全评价机构资质证书", certNo: "API-2026-001", category: "甲级资质", issuer: "重庆市应急管理局", startDate: "2026-01-15", endDate: "2029-01-14" },
];
export const CALIBRATION_MAP = {
done: { label: "已检定", color: "success" },
pending: { label: "待检定", color: "warning" },
};
export const LEGAL_CHECK_ITEMS = [
{ key: "qualification", title: "主体资格", desc: "独立法人资格,固定资产不少于一千万元", status: "pass" },
{ key: "facility", title: "场所与设备", desc: "工作场所建筑面积不少于1000㎡", status: "pass" },
{ key: "staffCount", title: "人员数量与结构", desc: "专职技术人员≥25人中级注安师≥30%", status: "pass" },
{ key: "staffExp", title: "人员资历", desc: "专职技术人员在本行业领域工作二年以上", status: "pass" },
{ key: "keyRole", title: "关键岗位要求", desc: "负责人具有高级职称,工作八年以上", status: "pass" },
{ key: "management", title: "管理体系", desc: "需核验文件化管理体系是否符合要求", status: "warn" },
{ key: "commitment", title: "责任承诺", desc: "法定代表人已出具承诺书", status: "pass" },
{ key: "disclosure", title: "信息公开", desc: "未检测到公示网站", status: "fail" },
{ key: "credit", title: "信用记录", desc: "三年内无重大违法失信记录", status: "pass" },
{ key: "other", title: "其他", desc: "法律、行政法规规定的其他条件", status: "pass" },
];
export const LEGAL_STATUS_STYLE = {
pass: { icon: "✅", borderColor: "#059669", bg: "#f0fdf4" },
warn: { icon: "⚠️", borderColor: "#d97706", bg: "#fffbeb" },
fail: { icon: "❌", borderColor: "#dc2626", bg: "#fef2f2" },
};

View File

@ -2,6 +2,7 @@ import { ImportCore } from "@cqsjjb/jjb-common-decorator/module";
import { theme as antdTheme, App, ConfigProvider } from "antd";
import language from "antd/locale/zh_CN";
import React from "react";
import "./index.less";
import { InjectContext } from "~/enumerate/context";
import SimulatedLayout from "./Layout";

View File

@ -0,0 +1,18 @@
.pageLayout-extra{
margin: 0px 0px 24px;
color: rgba(0, 0, 0, 0.45);
}
.micro-temp-modal-body{
max-height: 630px;
overflow-y: auto;
scrollbar-width: thin;
}
body{
height: 100%;
overflow-y: hidden;
}
.micro-temp-layout-container .micro-temp-lay-container-bottom{
padding: 10px 24px !important;
display: flex;
justify-content: flex-end;
}