huwei 2026-06-29 09:32:28 +08:00
commit e96c72adb5
24 changed files with 2864 additions and 284 deletions

View File

@ -27,7 +27,14 @@
basename: '<%= $basename %>',
API_HOST: '<%= $API_HOST %>'
};
APP_ENV.API_HOST = sessionStorage.API_HOST || APP_ENV.API_HOST || window.location.origin;
const injectedApiHost = APP_ENV.API_HOST;
const isDev = '<%= $mode %>' === 'development';
// 开发环境优先 jjb.config 注入的 API_HOST避免 sessionStorage 残留网关地址导致 Network Error
if (isDev && injectedApiHost && injectedApiHost.indexOf('http') === 0) {
APP_ENV.API_HOST = injectedApiHost;
} else {
APP_ENV.API_HOST = sessionStorage.API_HOST || injectedApiHost || window.location.origin;
}
window.process = {
env: { app: APP_ENV },
NODE_ENV: '<%= $mode %>'
@ -38,6 +45,46 @@
FRAMEWORK: APP_ENV.antd
};
})();
// 抑制 ResizeObserver 在页面/标签切换时的无害告警,避免 dev overlay 误报
(function () {
if (typeof window.ResizeObserver !== 'undefined') {
var NativeResizeObserver = window.ResizeObserver;
window.ResizeObserver = class extends NativeResizeObserver {
constructor(callback) {
super(function (entries, observer) {
window.requestAnimationFrame(function () {
callback(entries, observer);
});
});
}
};
}
function isResizeObserverLoopError(message) {
return typeof message === 'string' && message.indexOf('ResizeObserver loop') !== -1;
}
function isAxiosNetworkError(reason) {
if (!reason) return false;
var msg = typeof reason === 'string' ? reason : (reason.message || '');
return msg === 'Network Error' || (reason.isAxiosError && msg === 'Network Error');
}
window.addEventListener('error', function (event) {
if (isResizeObserverLoopError(event.message)) {
event.stopImmediatePropagation();
event.preventDefault();
}
});
window.addEventListener('unhandledrejection', function (event) {
if (isAxiosNetworkError(event.reason)) {
console.warn('[dev] API unreachable:', event.reason?.config?.url || event.reason?.message);
event.preventDefault();
}
});
})();
</script>
<!-- SCRIPTS -->
<% for (const item of $scripts) { %>

View File

@ -1,6 +1,6 @@
/** 企业信息管理:前后端字段与分页格式适配 */
import { formatDate, formatDateTime, toApiDate, toApiDateTime } from "../../utils/dateFormat";
import { formatDate, formatDateTime, normalizeDateValue, toApiDate, toApiDateTime } from "../../utils/dateFormat";
import { resolveUploadFileIds } from "../../utils/mockUpload";
import { asId, isIdFieldName, normalizeQueryIds } from "./idUtil";
import { withOrgId } from "./orgContext";
@ -162,6 +162,102 @@ export function fromOrgInfoForm(values = {}, options = {}) {
};
}
/** 监管端机构账号:后端 state 0=启用1=禁用 */
export function isOrgAccountEnabled(record = {}) {
return Number(record.state) === 0;
}
export function toOrgAccountRow(data = {}) {
return {
id: asId(data.id),
orgName: data.unitName,
district: data.districtName,
state: data.state,
createTime: formatDate(normalizeDateValue(data.createTime)),
};
}
/** 监管端分页查询(不带 orgInfoId部分筛选项需后端 OrgInfoPageQuery 扩展后生效) */
export function toRegulatorOrgAccountPageQuery(params = {}) {
const query = {
current: params.pageIndex ?? params.current ?? 1,
size: params.pageSize ?? params.size ?? 10,
};
if (params.orgName) {
query.unitName = params.orgName;
}
if (params.district) {
query.districtName = params.district;
}
const state = params.state;
if (state !== undefined && state !== null && state !== "" && state !== "全部") {
query.state = Number(state);
}
const range = params.openTimeRange;
if (Array.isArray(range) && range.length === 2 && range[0] && range[1]) {
query.createTimeStart = range[0].format ? range[0].format("YYYY-MM-DD") : range[0];
query.createTimeEnd = range[1].format ? range[1].format("YYYY-MM-DD") : range[1];
}
return normalizeQueryIds(query);
}
/** 监管端已备案机构列表行 */
export function toRegisteredOrgRow(data = {}) {
return {
id: asId(data.id),
orgName: data.unitName,
registerAddress: data.registerAddress,
serviceEnterpriseCount: 0,
evalProjectCount: 0,
filingType: data.filingTypeName,
filingRecordStatusCode: data.filingRecordStatusCode,
filingRecordStatusName: data.filingRecordStatusName,
};
}
/** 监管端已备案机构分页查询 */
export function toRegisteredOrgPageQuery(params = {}) {
const query = {
current: params.pageIndex ?? params.current ?? 1,
size: params.pageSize ?? params.size ?? 10,
};
if (params.orgName) {
query.unitName = params.orgName;
}
if (params.registerAddress) {
query.registerAddress = params.registerAddress;
}
const filingTypeCode = params.filingType;
if (filingTypeCode !== undefined && filingTypeCode !== null && filingTypeCode !== "") {
query.filingTypeCode = String(filingTypeCode);
}
const filingRecordStatusCode = params.filingRecordStatus;
if (filingRecordStatusCode !== undefined && filingRecordStatusCode !== null && filingRecordStatusCode !== "") {
query.filingRecordStatusCode = Number(filingRecordStatusCode);
}
return normalizeQueryIds(query);
}
export function fromRegulatorOrgInfoModify(values = {}, existing = {}) {
const merged = {
...toOrgInfoForm(existing),
...values,
id: asId(values.id ?? existing.id),
};
const payload = fromOrgInfoForm(merged, { isDraft: false });
return {
...payload,
id: asId(values.id ?? existing.id),
authStatusCode: existing.authStatusCode ?? payload.authStatusCode,
authStatusName: existing.authStatusName ?? payload.authStatusName,
tenantId: existing.tenantId ?? payload.tenantId,
filingTypeCode: existing.filingTypeCode ?? payload.filingTypeCode,
filingTypeName: existing.filingTypeName ?? payload.filingTypeName,
filingRecordStatusCode: existing.filingRecordStatusCode ?? payload.filingRecordStatusCode,
filingRecordStatusName: existing.filingRecordStatusName ?? payload.filingRecordStatusName,
};
}
// ─── 部门 / 岗位 ───
export function toDepartmentForm(data = {}) {
@ -411,6 +507,68 @@ export function fromQualificationForm(values = {}) {
};
}
function isQualificationExpireWarning(dateStr) {
if (!dateStr) {
return false;
}
const end = new Date(String(dateStr).replace(/-/g, "/"));
if (Number.isNaN(end.getTime())) {
return false;
}
const diff = end.getTime() - Date.now();
return diff > 0 && diff < 90 * 24 * 3600 * 1000;
}
/** classGet 分组 Map → 已备案机构详情「申请清单材料」结构 */
export function fromQualificationClassGetResponse(res) {
const map = res?.data;
if (!map || typeof map !== "object") {
return [];
}
return Object.entries(map).map(([code, list], index) => {
const items = (Array.isArray(list) ? list : []).map(toQualificationForm);
const scopeName = items[0]?.certType || code || `资质${index + 1}`;
const expireDate = items.reduce((latest, item) => {
const d = item.validEndDate;
if (!d) {
return latest;
}
return !latest || d > latest ? d : latest;
}, "");
return {
id: code || String(index),
scopeName,
expireDate: expireDate || "-",
expireWarning: isQualificationExpireWarning(expireDate),
materials: items.map((item, idx) => {
const previewUrl = item.certImgFiles?.[0]?.url || item.certImgs?.[0]?.url;
return {
id: item.id || `${code}-${idx}`,
name: item.certName || item.certType || "-",
required: true,
status: previewUrl ? "uploaded" : "pending",
previewUrl,
raw: item,
};
}),
};
});
}
/** 已备案机构详情人员列表行 */
export function toRegisteredOrgPersonnelRow(data = {}) {
const form = toStaffForm(data);
return {
id: form.id,
name: form.staffName,
gender: GENDER_NAME[form.gender] || "-",
position: form.positionName,
qualScope: form.qualScope,
education: form.education || form.educationLevel,
registerEngineer: form.registerEngineerFlag === 1 ? 1 : 0,
};
}
// ─── 装备 ───
/** 装备 enableFlag(1启用/2禁用) ↔ 前端 enableStatus(1启用/0禁用) */

View File

@ -45,20 +45,20 @@ export async function apiGet(path, params = {}, headers = {}, options = {}) {
}
/** @ 前缀表示 Post 请求体不包裹 request 字段 */
export async function apiPost(path, data = {}, headers = {}) {
export async function apiPost(path, data = {}, headers = {}, options = {}) {
const url = path.startsWith("@") ? path : `@${path}`;
try {
return await Post(url, withIds(data), buildRequestHeaders(headers));
return await Post(url, withIds(data), buildRequestHeaders(headers, options));
}
catch (err) {
return parseHttpError(err);
}
}
export async function apiPostDelete(path, id) {
export async function apiPostDelete(path, id, options = {}) {
const url = `${path}?id=${encodeURIComponent(asId(id) ?? "")}`;
try {
return await Post(url, {}, buildRequestHeaders());
return await Post(url, {}, buildRequestHeaders({}, options));
}
catch (err) {
return parseHttpError(err);

View File

@ -52,3 +52,15 @@ export const orgInfoDraft = declareRequest("orgInfoLoading", safeAction(async (v
persistOrgInfoId(result, res?.data);
return result;
}));
export {
orgAccountList,
orgAccountGet,
orgAccountModify,
orgAccountDelete,
orgAccountUpdateState,
orgAccountResetPassword,
registeredOrgList,
registeredOrgGet,
fetchRegisteredOrgDetail,
} from "../regulatorOrgInfo";

View File

@ -0,0 +1,108 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import {
fromPageResponse,
fromQualificationClassGetResponse,
fromRegulatorOrgInfoModify,
fromSingleResponse,
toOrgAccountRow,
toOrgInfoForm,
toRegisteredOrgPageQuery,
toRegisteredOrgPersonnelRow,
toRegisteredOrgRow,
toRegulatorOrgAccountPageQuery,
toStaffCertForm,
toStaffForm,
} from "../enterpriseInfo/adapter";
import { apiGet, apiPost, apiPostDelete, safeAction, safePageResult } from "../enterpriseInfo/http";
const NO_ORG_CONTEXT = { includeOrgContext: false };
export const orgAccountList = declareRequest("orgInfoLoading", safePageResult(async (params) => {
const query = toRegulatorOrgAccountPageQuery(params);
const res = await apiGet("/safety-eval/org-info/page", query, {}, NO_ORG_CONTEXT);
return fromPageResponse(res, toOrgAccountRow);
}));
export const orgAccountGet = declareRequest("orgInfoLoading", safeAction(async ({ id }) => {
const res = await apiGet("/safety-eval/org-info/get", { id }, {}, NO_ORG_CONTEXT);
const result = fromSingleResponse(res, toOrgInfoForm);
return {
...result,
raw: res?.data ?? null,
};
}));
export const orgAccountModify = declareRequest("orgInfoLoading", safeAction(async (values) => {
const { raw, ...formValues } = values;
const payload = fromRegulatorOrgInfoModify(formValues, raw || {});
const res = await apiPost("/safety-eval/org-info/modify", payload, {}, NO_ORG_CONTEXT);
return fromSingleResponse(res, toOrgInfoForm);
}));
export const orgAccountDelete = declareRequest("orgInfoLoading", safeAction(async ({ id }) => {
return apiPostDelete("/safety-eval/org-info/delete", id, NO_ORG_CONTEXT);
}));
export const orgAccountUpdateState = declareRequest("orgInfoLoading", safeAction(async ({ id, state }) => {
return apiPost("/safety-eval/org-info/update-state", { id, state }, {}, NO_ORG_CONTEXT);
}));
export const orgAccountResetPassword = declareRequest("orgInfoLoading", safeAction(async ({ id }) => {
const url = `/safety-eval/org-info/reset-password?id=${encodeURIComponent(id)}`;
return apiPost(url, {}, {}, NO_ORG_CONTEXT);
}));
export const registeredOrgList = declareRequest("orgInfoLoading", safePageResult(async (params) => {
const query = toRegisteredOrgPageQuery(params);
const res = await apiGet("/safety-eval/org-info/page", query, {}, NO_ORG_CONTEXT);
return fromPageResponse(res, toRegisteredOrgRow);
}));
export const registeredOrgGet = declareRequest("orgInfoLoading", safeAction(async ({ id }) => {
const res = await apiGet("/safety-eval/org-info/get", { id }, {}, NO_ORG_CONTEXT);
return fromSingleResponse(res, toOrgInfoForm);
}));
/** 详情页直调 HTTP避免 DVA loading 与 useEffect 依赖 dispatch 函数导致反复取消 */
export async function fetchRegisteredOrgDetail(id) {
const res = await apiGet("/safety-eval/org-info/get", { id }, {}, NO_ORG_CONTEXT);
return fromSingleResponse(res, toOrgInfoForm);
}
export async function fetchRegisteredOrgQualificationGroups(orgInfoId) {
const res = await apiGet("/safety-eval/org-qualification/classGet", { id: orgInfoId }, {}, NO_ORG_CONTEXT);
if (res?.success === false) {
throw new Error(res?.message || "加载申请材料失败");
}
return fromQualificationClassGetResponse(res);
}
export async function fetchRegisteredOrgPersonnelList(orgInfoId) {
const res = await apiGet("/safety-eval/org-personnel/page", {
orgId: orgInfoId,
current: 1,
size: 999,
}, {}, NO_ORG_CONTEXT);
const page = fromPageResponse(res, toRegisteredOrgPersonnelRow);
return page?.data || [];
}
export async function fetchRegisteredOrgPersonnelDetail({ id }) {
const res = await apiGet("/safety-eval/org-personnel/get", { id }, {}, NO_ORG_CONTEXT);
return fromSingleResponse(res, toStaffForm);
}
export async function fetchRegisteredOrgPersonnelCertList(personnelId) {
const res = await apiGet("/safety-eval/org-personnel-cert/page", {
personnelId,
current: 1,
size: 999,
}, {}, NO_ORG_CONTEXT);
const page = fromPageResponse(res, toStaffCertForm);
return page?.data || [];
}
export async function fetchRegisteredOrgPersonnelCertDetail({ id }) {
const res = await apiGet("/safety-eval/org-personnel-cert/get", { id }, {}, NO_ORG_CONTEXT);
return fromSingleResponse(res, toStaffCertForm);
}

View File

@ -35,3 +35,21 @@ export const staffCertificateEdit = declareRequest("staffCertificateLoading", sa
export const staffCertificateRemove = declareRequest("staffCertificateLoading", safeAction(async ({ id }) => {
return apiPostDelete("/safety-eval/org-personnel-cert/delete", id);
}));
export async function fetchStaffCertListByPersonnelId(personnelId) {
const query = toPageQuery({
pageIndex: 1,
pageSize: 999,
eqStaffId: personnelId,
}, {
eqStaffId: "personnelId",
});
const res = await apiGet("/safety-eval/org-personnel-cert/page", query);
const page = fromPageResponse(res, toStaffCertForm);
return page?.data || [];
}
export async function fetchStaffCertDetail({ id }) {
const res = await apiGet("/safety-eval/org-personnel-cert/get", { id });
return fromSingleResponse(res, toStaffCertForm);
}

View File

@ -0,0 +1,106 @@
import { Image, Modal } from "antd";
export function getFilePreviewType(url = "", fileName = "") {
const hint = `${fileName || ""} ${url || ""}`.toLowerCase();
if (/\.(jpe?g|png|gif|webp|bmp)(\?|#|$)/i.test(hint)) {
return "image";
}
if (/\.pdf(\?|#|$)/i.test(hint)) {
return "pdf";
}
return "unknown";
}
export function FilePreviewContent({ url, fileName, style }) {
if (!url) {
return (
<div style={{
minHeight: 120,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#fafafa",
color: "rgba(0,0,0,0.45)",
...style,
}}
>
暂无上传文件
</div>
);
}
const type = getFilePreviewType(url, fileName);
if (type === "image") {
return <Image src={url} alt={fileName || "预览"} style={{ maxHeight: 480, ...style }} />;
}
if (type === "pdf") {
return (
<iframe
title={fileName || "PDF 预览"}
src={url}
style={{ width: "100%", height: 480, border: "none", ...style }}
/>
);
}
return (
<div style={{
minHeight: 120,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#fafafa",
color: "rgba(0,0,0,0.45)",
...style,
}}
>
暂不支持在线预览请使用新窗口打开
</div>
);
}
export default function FilePreviewModal({
open,
title = "文件预览",
fileName,
url,
width = 720,
onCancel,
}) {
return (
<Modal
open={open}
destroyOnClose
title={title}
width={width}
cancelText="关闭"
okText={url ? "新窗口打开" : "关闭"}
onCancel={onCancel}
onOk={() => {
if (url) {
window.open(url, "_blank");
}
onCancel?.();
}}
>
{open && (
<div>
{fileName && (
<div style={{
padding: 12,
background: "#f8fafc",
border: "1px solid #f0f0f0",
borderRadius: 4,
marginBottom: 12,
fontWeight: 500,
}}
>
{fileName}
</div>
)}
<FilePreviewContent url={url} fileName={fileName} />
</div>
)}
</Modal>
);
}

View File

@ -31,12 +31,33 @@ export const FILING_TYPE_OPTIONS = [
{ label: "确认备案", value: "确认备案" },
];
/** 监管端已备案机构 - 备案类型搜索 */
export const REGISTERED_ORG_FILING_TYPE_SEARCH_OPTIONS = [
{ label: "全部", value: "" },
{ label: "审核备案", value: "1" },
{ label: "确认备案", value: "2" },
];
/** 监管端已备案机构 - 备案状态搜索1=已备案2=未备案) */
export const REGISTERED_ORG_FILING_RECORD_STATUS_OPTIONS = [
{ label: "全部", value: "" },
{ label: "已备案", value: "1" },
{ label: "未备案", value: "2" },
];
/** 备案状态(机构信息) */
export const FILING_RECORD_STATUS_OPTIONS = [
{ label: "已备案", value: "已备案" },
{ label: "未备案", value: "未备案" },
];
/** 监管端机构账号状态(后端 state0=启用1=禁用) */
export const ORG_ACCOUNT_STATE_OPTIONS = [
{ label: "全部", value: "" },
{ label: "启用", value: "0" },
{ label: "禁用", value: "1" },
];
/** 资质范围 / 证照类型(行业) */
export const QUALIFICATION_INDUSTRY_OPTIONS = [
{ label: "煤炭开采业", value: "煤炭开采业" },

View File

@ -13,7 +13,14 @@ dayjs.locale("zh-cn");
setJJBCommonAntdMessage(message);
const app = setup();
getFileUrlFromServer();
// jjb.config contextInject.fileUrl 优先,避免启动时请求网关附件接口失败触发 dev overlay
if (process.env.app?.fileUrl) {
window.fileUrl = process.env.app.fileUrl;
}
getFileUrlFromServer().catch((err) => {
console.warn("[getFileUrlFromServer] failed:", err?.message || err);
});
// 非底座环境运行
if (!window.__POWERED_BY_QIANKUN__) {
// 云组件默认依赖

View File

@ -0,0 +1,391 @@
/** 监管端 - 基础信息管理 mock 数据(暂不对接后端) */
const EVALUATOR_DETAILS = {
1: {
id: 1,
name: "张建国",
gender: "男",
birthDate: "1985-03-15",
idCard: "500***********1234",
education: "全日制 · 本科",
graduateSchool: "重庆大学",
major: "安全工程",
orgName: "重庆安评技术研究院有限公司",
deptName: "评价一部",
positionName: "高级评价师",
account: "138****5678",
employmentStatus: 1,
registerEngineerFlag: 1,
status: "normal",
statusTip: "",
certificatesSummary: "安全评价师一级, 注册安全工程师",
certificates: [
{
id: 101,
certName: "安全评价师一级",
certNo: "SJP-2020-****",
issueDate: "2020-06-01",
expireDate: "2026-06-01",
status: "expired",
statusLabel: "已过期",
statusTip: "证书已过期",
},
{
id: 102,
certName: "注册安全工程师",
certNo: "ZGAQ-2018-****",
issueDate: "2018-09-15",
expireDate: "2027-09-15",
status: "normal",
statusLabel: "正常",
statusTip: "",
},
{
id: 103,
certName: "安全评价师二级",
certNo: "SJP-2023-****",
issueDate: "2023-05-01",
expireDate: "2026-08-01",
status: "expiring",
statusLabel: "临期",
statusTip: "证书即将到期不足3月",
},
],
},
2: {
id: 2,
name: "李明华",
gender: "男",
birthDate: "1988-07-22",
idCard: "500***********5678",
education: "全日制 · 硕士",
graduateSchool: "中国矿业大学",
major: "安全技术及工程",
orgName: "重庆安评技术研究院有限公司",
deptName: "评价二部",
positionName: "评价师",
account: "139****1234",
employmentStatus: 1,
registerEngineerFlag: 1,
status: "normal",
statusTip: "",
certificatesSummary: "安全评价师二级, 注册安全工程师",
certificates: [
{
id: 201,
certName: "安全评价师二级",
certNo: "SJP-2021-****",
issueDate: "2021-03-10",
expireDate: "2027-03-10",
status: "normal",
statusLabel: "正常",
statusTip: "",
},
{
id: 202,
certName: "注册安全工程师",
certNo: "ZGAQ-2019-****",
issueDate: "2019-11-20",
expireDate: "2028-11-20",
status: "normal",
statusLabel: "正常",
statusTip: "",
},
],
},
3: {
id: 3,
name: "王丽萍",
gender: "女",
birthDate: "1990-11-08",
idCard: "500***********9012",
education: "全日制 · 本科",
graduateSchool: "西南大学",
major: "化学工程",
orgName: "重庆恒安安全评价有限公司",
deptName: "评价部",
positionName: "评价师",
account: "137****8899",
employmentStatus: 1,
registerEngineerFlag: 0,
status: "abnormal",
statusTip: "此评价师的社保存在多处缴纳",
certificatesSummary: "安全评价师三级",
certificates: [
{
id: 301,
certName: "安全评价师三级",
certNo: "SJP-2022-****",
issueDate: "2022-08-01",
expireDate: "2028-08-01",
status: "normal",
statusLabel: "正常",
statusTip: "",
},
],
},
4: {
id: 4,
name: "陈华",
gender: "男",
birthDate: "1987-04-18",
idCard: "500***********3456",
education: "在职教育 · 本科",
graduateSchool: "重庆理工大学",
major: "安全工程",
orgName: "重庆渝安风险评估中心",
deptName: "技术部",
positionName: "评价师",
account: "136****7788",
employmentStatus: 1,
registerEngineerFlag: 1,
status: "abnormal",
statusTip: "评价师证书临期不足3月",
certificatesSummary: "安全评价师二级",
certificates: [
{
id: 401,
certName: "安全评价师二级",
certNo: "SJP-2020-****",
issueDate: "2020-04-15",
expireDate: "2026-08-15",
status: "expiring",
statusLabel: "临期",
statusTip: "证书即将到期不足3月",
},
],
},
5: {
id: 5,
name: "赵敏",
gender: "女",
birthDate: "1992-01-30",
idCard: "500***********7890",
education: "全日制 · 本科",
graduateSchool: "重庆科技学院",
major: "安全工程",
orgName: "重庆安环检测技术有限公司",
deptName: "评价中心",
positionName: "评价师",
account: "135****6677",
employmentStatus: 0,
registerEngineerFlag: 0,
status: "abnormal",
statusTip: "此评价师的社保存在多处缴纳证书临期不足3月",
certificatesSummary: "安全评价师三级",
certificates: [
{
id: 501,
certName: "安全评价师三级",
certNo: "SJP-2023-****",
issueDate: "2023-06-01",
expireDate: "2026-07-01",
status: "expiring",
statusLabel: "临期",
statusTip: "证书即将到期不足3月",
},
],
},
};
const EVALUATOR_LIST = Object.values(EVALUATOR_DETAILS).map((item) => ({
id: item.id,
orgName: item.orgName,
evaluatorName: item.name,
registerEngineerFlag: item.registerEngineerFlag,
registerEngineerLabel: item.registerEngineerFlag === 1 ? "是" : "否",
employmentStatus: item.employmentStatus,
employmentLabel: item.employmentStatus === 1 ? "是" : "否",
certificatesSummary: item.certificatesSummary,
status: item.status,
statusLabel: item.status === "normal" ? "正常" : "异常",
statusTip: item.statusTip,
}));
const ENTERPRISE_PORTRAIT_DETAILS = {
1: {
id: 1,
enterpriseName: "重庆华安安全科技有限公司",
creditCode: "91500112MA******",
industry: "化工行业",
district: "渝北区",
accountSource: "企业注册",
projectOnTime: "12/15",
projectOnTimeRate: "80%",
reportQuality: "合格",
complaintCount: 0,
penaltyCount: 0,
infoLevel: "一级",
infoLevelCode: "level1",
scoreGrade: "A级",
evalCount: 15,
reportQualityDesc: "近6个月无不合格报告",
penaltyDesc: "无不良记录",
evalCountDesc: "累计安全评价",
},
2: {
id: 2,
enterpriseName: "重庆鼎信安全咨询有限公司",
creditCode: "91500105MA******",
industry: "建筑施工",
district: "江北区",
accountSource: "机构注册",
projectOnTime: "8/10",
projectOnTimeRate: "80%",
reportQuality: "合格",
complaintCount: 1,
penaltyCount: 0,
infoLevel: "二级",
infoLevelCode: "level2",
scoreGrade: "B级",
evalCount: 10,
reportQualityDesc: "近6个月1份需整改",
penaltyDesc: "无行政处罚",
evalCountDesc: "累计安全评价",
},
3: {
id: 3,
enterpriseName: "重庆安环检测技术有限公司",
creditCode: "91500108MA******",
industry: "仓储物流",
district: "南岸区",
accountSource: "监管注册",
projectOnTime: "5/8",
projectOnTimeRate: "62.5%",
reportQuality: "不合格",
complaintCount: 2,
penaltyCount: 1,
infoLevel: "三级",
infoLevelCode: "level3",
scoreGrade: "C级",
evalCount: 8,
reportQualityDesc: "近6个月存在不合格报告",
penaltyDesc: "1次行政处罚",
evalCountDesc: "累计安全评价",
},
4: {
id: 4,
enterpriseName: "重庆恒安安全评价有限公司",
creditCode: "91500106MA******",
industry: "矿山",
district: "九龙坡区",
accountSource: "企业注册",
projectOnTime: "20/22",
projectOnTimeRate: "91%",
reportQuality: "合格",
complaintCount: 0,
penaltyCount: 0,
infoLevel: "一级",
infoLevelCode: "level1",
scoreGrade: "A级",
evalCount: 22,
reportQualityDesc: "近6个月无不合格报告",
penaltyDesc: "无不良记录",
evalCountDesc: "累计安全评价",
},
5: {
id: 5,
enterpriseName: "重庆渝安风险评估中心",
creditCode: "91500107MA******",
industry: "石油加工",
district: "渝中区",
accountSource: "机构注册",
projectOnTime: "6/9",
projectOnTimeRate: "67%",
reportQuality: "合格",
complaintCount: 0,
penaltyCount: 0,
infoLevel: "二级",
infoLevelCode: "level2",
scoreGrade: "B级",
evalCount: 9,
reportQualityDesc: "近6个月无不合格报告",
penaltyDesc: "无不良记录",
evalCountDesc: "累计安全评价",
},
};
const ENTERPRISE_PORTRAIT_LIST = Object.values(ENTERPRISE_PORTRAIT_DETAILS).map((item) => ({
id: item.id,
enterpriseName: item.enterpriseName,
district: item.district,
accountSource: item.accountSource,
projectOnTime: item.projectOnTime,
reportQuality: item.reportQuality,
complaintCount: item.complaintCount,
penaltyCount: item.penaltyCount,
infoLevel: item.infoLevel,
infoLevelCode: item.infoLevelCode,
}));
function paginate(list, params = {}) {
const pageIndex = Number(params.pageIndex ?? params.current ?? 1);
const pageSize = Number(params.pageSize ?? params.size ?? 10);
const start = (pageIndex - 1) * pageSize;
return {
success: true,
data: list.slice(start, start + pageSize),
totalCount: list.length,
};
}
function includesKeyword(value, keyword) {
if (!keyword) return true;
return String(value || "").includes(String(keyword).trim());
}
export function queryEvaluatorPage(params = {}) {
let list = [...EVALUATOR_LIST];
if (params.evaluatorName) {
list = list.filter((item) => includesKeyword(item.evaluatorName, params.evaluatorName));
}
if (params.orgName) {
list = list.filter((item) => includesKeyword(item.orgName, params.orgName));
}
if (params.employmentStatus !== undefined && params.employmentStatus !== null && params.employmentStatus !== "") {
list = list.filter((item) => item.employmentStatus === Number(params.employmentStatus));
}
if (params.registerEngineerFlag !== undefined && params.registerEngineerFlag !== null && params.registerEngineerFlag !== "") {
list = list.filter((item) => item.registerEngineerFlag === Number(params.registerEngineerFlag));
}
if (params.status) {
list = list.filter((item) => item.status === params.status);
}
return Promise.resolve(paginate(list, params));
}
export function getEvaluatorDetail(id) {
const detail = EVALUATOR_DETAILS[id];
return Promise.resolve({
success: !!detail,
data: detail || null,
});
}
export function queryEnterprisePortraitPage(params = {}) {
let list = [...ENTERPRISE_PORTRAIT_LIST];
if (params.enterpriseName) {
list = list.filter((item) => includesKeyword(item.enterpriseName, params.enterpriseName));
}
if (params.district) {
list = list.filter((item) => item.district === params.district);
}
if (params.accountSource) {
list = list.filter((item) => item.accountSource === params.accountSource);
}
return Promise.resolve(paginate(list, params));
}
export function getEnterprisePortraitDetail(id) {
const detail = ENTERPRISE_PORTRAIT_DETAILS[id];
return Promise.resolve({
success: !!detail,
data: detail || null,
});
}
export const ACCOUNT_SOURCE_OPTIONS = [
{ label: "企业注册", value: "企业注册" },
{ label: "机构注册", value: "机构注册" },
{ label: "监管注册", value: "监管注册" },
];

View File

@ -0,0 +1,363 @@
/** 监管端 - 机构账号 / 已备案机构 mock 数据 */
import { FILING_TYPE_OPTIONS } from "~/enumerate/enterpriseOptions";
function paginate(list, params = {}) {
const pageIndex = Number(params.pageIndex ?? params.current ?? 1);
const pageSize = Number(params.pageSize ?? params.size ?? 10);
const start = (pageIndex - 1) * pageSize;
return {
success: true,
data: list.slice(start, start + pageSize),
totalCount: list.length,
};
}
function includesKeyword(value, keyword) {
if (!keyword) return true;
return String(value || "").includes(String(keyword).trim());
}
function inDateRange(dateStr, start, end) {
if (!dateStr) return false;
if (start && dateStr < start) return false;
if (end && dateStr > end) return false;
return true;
}
export const ORG_ACCOUNT_SOURCE_OPTIONS = [
{ label: "机构注册", value: "机构注册" },
{ label: "监管注册", value: "监管注册" },
];
export const ORG_ACCOUNT_STATUS_OPTIONS = [
{ label: "启用", value: 1 },
{ label: "禁用", value: 0 },
];
const ORG_ACCOUNT_DETAILS = {
1: {
id: 1,
orgName: "重庆安评技术研究院有限公司",
creditCode: "91500112MA7******",
district: "渝北区",
industries: ["化学原料/危化品生产、储存、经营、医药化工"],
businessAddress: "重庆市渝北区龙山街道100号",
enterpriseStatus: "正常",
longitude: "106.532",
latitude: "29.612",
principalName: "刘强",
principalPhone: "138****5678",
legalPerson: "刘强",
legalPhone: "023-6789****",
enterpriseScale: "中型",
aboveScaleFlag: 0,
accountSource: "机构注册",
enableStatus: 1,
openTime: "2025-09-01",
},
2: {
id: 2,
orgName: "重庆恒安安全评价有限公司",
creditCode: "91500105MA6******",
district: "南岸区",
industries: ["石油天然气开采、油气管道、石油加工"],
businessAddress: "重庆市南岸区南坪西路200号",
enterpriseStatus: "正常",
longitude: "106.571",
latitude: "29.523",
principalName: "王芳",
principalPhone: "139****2345",
legalPerson: "王芳",
legalPhone: "023-6288****",
enterpriseScale: "小型",
aboveScaleFlag: 0,
accountSource: "监管注册",
enableStatus: 1,
openTime: "2025-10-15",
},
3: {
id: 3,
orgName: "重庆渝安风险评估中心",
creditCode: "91500107MA5******",
district: "九龙坡区",
industries: ["建筑施工、建材"],
businessAddress: "重庆市九龙坡区石桥铺300号",
enterpriseStatus: "正常",
longitude: "106.480",
latitude: "29.502",
principalName: "陈明",
principalPhone: "137****8899",
legalPerson: "陈明",
legalPhone: "023-6866****",
enterpriseScale: "微型",
aboveScaleFlag: 0,
accountSource: "机构注册",
enableStatus: 0,
openTime: "2025-06-20",
},
};
let orgAccountStore = Object.values(ORG_ACCOUNT_DETAILS).map((item) => ({
id: item.id,
orgName: item.orgName,
district: item.district,
accountSource: item.accountSource,
enableStatus: item.enableStatus,
openTime: item.openTime,
}));
const REGISTERED_ORG_DETAILS = {
1: {
id: 1,
orgName: "重庆安评技术研究院有限公司",
registerAddress: "渝北区龙山街道100号",
serviceEnterpriseCount: 12,
evalProjectCount: 45,
filingType: "审核备案",
filingStatus: "filed",
filingStatusLabel: "已备案",
filingInfo: {
filingDistrict: "重庆市沙坪坝区",
filingUnit: "重庆市安全生产科学研究有限公司",
filingUnitType: "本地单位",
registerAddress: "重庆市沙坪坝区大学城东路20号重庆科技学院第39栋西南角实习用房三楼",
officeAddress: "重庆市沙坪坝区大学城东路20号重庆科技学院第39栋西南角实习用房三楼",
qualCertNo: "API-2026-001",
creditCode: "91500112MA7******",
legalPersonPhone: "刘强 / 138****5678",
infoUrl: "www.cqap.com",
fixedAssetTotal: "1200",
contactPhone: "陈芳 / 139****9012",
archiveArea: "80 ㎡",
workplaceArea: "1200 ㎡",
fullTimeEvaluatorCount: "25 人",
registerEngineerCount: "12 人",
intro: "重庆安全生产科学研究有限公司成立于2015年是一家专业从事安全评价、安全咨询、安全技术开发与转让的综合性安全技术服务机构。",
attachments: ["资质申请书.pdf", "营业执照副本.pdf", "社保缴纳证明.pdf"],
},
materialGroups: [
{
id: 1,
scopeName: "化工",
expireDate: "2029-01-14",
materials: [
{ id: 11, name: "化工领域安全评价机构资质申请书", required: true, status: "uploaded" },
{ id: 12, name: "化工领域技术人员资质证书", required: true, status: "uploaded" },
{ id: 13, name: "化工领域业绩证明材料", required: false, status: "uploaded" },
],
},
{
id: 2,
scopeName: "矿山",
expireDate: "2028-06-30",
materials: [
{ id: 21, name: "矿山领域安全评价机构资质申请书", required: true, status: "uploaded" },
{ id: 22, name: "矿山领域技术人员资质证书", required: true, status: "uploaded" },
{ id: 23, name: "矿山领域安全管理制度文件", required: true, status: "pending" },
{ id: 24, name: "矿山领域业绩证明材料", required: false, status: "uploaded" },
],
},
{
id: 3,
scopeName: "建筑施工",
expireDate: "2026-05-31",
expireWarning: true,
materials: [
{ id: 31, name: "建筑施工领域安全评价机构资质申请书", required: true, status: "uploaded" },
{ id: 32, name: "建筑施工领域技术人员资质证书", required: true, status: "uploaded" },
{ id: 33, name: "建筑施工领域设备清单及检定报告", required: true, status: "pending" },
],
},
],
personnel: [
{ id: 1, name: "张建国", gender: "男", personType: "专职评价师", qualScope: "化工", education: "本科", registerEngineer: 1 },
{ id: 2, name: "李明华", gender: "男", personType: "专职评价师", qualScope: "矿山", education: "硕士", registerEngineer: 1 },
{ id: 3, name: "王丽萍", gender: "女", personType: "基础人员", qualScope: "建筑施工", education: "本科", registerEngineer: 0 },
{ id: 4, name: "陈志强", gender: "男", personType: "专职评价师", qualScope: "化工", education: "本科", registerEngineer: 1 },
{ id: 5, name: "赵磊", gender: "男", personType: "管理人员", qualScope: "—", education: "专科", registerEngineer: 0 },
],
evalProjects: [
{ id: 1, projectType: "安全预评价", projectNo: "YP-2026-0012", projectName: "华安化工园区安全预评价项目", projectStatus: "待现场勘查" },
{ id: 2, projectType: "安全验收评价", projectNo: "YS-2026-0008", projectName: "鼎信建设安全验收评价", projectStatus: "过程管控" },
{ id: 3, projectType: "安全现状评价", projectNo: "XZ-2026-0003", projectName: "安环检测年度安全现状评价", projectStatus: "归档" },
],
},
2: {
id: 2,
orgName: "重庆恒安安全评价有限公司",
registerAddress: "南岸区南坪西路200号",
serviceEnterpriseCount: 8,
evalProjectCount: 32,
filingType: "确认备案",
filingStatus: "filing",
filingStatusLabel: "备案中",
filingInfo: {
filingDistrict: "重庆市南岸区",
filingUnit: "重庆恒安安全评价有限公司",
filingUnitType: "本地单位",
registerAddress: "重庆市南岸区南坪西路200号",
officeAddress: "重庆市南岸区南坪西路200号",
qualCertNo: "API-2026-002",
creditCode: "91500105MA6******",
legalPersonPhone: "王芳 / 139****2345",
infoUrl: "www.ha-anping.com",
fixedAssetTotal: "800",
contactPhone: "李强 / 136****5566",
archiveArea: "50 ㎡",
workplaceArea: "600 ㎡",
fullTimeEvaluatorCount: "15 人",
registerEngineerCount: "6 人",
intro: "重庆恒安安全评价有限公司专注于矿山、化工行业安全评价服务。",
attachments: ["资质申请书.pdf", "营业执照副本.pdf"],
},
materialGroups: [
{
id: 1,
scopeName: "矿山",
expireDate: "2028-12-31",
materials: [
{ id: 11, name: "矿山领域安全评价机构资质申请书", required: true, status: "uploaded" },
{ id: 12, name: "矿山领域技术人员资质证书", required: true, status: "uploaded" },
],
},
],
personnel: [
{ id: 1, name: "李明华", gender: "男", personType: "专职评价师", qualScope: "矿山", education: "硕士", registerEngineer: 1 },
],
evalProjects: [
{ id: 1, projectType: "安全现状评价", projectNo: "XZ-2026-0010", projectName: "恒安矿山安全现状评价", projectStatus: "过程管控" },
],
},
};
const REGISTERED_ORG_LIST = Object.values(REGISTERED_ORG_DETAILS).map((item) => ({
id: item.id,
orgName: item.orgName,
registerAddress: item.registerAddress,
serviceEnterpriseCount: item.serviceEnterpriseCount,
evalProjectCount: item.evalProjectCount,
filingType: item.filingType,
filingStatus: item.filingStatus,
filingStatusLabel: item.filingStatusLabel,
}));
export const FILING_STATUS_OPTIONS = [
{ label: "待备案", value: "pending" },
{ label: "备案中", value: "filing" },
{ label: "已备案", value: "filed" },
];
export function queryOrgAccountPage(params = {}) {
let list = [...orgAccountStore];
if (params.orgName) {
list = list.filter((item) => includesKeyword(item.orgName, params.orgName));
}
if (params.district) {
list = list.filter((item) => item.district === params.district);
}
if (params.enableStatus !== undefined && params.enableStatus !== null && params.enableStatus !== "") {
list = list.filter((item) => item.enableStatus === Number(params.enableStatus));
}
if (params.accountSource) {
list = list.filter((item) => item.accountSource === params.accountSource);
}
const range = params.openTimeRange;
if (Array.isArray(range) && range.length === 2 && range[0] && range[1]) {
const start = range[0].format ? range[0].format("YYYY-MM-DD") : range[0];
const end = range[1].format ? range[1].format("YYYY-MM-DD") : range[1];
list = list.filter((item) => inDateRange(item.openTime, start, end));
}
return Promise.resolve(paginate(list, params));
}
export function getOrgAccountDetail(id) {
const detail = ORG_ACCOUNT_DETAILS[id];
if (!detail) {
return Promise.resolve({ success: false, data: null });
}
const current = orgAccountStore.find((item) => item.id === Number(id));
return Promise.resolve({
success: true,
data: { ...detail, enableStatus: current?.enableStatus ?? detail.enableStatus },
});
}
export function updateOrgAccount(id, values) {
const numId = Number(id);
const detail = ORG_ACCOUNT_DETAILS[numId];
if (!detail) {
return Promise.resolve({ success: false, message: "记录不存在" });
}
Object.assign(detail, values);
const row = orgAccountStore.find((item) => item.id === numId);
if (row) {
row.orgName = values.orgName ?? row.orgName;
row.district = values.district ?? row.district;
}
return Promise.resolve({ success: true, data: detail });
}
export function toggleOrgAccountStatus(id) {
const numId = Number(id);
const row = orgAccountStore.find((item) => item.id === numId);
if (!row) {
return Promise.resolve({ success: false, message: "记录不存在" });
}
row.enableStatus = row.enableStatus === 1 ? 0 : 1;
if (ORG_ACCOUNT_DETAILS[numId]) {
ORG_ACCOUNT_DETAILS[numId].enableStatus = row.enableStatus;
}
return Promise.resolve({ success: true, data: row });
}
export function deleteOrgAccount(id) {
const numId = Number(id);
orgAccountStore = orgAccountStore.filter((item) => item.id !== numId);
return Promise.resolve({ success: true });
}
export function resetOrgAccountPassword(id) {
return Promise.resolve({
success: true,
message: "密码已重置为初始密码 Ap@123456",
data: { id: Number(id), initialPassword: "Ap@123456" },
});
}
export function queryRegisteredOrgPage(params = {}) {
let list = [...REGISTERED_ORG_LIST];
if (params.orgName) {
list = list.filter((item) => includesKeyword(item.orgName, params.orgName));
}
if (params.registerAddress) {
list = list.filter((item) => includesKeyword(item.registerAddress, params.registerAddress));
}
if (params.filingType) {
list = list.filter((item) => item.filingType === params.filingType);
}
if (params.filingStatus) {
list = list.filter((item) => item.filingStatus === params.filingStatus);
}
return Promise.resolve(paginate(list, params));
}
export function getRegisteredOrgDetail(id) {
const detail = REGISTERED_ORG_DETAILS[id];
return Promise.resolve({ success: !!detail, data: detail || null });
}
/** 详情页 Mock 扩展数据(申请清单材料、人员信息,后端暂无接口) */
export function getRegisteredOrgDetailMockExtras(id) {
const detail = REGISTERED_ORG_DETAILS[id] || REGISTERED_ORG_DETAILS[1];
const personnel = (detail?.personnel || []).map((item) => ({
...item,
position: item.position || item.personType,
}));
return Promise.resolve({
materialGroups: detail?.materialGroups || [],
personnel,
});
}
export { FILING_TYPE_OPTIONS };

View File

@ -0,0 +1,198 @@
import React from "react";
import Upload from "zy-react-library/components/Upload";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import {
CHONGQING_DISTRICTS,
ENTERPRISE_SCALE_OPTIONS,
ENTERPRISE_STATUS_OPTIONS,
FILING_RECORD_STATUS_OPTIONS,
FILING_TYPE_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { formSelectField } from "~/utils/enterpriseForm";
import {
creditCodeRule,
latitudeRule,
longitudeRule,
nonNegativeIntegerRule,
phoneRule,
positiveNumberRule,
urlRule,
} from "~/utils/validators";
const numberFieldProps = { style: { width: "100%" } };
/** 机构信息管理 / 备案信息详情 共用表单字段配置 */
export function buildOrgInfoFormOptions() {
return [
{
name: "orgName",
label: "生产经营单位名称",
rules: [{ required: true, message: "请输入生产经营单位名称" }],
},
{
name: "creditCode",
label: "统一社会信用代码",
rules: [creditCodeRule(true)],
},
{
name: "safetyIndustryCategory",
label: "安全生产监管行业类别",
rules: [{ required: true, message: "请输入安全生产监管行业类别" }],
},
{
...formSelectField("regionCountyName", "属地", CHONGQING_DISTRICTS, {
rules: [{ required: true, message: "请选择属地" }],
colProps: { span: 12 },
}),
},
{
name: "regionStreetName",
label: "所属镇、街道",
rules: [{ required: true, message: "请输入所属镇街道" }],
},
{
name: "regionCommunityName",
label: "属村(社区)",
required: false,
},
{
name: "longitude",
label: "所在地坐标经度",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: -180, max: 180, precision: 6 },
rules: [longitudeRule(false)],
},
{
name: "latitude",
label: "所在地坐标纬度",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: -90, max: 90, precision: 6 },
rules: [latitudeRule(false)],
},
{
name: "registerAddress",
label: "注册地址",
rules: [{ required: true, message: "请输入注册地址" }],
render: FORM_ITEM_RENDER_ENUM.TEXTAREA,
},
{
name: "businessAddress",
label: "经营地址",
rules: [{ required: true, message: "请输入经营地址" }],
render: FORM_ITEM_RENDER_ENUM.TEXTAREA,
},
{
name: "ownershipType",
label: "归属类型",
required: false,
},
{
name: "gbIndustryCode",
label: "国民经济行业分类(GB/T4754-2017)",
required: false,
},
{
name: "legalRepresentative",
label: "法定代表人",
rules: [{ required: true, message: "请输入法定代表人" }],
},
{
name: "legalRepPhone",
label: "法定代表人联系电话",
rules: [phoneRule("法定代表人联系电话", false)],
},
{
name: "principal",
label: "主要负责人",
rules: [{ required: true, message: "请输入主要负责人" }],
},
{
name: "principalPhone",
label: "主要负责人联系电话",
rules: [phoneRule("主要负责人联系电话", true)],
},
{
name: "safetyDeptHead",
label: "安全管理部门负责人",
required: false,
},
{
name: "safetyDeptPhone",
label: "安全管理部门负责人联系电话",
rules: [phoneRule("安全管理部门负责人联系电话", false)],
},
{
name: "safetyVp",
label: "主管安全副总",
required: false,
},
{
name: "safetyVpPhone",
label: "主管安全副总联系电话",
rules: [phoneRule("主管安全副总联系电话", false)],
},
{
name: "productionDate",
label: "投产日期",
required: false,
render: FORM_ITEM_RENDER_ENUM.DATE,
},
{
name: "businessStatus",
label: "企业经营状态",
required: false,
},
{
name: "disclosureUrl",
label: "信息公开网址",
useConstraints: false,
rules: [urlRule("信息公开网址", false)],
},
{
name: "workplaceArea",
label: "工作场所建筑面积",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 2 },
rules: [positiveNumberRule("工作场所建筑面积", false)],
},
{
name: "archiveRoomArea",
label: "档案室面积",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 2 },
rules: [positiveNumberRule("档案室面积", false)],
},
{
name: "fullTimeEvaluatorCount",
label: "专职安全评价师数量",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 0 },
rules: [nonNegativeIntegerRule("专职安全评价师数量", false)],
},
{
name: "registeredSafetyEngineerCount",
label: "注册安全工程师数量",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 0 },
rules: [nonNegativeIntegerRule("注册安全工程师数量", false)],
},
formSelectField("enterpriseStatus", "企业状态", ENTERPRISE_STATUS_OPTIONS, { required: false }),
formSelectField("enterpriseScale", "企业规模", ENTERPRISE_SCALE_OPTIONS, { required: false }),
formSelectField("filingType", "备案类型", FILING_TYPE_OPTIONS, { required: false }),
formSelectField("filingRecordStatus", "备案状态", FILING_RECORD_STATUS_OPTIONS, { required: false }),
{
name: "attachments",
label: "上传附件",
required: false,
render: (
<Upload maxCount={5} />
),
},
];
}

View File

@ -3,31 +3,11 @@ import { Button, Form, message, Space } from "antd";
import { useEffect, useState } from "react";
import FormBuilder from "zy-react-library/components/FormBuilder";
import Page from "zy-react-library/components/Page";
import Upload from "zy-react-library/components/Upload";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import { getOrgInfoDetail } from "~/api/enterpriseInfo/orgBootstrap";
import PageHeader from "../components/PageHeader";
import { NS_ORG_INFO } from "~/enumerate/namespace";
import {
CHONGQING_DISTRICTS,
ENTERPRISE_SCALE_OPTIONS,
ENTERPRISE_STATUS_OPTIONS,
FILING_RECORD_STATUS_OPTIONS,
FILING_TYPE_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { formSelectField } from "~/utils/enterpriseForm";
import { mockUploadFileList } from "~/utils/mockUpload";
import {
creditCodeRule,
latitudeRule,
longitudeRule,
nonNegativeIntegerRule,
phoneRule,
positiveNumberRule,
urlRule,
} from "~/utils/validators";
const numberFieldProps = { style: { width: "100%" } };
import { buildOrgInfoFormOptions } from "./formOptions";
function OrgInfoPage(props) {
const [form] = Form.useForm();
@ -37,6 +17,8 @@ function OrgInfoPage(props) {
/** 是否已存在机构数据(有 id 视为已入库,只能修改) */
const [hasExistingData, setHasExistingData] = useState(false);
const formOptions = buildOrgInfoFormOptions();
const loadDetail = async () => {
try {
const res = getOrgInfoDetail();
@ -117,179 +99,6 @@ function OrgInfoPage(props) {
}
};
const formOptions = [
{
name: "orgName",
label: "生产经营单位名称",
rules: [{ required: true, message: "请输入生产经营单位名称" }],
},
{
name: "creditCode",
label: "统一社会信用代码",
rules: [creditCodeRule(true)],
},
{
name: "safetyIndustryCategory",
label: "安全生产监管行业类别",
rules: [{ required: true, message: "请输入安全生产监管行业类别" }],
},
{
...formSelectField("regionCountyName", "属地", CHONGQING_DISTRICTS, {
rules: [{ required: true, message: "请选择属地" }],
colProps: { span: 12 },
}),
},
{
name: "regionStreetName",
label: "所属镇、街道",
rules: [{ required: true, message: "请输入所属镇街道" }],
},
{
name: "regionCommunityName",
label: "属村(社区)",
required: false,
},
{
name: "longitude",
label: "所在地坐标经度",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: -180, max: 180, precision: 6 },
rules: [longitudeRule(false)],
},
{
name: "latitude",
label: "所在地坐标纬度",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: -90, max: 90, precision: 6 },
rules: [latitudeRule(false)],
},
{
name: "registerAddress",
label: "注册地址",
rules: [{ required: true, message: "请输入注册地址" }],
render: FORM_ITEM_RENDER_ENUM.TEXTAREA,
},
{
name: "businessAddress",
label: "经营地址",
rules: [{ required: true, message: "请输入经营地址" }],
render: FORM_ITEM_RENDER_ENUM.TEXTAREA,
},
{
name: "ownershipType",
label: "归属类型",
required: false,
},
{
name: "gbIndustryCode",
label: "国民经济行业分类(GB/T4754-2017)",
required: false,
},
{
name: "legalRepresentative",
label: "法定代表人",
rules: [{ required: true, message: "请输入法定代表人" }],
},
{
name: "legalRepPhone",
label: "法定代表人联系电话",
rules: [phoneRule("法定代表人联系电话", false)],
},
{
name: "principal",
label: "主要负责人",
rules: [{ required: true, message: "请输入主要负责人" }],
},
{
name: "principalPhone",
label: "主要负责人联系电话",
rules: [phoneRule("主要负责人联系电话", true)],
},
{
name: "safetyDeptHead",
label: "安全管理部门负责人",
required: false,
},
{
name: "safetyDeptPhone",
label: "安全管理部门负责人联系电话",
rules: [phoneRule("安全管理部门负责人联系电话", false)],
},
{
name: "safetyVp",
label: "主管安全副总",
required: false,
},
{
name: "safetyVpPhone",
label: "主管安全副总联系电话",
rules: [phoneRule("主管安全副总联系电话", false)],
},
{
name: "productionDate",
label: "投产日期",
required: false,
render: FORM_ITEM_RENDER_ENUM.DATE,
},
{
name: "businessStatus",
label: "企业经营状态",
required: false,
},
{
name: "disclosureUrl",
label: "信息公开网址",
useConstraints: false,
rules: [urlRule("信息公开网址", false)],
},
{
name: "workplaceArea",
label: "工作场所建筑面积",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 2 },
rules: [positiveNumberRule("工作场所建筑面积", false)],
},
{
name: "archiveRoomArea",
label: "档案室面积",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 2 },
rules: [positiveNumberRule("档案室面积", false)],
},
{
name: "fullTimeEvaluatorCount",
label: "专职安全评价师数量",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 0 },
rules: [nonNegativeIntegerRule("专职安全评价师数量", false)],
},
{
name: "registeredSafetyEngineerCount",
label: "注册安全工程师数量",
render: FORM_ITEM_RENDER_ENUM.INPUT_NUMBER,
useConstraints: false,
componentProps: { ...numberFieldProps, min: 0, precision: 0 },
rules: [nonNegativeIntegerRule("注册安全工程师数量", false)],
},
formSelectField("enterpriseStatus", "企业状态", ENTERPRISE_STATUS_OPTIONS, { required: false }),
formSelectField("enterpriseScale", "企业规模", ENTERPRISE_SCALE_OPTIONS, { required: false }),
formSelectField("filingType", "备案类型", FILING_TYPE_OPTIONS, { required: false }),
formSelectField("filingRecordStatus", "备案状态", FILING_RECORD_STATUS_OPTIONS, { required: false }),
{
name: "attachments",
label: "上传附件",
required: false,
render: (
<Upload maxCount={5} />
),
},
];
return (
<Page isShowAllAction={false}>
<PageHeader

View File

@ -1,5 +1,5 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, DatePicker, Descriptions, Form, Input, message, Modal, Row, Col, Select, Space } from "antd";
import { Button, DatePicker, Form, Input, message, Modal, Row, Col, Select, Space } from "antd";
import { useEffect, useRef, useState } from "react";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import Page from "zy-react-library/components/Page";
@ -23,9 +23,7 @@ import { toDayjs } from "~/utils/dateFormat";
import { formSelectField } from "~/utils/enterpriseForm";
import { idCardRule, mobileRule } from "~/utils/validators";
import PageHeader from "../../components/PageHeader";
const GENDER_MAP = { 1: "男", 2: "女" };
const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" };
import StaffViewModal from "../StaffViewModal";
function mapPositionOptions(list = []) {
return list.map((p) => ({
@ -501,80 +499,4 @@ function StaffFormModal({
);
}
function StaffViewModal({ open, currentId, requestDetails, onCancel }) {
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
useEffect(() => {
if (!open || !currentId) {
setInfo({});
return;
}
let cancelled = false;
setDetailLoading(true);
safeRequest(requestDetails, { id: currentId }).then((res) => {
if (!cancelled) {
setInfo(res?.data || {});
}
}).finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
});
return () => {
cancelled = true;
};
}, [open, currentId]);
return (
<Modal
open={open}
destroyOnClose
title="查看"
width={900}
loading={detailLoading}
cancelText="返回"
okButtonProps={{ style: { display: "none" } }}
onCancel={onCancel}
>
<Descriptions bordered column={2} labelStyle={{ width: 120 }}>
<Descriptions.Item label="姓名">{info.staffName}</Descriptions.Item>
<Descriptions.Item label="性别">{GENDER_MAP[info.gender]}</Descriptions.Item>
<Descriptions.Item label="出生日期">{info.birthDate}</Descriptions.Item>
<Descriptions.Item label="账号">{info.account}</Descriptions.Item>
<Descriptions.Item label="部门">{info.deptName}</Descriptions.Item>
<Descriptions.Item label="岗位">{info.positionName}</Descriptions.Item>
<Descriptions.Item label="人员类型">{info.personType || "基础人员"}</Descriptions.Item>
<Descriptions.Item label="资质范围">{info.qualScope || "-"}</Descriptions.Item>
<Descriptions.Item label="职业等级">{info.professionalLevel || "-"}</Descriptions.Item>
<Descriptions.Item label="证书编号">{info.evaluatorCertNo || "-"}</Descriptions.Item>
<Descriptions.Item label="学历类型">{info.educationType || "-"}</Descriptions.Item>
<Descriptions.Item label="学历层次">{info.educationLevel || "-"}</Descriptions.Item>
<Descriptions.Item label="职称">{info.titleName || "-"}</Descriptions.Item>
<Descriptions.Item label="是否注册安全工程师">
{REGISTER_ENGINEER_MAP[info.registerEngineerFlag] ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="身份证号">{info.idCardNo}</Descriptions.Item>
<Descriptions.Item label="学历">{info.education || "-"}</Descriptions.Item>
<Descriptions.Item label="现住地址" span={2}>{info.homeAddress}</Descriptions.Item>
<Descriptions.Item label="办公地址" span={2}>{info.officeAddress}</Descriptions.Item>
<Descriptions.Item label="毕业院校">{info.graduateSchool}</Descriptions.Item>
<Descriptions.Item label="专业">{info.major}</Descriptions.Item>
<Descriptions.Item label="出版学术专著、专利、获奖、发表学术论文等" span={2}>
{info.publications || "-"}
</Descriptions.Item>
<Descriptions.Item label="自我申报的专业能力及认定方式" span={2}>
{info.abilityDeclaration || "-"}
</Descriptions.Item>
<Descriptions.Item label="主要学习工作经历" span={2}>
{info.workExperience || "-"}
</Descriptions.Item>
<Descriptions.Item label="申报专业能力证明材料" span={2}>
{(info.proofMaterials || []).map((file) => file.name || file.fileName).join("、") || "-"}
</Descriptions.Item>
</Descriptions>
</Modal>
);
}
export default Connect([NS_STAFF_INFO, NS_ORG_DEPARTMENT, NS_ORG_POSITION], true)(PersonnelInfoPage);

View File

@ -0,0 +1,220 @@
import { Button, Descriptions, Modal, Space, Table } from "antd";
import { useEffect, useState } from "react";
import FilePreviewModal, { FilePreviewContent } from "~/components/FilePreviewModal";
import { fetchStaffCertDetail, fetchStaffCertListByPersonnelId } from "~/api/staffCertificate";
import { safeRequest } from "~/utils";
const GENDER_MAP = { 1: "男", 2: "女" };
const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" };
export default function StaffViewModal({
open,
currentId,
requestDetails,
requestCertList = fetchStaffCertListByPersonnelId,
requestCertDetails = fetchStaffCertDetail,
onCancel,
}) {
const [info, setInfo] = useState({});
const [certificates, setCertificates] = useState([]);
const [detailLoading, setDetailLoading] = useState(false);
const [attachmentPreview, setAttachmentPreview] = useState(null);
const [certPreview, setCertPreview] = useState(null);
const [certPreviewLoading, setCertPreviewLoading] = useState(false);
const [certPreviewInfo, setCertPreviewInfo] = useState(null);
useEffect(() => {
if (!open || !currentId) {
setInfo({});
setCertificates([]);
return;
}
let cancelled = false;
setDetailLoading(true);
Promise.all([
safeRequest(requestDetails, { id: currentId }),
typeof requestCertList === "function"
? Promise.resolve(requestCertList(currentId)).catch((err) => {
console.warn("[StaffViewModal] load certificates failed:", err);
return [];
})
: Promise.resolve([]),
]).then(([detailRes, certList]) => {
if (!cancelled) {
setInfo(detailRes?.data || {});
setCertificates(Array.isArray(certList) ? certList : []);
}
}).finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
});
return () => {
cancelled = true;
};
}, [open, currentId, requestDetails, requestCertList]);
const openCertPreview = async (record) => {
setCertPreview(record);
setCertPreviewInfo(null);
if (typeof requestCertDetails !== "function") {
setCertPreviewInfo(record);
return;
}
setCertPreviewLoading(true);
try {
const res = await safeRequest(requestCertDetails, { id: record.id });
setCertPreviewInfo(res?.data || record);
}
finally {
setCertPreviewLoading(false);
}
};
const proofFiles = info.proofMaterials || [];
const certPreviewFiles = certPreviewInfo?.certImgs?.length
? certPreviewInfo.certImgs
: certPreviewInfo?.certImgFiles || [];
const certPreviewUrl = certPreviewFiles[0]?.url;
const certPreviewName = certPreviewFiles[0]?.fileName || certPreviewFiles[0]?.name || certPreviewInfo?.certName;
return (
<>
<Modal
open={open}
destroyOnClose
title="查看"
width={900}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={onCancel}
>
<Descriptions bordered column={2} labelStyle={{ width: 120 }}>
<Descriptions.Item label="姓名">{info.staffName}</Descriptions.Item>
<Descriptions.Item label="性别">{GENDER_MAP[info.gender]}</Descriptions.Item>
<Descriptions.Item label="出生日期">{info.birthDate}</Descriptions.Item>
<Descriptions.Item label="账号">{info.account}</Descriptions.Item>
<Descriptions.Item label="部门">{info.deptName}</Descriptions.Item>
<Descriptions.Item label="岗位">{info.positionName}</Descriptions.Item>
<Descriptions.Item label="人员类型">{info.personType || "基础人员"}</Descriptions.Item>
<Descriptions.Item label="资质范围">{info.qualScope || "-"}</Descriptions.Item>
<Descriptions.Item label="职业等级">{info.professionalLevel || "-"}</Descriptions.Item>
<Descriptions.Item label="证书编号">{info.evaluatorCertNo || "-"}</Descriptions.Item>
<Descriptions.Item label="学历类型">{info.educationType || "-"}</Descriptions.Item>
<Descriptions.Item label="学历层次">{info.educationLevel || "-"}</Descriptions.Item>
<Descriptions.Item label="职称">{info.titleName || "-"}</Descriptions.Item>
<Descriptions.Item label="是否注册安全工程师">
{REGISTER_ENGINEER_MAP[info.registerEngineerFlag] ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="身份证号">{info.idCardNo}</Descriptions.Item>
<Descriptions.Item label="学历">{info.education || "-"}</Descriptions.Item>
<Descriptions.Item label="现住地址" span={2}>{info.homeAddress}</Descriptions.Item>
<Descriptions.Item label="办公地址" span={2}>{info.officeAddress}</Descriptions.Item>
<Descriptions.Item label="毕业院校">{info.graduateSchool}</Descriptions.Item>
<Descriptions.Item label="专业">{info.major}</Descriptions.Item>
<Descriptions.Item label="出版学术专著、专利、获奖、发表学术论文等" span={2}>
{info.publications || "-"}
</Descriptions.Item>
<Descriptions.Item label="自我申报的专业能力及认定方式" span={2}>
{info.abilityDeclaration || "-"}
</Descriptions.Item>
<Descriptions.Item label="主要学习工作经历" span={2}>
{info.workExperience || "-"}
</Descriptions.Item>
<Descriptions.Item label="申报专业能力证明材料" span={2}>
{proofFiles.length ? (
<Space direction="vertical" size={4}>
{proofFiles.map((file, index) => {
const fileName = file.name || file.fileName || `专业能力证明${index + 1}.pdf`;
return (
<Space key={`${fileName}-${index}`}>
<Button
type="link"
size="small"
style={{ padding: 0 }}
onClick={() => setAttachmentPreview({ url: file.url, fileName })}
>
查看附件
</Button>
<span>{fileName}</span>
</Space>
);
})}
</Space>
) : "-"}
</Descriptions.Item>
</Descriptions>
<div style={{ marginTop: 16, marginBottom: 8, fontWeight: 600 }}>证照信息</div>
<Table
size="small"
bordered
rowKey="id"
pagination={false}
dataSource={certificates}
locale={{ emptyText: "暂无证照信息" }}
columns={[
{ title: "证照名称", dataIndex: "certName" },
{ title: "证书编号", dataIndex: "certNo" },
{ title: "证书类别", dataIndex: "certCategory" },
{ title: "发证机关", dataIndex: "issueOrg" },
{ title: "有效开始日期", dataIndex: "validStartDate", width: 120 },
{ title: "有效结束日期", dataIndex: "validEndDate", width: 120 },
{
title: "操作",
width: 90,
render: (_, record) => (
<Button type="link" size="small" onClick={() => openCertPreview(record)}>查看证书</Button>
),
},
]}
/>
</Modal>
<FilePreviewModal
open={!!attachmentPreview}
title="附件预览"
fileName={attachmentPreview?.fileName}
url={attachmentPreview?.url}
onCancel={() => setAttachmentPreview(null)}
/>
<Modal
open={!!certPreview}
destroyOnClose
title="证书预览"
width={720}
loading={certPreviewLoading}
cancelText="关闭"
okText={certPreviewUrl ? "新窗口打开" : "关闭"}
okButtonProps={{ style: certPreviewUrl ? undefined : { display: "none" } }}
onCancel={() => {
setCertPreview(null);
setCertPreviewInfo(null);
}}
onOk={() => {
if (certPreviewUrl) {
window.open(certPreviewUrl, "_blank");
}
setCertPreview(null);
setCertPreviewInfo(null);
}}
>
{certPreviewInfo && (
<div>
<Descriptions bordered column={1} size="small" style={{ marginBottom: 12 }}>
<Descriptions.Item label="证照名称">{certPreviewInfo.certName}</Descriptions.Item>
<Descriptions.Item label="证书编号">{certPreviewInfo.certNo}</Descriptions.Item>
<Descriptions.Item label="证书类别">{certPreviewInfo.certCategory || "-"}</Descriptions.Item>
<Descriptions.Item label="发证机关">{certPreviewInfo.issueOrg || "-"}</Descriptions.Item>
<Descriptions.Item label="有效开始日期">{certPreviewInfo.validStartDate || "-"}</Descriptions.Item>
<Descriptions.Item label="有效结束日期">{certPreviewInfo.validEndDate || "-"}</Descriptions.Item>
</Descriptions>
<FilePreviewContent url={certPreviewUrl} fileName={certPreviewName} />
</div>
)}
</Modal>
</>
);
}

View File

@ -16,6 +16,8 @@ import {
ApartmentOutlined,
SwapOutlined,
LogoutOutlined,
FileTextOutlined,
KeyOutlined,
} from "@ant-design/icons";
const menuItems = [
@ -29,6 +31,33 @@ const menuItems = [
label: "Test2 测试",
icon: <ExperimentOutlined />,
},
{
key: "/certificate/container/Supervision/BasicInfo",
label: "基础信息管理",
icon: <FileTextOutlined />,
children: [
{
key: "/certificate/container/Supervision/BasicInfo/OrgAccount",
label: "机构账号管理",
icon: <KeyOutlined />,
},
{
key: "/certificate/container/Supervision/BasicInfo/RegisteredOrg/List",
label: "已备案机构管理",
icon: <FileProtectOutlined />,
},
{
key: "/certificate/container/Supervision/BasicInfo/EvaluatorInfo",
label: "评价师信息管理",
icon: <UserOutlined />,
},
{
key: "/certificate/container/Supervision/BasicInfo/EnterprisePortrait",
label: "企业画像管理",
icon: <BarChartOutlined />,
},
],
},
{
key: "/certificate/container/Supervision/EnterpriseLicense",
label: "企业证照",

View File

@ -0,0 +1,205 @@
import { Button, Card, Col, Descriptions, Form, Modal, Row, Statistic, Tag } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import useTable from "zy-react-library/hooks/useTable";
import { CHONGQING_DISTRICTS } from "~/enumerate/enterpriseOptions";
import {
ACCOUNT_SOURCE_OPTIONS,
getEnterprisePortraitDetail,
queryEnterprisePortraitPage,
} from "~/mock/regulator/basicInfo";
import PageHeader from "../../../EnterpriseInfo/components/PageHeader";
const INFO_LEVEL_COLOR = {
level1: "success",
level2: "processing",
level3: "warning",
};
const SCORE_RING_COLOR = {
"A级": "#52c41a",
"B级": "#1677ff",
"C级": "#faad14",
};
function EnterprisePortraitPage() {
const [searchForm] = Form.useForm();
const [viewOpen, setViewOpen] = useState(false);
const [detail, setDetail] = useState(null);
const [detailLoading, setDetailLoading] = useState(false);
const { tableProps, getData } = useTable(queryEnterprisePortraitPage, { form: searchForm });
useEffect(() => {
getData();
}, []);
const openProfile = async (id) => {
setViewOpen(true);
setDetailLoading(true);
const res = await getEnterprisePortraitDetail(id);
setDetail(res?.data || null);
setDetailLoading(false);
};
const closeProfile = () => {
setViewOpen(false);
setDetail(null);
};
return (
<Page isShowAllAction={false}>
<PageHeader
title="企业画像管理"
subTitle="监管端查看企业安全能力画像与综合评级(当前为 Mock 数据)。"
/>
<Search
form={searchForm}
options={[
{ name: "enterpriseName", label: "企业名称", placeholder: "关键字搜索" },
{
name: "district",
label: "属地",
render: FORM_ITEM_RENDER_ENUM.SELECT,
options: CHONGQING_DISTRICTS,
},
{
name: "accountSource",
label: "开户来源",
render: FORM_ITEM_RENDER_ENUM.SELECT,
options: ACCOUNT_SOURCE_OPTIONS,
},
]}
onFinish={getData}
/>
<Table
{...tableProps}
columns={[
{ title: "企业名称", dataIndex: "enterpriseName", ellipsis: true },
{ title: "项目按时完成", dataIndex: "projectOnTime", width: 120 },
{ title: "报告质量", dataIndex: "reportQuality", width: 100 },
{ title: "投诉记录", dataIndex: "complaintCount", width: 90 },
{ title: "行政处罚", dataIndex: "penaltyCount", width: 90 },
{
title: "信息等级",
width: 100,
render: (_, record) => (
<Tag color={INFO_LEVEL_COLOR[record.infoLevelCode] || "default"}>
{record.infoLevel}
</Tag>
),
},
{
title: "操作",
width: 100,
render: (_, record) => (
<Button type="link" onClick={() => openProfile(record.id)}>查看画像</Button>
),
},
]}
/>
<Modal
open={viewOpen}
destroyOnClose
title="企业安全能力画像"
width={800}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={closeProfile}
>
{detail && (
<>
<div style={{ display: "flex", gap: 24, marginBottom: 24, alignItems: "center" }}>
<div
style={{
width: 80,
height: 80,
borderRadius: "50%",
border: `4px solid ${SCORE_RING_COLOR[detail.scoreGrade] || "#1677ff"}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 20,
fontWeight: 700,
color: SCORE_RING_COLOR[detail.scoreGrade] || "#1677ff",
flexShrink: 0,
}}
>
{detail.scoreGrade}
</div>
<div>
<h3 style={{ margin: "0 0 4px" }}>{detail.enterpriseName}</h3>
<p style={{ margin: 0, color: "rgba(0,0,0,0.45)", fontSize: 13 }}>
统一社会信用代码
{detail.creditCode}
{" "}
|
{detail.industry}
{" "}
|
{detail.district}
</p>
</div>
</div>
<Row gutter={16}>
<Col span={6}>
<Card size="small">
<Statistic title="项目按时完成率" value={detail.projectOnTimeRate} />
<div style={{ marginTop: 8, color: "#52c41a", fontSize: 12 }}>
{" "}
{detail.projectOnTime}
</div>
</Card>
</Col>
<Col span={6}>
<Card size="small">
<Statistic title="报告质量" value={detail.reportQuality} />
<div style={{ marginTop: 8, color: "rgba(0,0,0,0.45)", fontSize: 12 }}>
{detail.reportQualityDesc}
</div>
</Card>
</Col>
<Col span={6}>
<Card size="small">
<Statistic
title="投诉/处罚"
value={detail.complaintCount + detail.penaltyCount}
/>
<div style={{ marginTop: 8, color: "rgba(0,0,0,0.45)", fontSize: 12 }}>
{detail.penaltyDesc}
</div>
</Card>
</Col>
<Col span={6}>
<Card size="small">
<Statistic title="评价次数" value={detail.evalCount} />
<div style={{ marginTop: 8, color: "rgba(0,0,0,0.45)", fontSize: 12 }}>
{detail.evalCountDesc}
</div>
</Card>
</Col>
</Row>
<Descriptions bordered column={2} size="small" style={{ marginTop: 24 }}>
<Descriptions.Item label="信息等级">
<Tag color={INFO_LEVEL_COLOR[detail.infoLevelCode] || "default"}>
{detail.infoLevel}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="开户来源">{detail.accountSource}</Descriptions.Item>
<Descriptions.Item label="投诉记录">{detail.complaintCount}</Descriptions.Item>
<Descriptions.Item label="行政处罚">{detail.penaltyCount}</Descriptions.Item>
</Descriptions>
</>
)}
</Modal>
</Page>
);
}
export default EnterprisePortraitPage;

View File

@ -0,0 +1,208 @@
import { Button, Descriptions, Form, Modal, Table, Tag, Tooltip } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import TableList from "zy-react-library/components/Table";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import useTable from "zy-react-library/hooks/useTable";
import { getEvaluatorDetail, queryEvaluatorPage } from "~/mock/regulator/basicInfo";
import PageHeader from "../../../EnterpriseInfo/components/PageHeader";
const YES_NO_OPTIONS = [
{ label: "是", value: 1 },
{ label: "否", value: 0 },
];
const STATUS_OPTIONS = [
{ label: "正常", value: "normal" },
{ label: "异常", value: "abnormal" },
];
const CERT_STATUS_COLOR = {
normal: "success",
expiring: "warning",
expired: "error",
};
function EvaluatorInfoPage() {
const [searchForm] = Form.useForm();
const [viewOpen, setViewOpen] = useState(false);
const [detail, setDetail] = useState(null);
const [detailLoading, setDetailLoading] = useState(false);
const [certPreview, setCertPreview] = useState(null);
const { tableProps, getData } = useTable(queryEvaluatorPage, { form: searchForm });
useEffect(() => {
getData();
}, []);
const openDetail = async (id) => {
setViewOpen(true);
setDetailLoading(true);
const res = await getEvaluatorDetail(id);
setDetail(res?.data || null);
setDetailLoading(false);
};
const closeDetail = () => {
setViewOpen(false);
setDetail(null);
setCertPreview(null);
};
return (
<Page isShowAllAction={false}>
<PageHeader
title="评价师信息管理"
subTitle="监管端查看辖区内评价师备案及资质状态(当前为 Mock 数据)。"
/>
<Search
form={searchForm}
options={[
{ name: "evaluatorName", label: "评价师名称", placeholder: "关键字搜索" },
{ name: "orgName", label: "机构名称", placeholder: "关键字搜索" },
{
name: "employmentStatus",
label: "是否在职",
render: FORM_ITEM_RENDER_ENUM.SELECT,
options: YES_NO_OPTIONS,
},
{
name: "registerEngineerFlag",
label: "是否注册安全工程师",
render: FORM_ITEM_RENDER_ENUM.SELECT,
options: YES_NO_OPTIONS,
},
{
name: "status",
label: "状态",
render: FORM_ITEM_RENDER_ENUM.SELECT,
options: STATUS_OPTIONS,
},
]}
onFinish={getData}
/>
<TableList
{...tableProps}
columns={[
{ title: "机构名称", dataIndex: "orgName", ellipsis: true },
{ title: "评价师名称", dataIndex: "evaluatorName", width: 120 },
{
title: "是否注册安全工程师",
dataIndex: "registerEngineerLabel",
width: 160,
},
{ title: "资格证书", dataIndex: "certificatesSummary", ellipsis: true },
{
title: "状态",
width: 90,
render: (_, record) => {
const tag = (
<Tag color={record.status === "normal" ? "success" : "error"}>
{record.statusLabel}
</Tag>
);
return record.statusTip ? <Tooltip title={record.statusTip}>{tag}</Tooltip> : tag;
},
},
{
title: "操作",
width: 80,
render: (_, record) => (
<Button type="link" onClick={() => openDetail(record.id)}>查看</Button>
),
},
]}
/>
<Modal
open={viewOpen}
destroyOnClose
title="评价师详细信息"
width={900}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={closeDetail}
>
{detail && (
<>
<Descriptions title="个人信息" bordered column={2} size="small" style={{ marginBottom: 16 }}>
<Descriptions.Item label="姓名">{detail.name}</Descriptions.Item>
<Descriptions.Item label="性别">{detail.gender}</Descriptions.Item>
<Descriptions.Item label="出生日期">{detail.birthDate}</Descriptions.Item>
<Descriptions.Item label="身份证号">{detail.idCard}</Descriptions.Item>
<Descriptions.Item label="学历">{detail.education}</Descriptions.Item>
<Descriptions.Item label="毕业院校">{detail.graduateSchool}</Descriptions.Item>
<Descriptions.Item label="专业">{detail.major}</Descriptions.Item>
</Descriptions>
<Descriptions title="机构信息" bordered column={2} size="small" style={{ marginBottom: 16 }}>
<Descriptions.Item label="所在机构">{detail.orgName}</Descriptions.Item>
<Descriptions.Item label="部门">{detail.deptName}</Descriptions.Item>
<Descriptions.Item label="岗位">{detail.positionName}</Descriptions.Item>
<Descriptions.Item label="账号">{detail.account}</Descriptions.Item>
</Descriptions>
<div style={{ marginBottom: 8, fontWeight: 600 }}>资质证照</div>
<Table
size="small"
bordered
rowKey="id"
pagination={false}
dataSource={detail.certificates || []}
columns={[
{ title: "证书名称", dataIndex: "certName" },
{ title: "证书编号", dataIndex: "certNo" },
{ title: "发证日期", dataIndex: "issueDate", width: 110 },
{ title: "有效期至", dataIndex: "expireDate", width: 110 },
{
title: "状态",
width: 90,
render: (_, record) => {
const tag = (
<Tag color={CERT_STATUS_COLOR[record.status] || "default"}>
{record.statusLabel}
</Tag>
);
return record.statusTip ? <Tooltip title={record.statusTip}>{tag}</Tooltip> : tag;
},
},
{
title: "操作",
width: 90,
render: (_, record) => (
<Button type="link" size="small" onClick={() => setCertPreview(record)}>
查看证书
</Button>
),
},
]}
/>
</>
)}
</Modal>
<Modal
open={!!certPreview}
destroyOnClose
title="证书预览"
width={520}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={() => setCertPreview(null)}
>
{certPreview && (
<Descriptions bordered column={1} size="small">
<Descriptions.Item label="证书名称">{certPreview.certName}</Descriptions.Item>
<Descriptions.Item label="证书编号">{certPreview.certNo}</Descriptions.Item>
<Descriptions.Item label="发证日期">{certPreview.issueDate}</Descriptions.Item>
<Descriptions.Item label="有效期至">{certPreview.expireDate}</Descriptions.Item>
<Descriptions.Item label="状态">{certPreview.statusLabel}</Descriptions.Item>
</Descriptions>
)}
</Modal>
</Page>
);
}
export default EvaluatorInfoPage;

View File

@ -0,0 +1,346 @@
import {
Button, Descriptions, Form, Input, InputNumber, message, Modal, Row, Col, Select, Space, Tag,
} from "antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import useTable from "zy-react-library/hooks/useTable";
import { isOrgAccountEnabled } from "~/api/enterpriseInfo/adapter";
import {
CHONGQING_DISTRICTS,
ENTERPRISE_SCALE_OPTIONS,
ENTERPRISE_STATUS_OPTIONS,
ORG_ACCOUNT_STATE_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { NS_ORG_INFO } from "~/enumerate/namespace";
import { safeListRequest, safeRequest } from "~/utils";
import { formSelectField } from "~/utils/enterpriseForm";
import PageHeader from "../../../EnterpriseInfo/components/PageHeader";
const SEARCH_COL = { xs: 24, sm: 12, md: 8, lg: 6 };
const SEARCH_DEFAULT_VALUES = { state: "" };
function OrgAccountPage(props) {
const [searchForm] = Form.useForm();
const [editForm] = Form.useForm();
const [viewOpen, setViewOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
const [currentId, setCurrentId] = useState(null);
const [detail, setDetail] = useState(null);
const [rawDetail, setRawDetail] = useState(null);
const [detailLoading, setDetailLoading] = useState(false);
const [editLoading, setEditLoading] = useState(false);
const { tableProps, getData } = useTable(safeListRequest(props.orgAccountList), { form: searchForm });
useEffect(() => {
getData();
}, []);
const loadDetail = async (id) => {
setDetailLoading(true);
const res = await safeRequest(props.orgAccountGet, { id });
setDetail(res?.data || null);
setRawDetail(res?.raw || null);
setDetailLoading(false);
return res;
};
const openView = async (id) => {
setCurrentId(id);
setViewOpen(true);
await loadDetail(id);
};
const openEdit = async (id) => {
setCurrentId(id);
setEditOpen(true);
const res = await loadDetail(id);
if (res?.data) {
editForm.setFieldsValue(res.data);
}
};
const onToggleStatus = (record) => {
const enabled = isOrgAccountEnabled(record);
Modal.confirm({
title: "确认操作",
content: enabled ? `确认禁用「${record.orgName}」账号?` : `确认启用「${record.orgName}」账号?`,
okText: "确认",
cancelText: "取消",
onOk: async () => {
const nextState = enabled ? 1 : 0;
const res = await props.orgAccountUpdateState({ id: record.id, state: nextState });
if (res?.success !== false) {
message.success("操作成功");
await getData();
}
else {
message.error(res?.message || "操作失败");
}
},
});
};
const onResetPassword = (record) => {
Modal.confirm({
title: "重置密码",
content: (
<div>
<p>
确认将
<strong>{record.orgName}</strong>
的密码重置为初始密码
</p>
<p style={{ color: "rgba(0,0,0,0.45)", fontSize: 12 }}>初始密码Ap@123456</p>
</div>
),
okText: "确认重置",
cancelText: "取消",
onOk: async () => {
const res = await props.orgAccountResetPassword({ id: record.id });
if (res?.success !== false) {
message.success("密码已重置");
}
else {
message.error(res?.message || "重置失败");
}
},
});
};
const onDelete = (record) => {
Modal.confirm({
title: "确认删除",
content: (
<div>
<p>
确认删除
<strong>{record.orgName}</strong>
的账号
</p>
<p style={{ color: "rgba(0,0,0,0.45)", fontSize: 12 }}>此操作不可恢复请谨慎操作</p>
</div>
),
okText: "确认删除",
okButtonProps: { danger: true },
cancelText: "取消",
onOk: async () => {
const res = await props.orgAccountDelete({ id: record.id });
if (res?.success !== false) {
message.success("删除成功");
await getData();
}
else {
message.error(res?.message || "删除失败");
}
},
});
};
const onEditSubmit = async () => {
const values = await editForm.validateFields();
setEditLoading(true);
const res = await props.orgAccountModify({
...values,
id: currentId,
raw: rawDetail,
});
setEditLoading(false);
if (res?.success !== false) {
message.success("保存成功");
setEditOpen(false);
setCurrentId(null);
setRawDetail(null);
editForm.resetFields();
await getData();
}
else {
message.error(res?.message || "保存失败");
}
};
return (
<Page isShowAllAction={false}>
<PageHeader
title="评价机构账号管理"
subTitle="监管端维护评价机构开户账号。"
/>
<Search
form={searchForm}
values={SEARCH_DEFAULT_VALUES}
options={[
{ name: "orgName", label: "机构名称", placeholder: "关键字搜索", colProps: SEARCH_COL },
formSelectField("district", "属地", CHONGQING_DISTRICTS, {
colProps: SEARCH_COL,
placeholder: "请选择属地",
}),
formSelectField("state", "状态", ORG_ACCOUNT_STATE_OPTIONS, {
colProps: SEARCH_COL,
componentProps: { allowClear: false, showSearch: false },
}),
{
name: "openTimeRange",
label: "开户时间",
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
colProps: { ...SEARCH_COL, lg: 8 },
},
]}
onFinish={getData}
/>
<Table
{...tableProps}
columns={[
{ title: "机构名称", dataIndex: "orgName", ellipsis: true },
{ title: "属地", dataIndex: "district", width: 100 },
{
title: "状态",
width: 80,
render: (_, record) => (
<Tag color={isOrgAccountEnabled(record) ? "success" : "error"}>
{isOrgAccountEnabled(record) ? "启用" : "禁用"}
</Tag>
),
},
{ title: "开户时间", dataIndex: "createTime", width: 110 },
{
title: "操作",
width: 320,
fixed: "right",
render: (_, record) => (
<Space size={0} wrap>
<Button type="link" size="small" onClick={() => openView(record.id)}>查看</Button>
<Button type="link" size="small" onClick={() => openEdit(record.id)}>编辑</Button>
<Button type="link" size="small" onClick={() => onResetPassword(record)}>重置密码</Button>
<Button type="link" size="small" danger onClick={() => onDelete(record)}>删除</Button>
<Button type="link" size="small" onClick={() => onToggleStatus(record)}>
{isOrgAccountEnabled(record) ? "禁用" : "启用"}
</Button>
</Space>
),
},
]}
/>
<Modal
open={viewOpen}
destroyOnClose
title="机构信息查看"
width={760}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={() => { setViewOpen(false); setDetail(null); setRawDetail(null); }}
>
{detail && (
<Descriptions bordered column={2} size="small">
<Descriptions.Item label="机构名称">{detail.orgName}</Descriptions.Item>
<Descriptions.Item label="统一社会信用代码">{detail.creditCode}</Descriptions.Item>
<Descriptions.Item label="属地">{detail.regionCountyName}</Descriptions.Item>
<Descriptions.Item label="安全生产监管行业类别">{detail.safetyIndustryCategory}</Descriptions.Item>
<Descriptions.Item label="经营地址" span={2}>{detail.businessAddress}</Descriptions.Item>
<Descriptions.Item label="企业状态">{detail.enterpriseStatus}</Descriptions.Item>
<Descriptions.Item label="经度/纬度">{detail.longitude}, {detail.latitude}</Descriptions.Item>
<Descriptions.Item label="主要负责人">{detail.principal}</Descriptions.Item>
<Descriptions.Item label="主要负责人电话">{detail.principalPhone}</Descriptions.Item>
<Descriptions.Item label="法定代表人">{detail.legalRepresentative}</Descriptions.Item>
<Descriptions.Item label="法人电话">{detail.legalRepPhone}</Descriptions.Item>
<Descriptions.Item label="企业规模" span={2}>{detail.enterpriseScale}</Descriptions.Item>
</Descriptions>
)}
</Modal>
<Modal
open={editOpen}
destroyOnClose
title="编辑机构信息"
width={760}
confirmLoading={editLoading}
okText="保存"
cancelText="取消"
onCancel={() => {
setEditOpen(false);
setCurrentId(null);
setRawDetail(null);
editForm.resetFields();
}}
onOk={onEditSubmit}
>
<Form form={editForm} layout="vertical">
<Row gutter={16}>
<Col span={12}>
<Form.Item name="orgName" label="机构名称" rules={[{ required: true, message: "请输入机构名称" }]}>
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="creditCode" label="统一社会信用代码" rules={[{ required: true, message: "请输入信用代码" }]}>
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="regionCountyName" label="属地">
<Select options={CHONGQING_DISTRICTS} placeholder="请选择属地" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="safetyIndustryCategory" label="安全生产监管行业类别">
<Input />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="businessAddress" label="企事业单位经营地址">
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="enterpriseStatus" label="企业状态">
<Select options={ENTERPRISE_STATUS_OPTIONS} />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="longitude" label="经度">
<InputNumber style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={6}>
<Form.Item name="latitude" label="纬度">
<InputNumber style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="principal" label="主要负责人">
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="principalPhone" label="主要负责人电话">
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="legalRepresentative" label="法定代表人">
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="legalRepPhone" label="法人电话">
<Input />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="enterpriseScale" label="企业规模">
<Select options={ENTERPRISE_SCALE_OPTIONS.map((o) => ({ label: o.label, value: o.label }))} />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
</Page>
);
}
export default Connect([NS_ORG_INFO], true)(OrgAccountPage);

View File

@ -0,0 +1,274 @@
import { Button, Card, Form, Spin, Table, Tabs, Tag, message } from "antd";
import { useEffect, useMemo, useState } from "react";
import FormBuilder from "zy-react-library/components/FormBuilder";
import Page from "zy-react-library/components/Page";
import FilePreviewModal from "~/components/FilePreviewModal";
import {
fetchRegisteredOrgDetail,
fetchRegisteredOrgPersonnelCertDetail,
fetchRegisteredOrgPersonnelCertList,
fetchRegisteredOrgPersonnelDetail,
fetchRegisteredOrgPersonnelList,
fetchRegisteredOrgQualificationGroups,
} from "~/api/regulatorOrgInfo";
import { buildOrgInfoFormOptions } from "../../../../EnterpriseInfo/OrgInfo/formOptions";
import PageHeader from "../../../../EnterpriseInfo/components/PageHeader";
import StaffViewModal from "../../../../EnterpriseInfo/PersonnelInfo/StaffViewModal";
const LIST_PATH = "/container/supervision/basicInfo/registeredOrg/list";
const ORG_INFO_FORM_OPTIONS = buildOrgInfoFormOptions();
function parseQueryId(location) {
const search = location?.search || window.location.search || "";
return new URLSearchParams(search).get("id");
}
function MaterialsTab({ materialGroups, loading, onPreview }) {
if (loading) {
return <Spin />;
}
return (
<div>
<div style={{ marginBottom: 12, color: "rgba(0,0,0,0.45)" }}>
该机构共申请
<strong>{materialGroups.length}</strong>
个业务范围资质以下为各范围对应的申请材料清单
</div>
{materialGroups.map((group) => (
<Card
key={group.id}
size="small"
style={{ marginBottom: 16 }}
title={`业务范围:${group.scopeName}`}
extra={(
<span style={{ fontSize: 12 }}>
证书有效期
<span style={{ color: group.expireWarning ? "#faad14" : "#52c41a", fontWeight: 500 }}>
{group.expireDate}
{group.expireWarning ? "(即将到期)" : ""}
</span>
</span>
)}
>
<Table
size="small"
bordered
rowKey="id"
pagination={false}
dataSource={group.materials}
columns={[
{ title: "材料名称", dataIndex: "name" },
{
title: "操作",
width: 80,
render: (_, record) => (
<Button type="link" size="small" onClick={() => onPreview(record)}>预览</Button>
),
},
]}
/>
</Card>
))}
</div>
);
}
function PersonnelTab({ personnel, loading, onView }) {
if (loading) {
return <Spin />;
}
return (
<Table
size="small"
bordered
rowKey="id"
pagination={false}
dataSource={personnel}
columns={[
{ title: "序号", width: 60, render: (_, __, index) => index + 1 },
{ title: "姓名", dataIndex: "name", width: 90 },
{ title: "性别", dataIndex: "gender", width: 60 },
{ title: "岗位", dataIndex: "position", width: 110 },
{ title: "资质范围", dataIndex: "qualScope", width: 90 },
{ title: "学历", dataIndex: "education", width: 80 },
{
title: "注册安全工程师",
width: 120,
render: (_, record) => (
<Tag color={record.registerEngineer === 1 ? "success" : "error"}>
{record.registerEngineer === 1 ? "是" : "否"}
</Tag>
),
},
{
title: "操作",
width: 80,
render: (_, record) => (
<Button type="link" size="small" onClick={() => onView(record)}>查看</Button>
),
},
]}
/>
);
}
function RegisteredOrgDetailPage(props) {
const id = useMemo(() => parseQueryId(props.location), [props.location?.search]);
const [form] = Form.useForm();
const [orgLoading, setOrgLoading] = useState(Boolean(parseQueryId(props.location)));
const [extrasLoading, setExtrasLoading] = useState(Boolean(parseQueryId(props.location)));
const [orgName, setOrgName] = useState("");
const [materialGroups, setMaterialGroups] = useState([]);
const [personnel, setPersonnel] = useState([]);
const [filePreview, setFilePreview] = useState(null);
const [viewPersonnelId, setViewPersonnelId] = useState("");
useEffect(() => {
if (!id) {
setOrgLoading(false);
setExtrasLoading(false);
return;
}
let cancelled = false;
setOrgLoading(true);
setExtrasLoading(true);
(async () => {
try {
const [orgRes, groups, staffList] = await Promise.all([
fetchRegisteredOrgDetail(id),
fetchRegisteredOrgQualificationGroups(id).catch((err) => {
console.warn("[RegisteredOrgDetail] qualification load failed:", err);
message.error(err?.message || "加载申请材料失败");
return [];
}),
fetchRegisteredOrgPersonnelList(id).catch((err) => {
console.warn("[RegisteredOrgDetail] personnel load failed:", err);
message.error(err?.message || "加载人员信息失败");
return [];
}),
]);
if (cancelled) {
return;
}
if (orgRes?.data) {
form.setFieldsValue(orgRes.data);
setOrgName(orgRes.data.orgName || "");
}
setMaterialGroups(groups || []);
setPersonnel(staffList || []);
}
catch (err) {
if (!cancelled) {
console.warn("[RegisteredOrgDetail] load failed:", err);
message.error("加载机构详情失败");
}
}
finally {
if (!cancelled) {
setOrgLoading(false);
setExtrasLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, [id]);
const goBack = () => {
if (props.history?.goBack && props.history.length > 1) {
props.history.goBack();
return;
}
if (props.history?.push) {
props.history.push(LIST_PATH);
return;
}
window.location.href = `/certificate${LIST_PATH}`;
};
if (!id) {
return (
<Page isShowAllAction={false}>
<PageHeader title="备案信息详情" subTitle="缺少机构 ID 参数" extra={<Button onClick={goBack}>返回</Button>} />
</Page>
);
}
const tabItems = [
{
key: "info",
label: "备案信息",
children: (
<Spin spinning={orgLoading}>
<FormBuilder
form={form}
span={12}
disabled
useAutoGenerateRequired={false}
options={ORG_INFO_FORM_OPTIONS}
labelCol={{ span: 10 }}
showActionButtons={false}
/>
</Spin>
),
},
{
key: "materials",
label: "申请清单材料",
children: (
<MaterialsTab
materialGroups={materialGroups}
loading={extrasLoading}
onPreview={setFilePreview}
/>
),
},
{
key: "personnel",
label: "人员信息",
children: (
<PersonnelTab
personnel={personnel}
loading={extrasLoading}
onView={(record) => setViewPersonnelId(record.id)}
/>
),
},
];
return (
<Page isShowAllAction={false}>
<PageHeader
title="备案信息详情"
subTitle={orgName || undefined}
extra={<Button onClick={goBack}> 返回</Button>}
/>
<Tabs items={tabItems} />
<FilePreviewModal
open={!!filePreview}
title="文件预览"
fileName={filePreview?.name}
url={filePreview?.previewUrl}
onCancel={() => setFilePreview(null)}
/>
{viewPersonnelId && (
<StaffViewModal
open={!!viewPersonnelId}
currentId={viewPersonnelId}
requestDetails={fetchRegisteredOrgPersonnelDetail}
requestCertList={fetchRegisteredOrgPersonnelCertList}
requestCertDetails={fetchRegisteredOrgPersonnelCertDetail}
onCancel={() => setViewPersonnelId("")}
/>
)}
</Page>
);
}
export default RegisteredOrgDetailPage;

View File

@ -0,0 +1,101 @@
import { Button, Form, Tag } from "antd";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { useEffect } from "react";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import TableList from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import {
REGISTERED_ORG_FILING_RECORD_STATUS_OPTIONS,
REGISTERED_ORG_FILING_TYPE_SEARCH_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { NS_ORG_INFO } from "~/enumerate/namespace";
import { safeListRequest } from "~/utils";
import { formSelectField } from "~/utils/enterpriseForm";
import PageHeader from "../../../../EnterpriseInfo/components/PageHeader";
const FILING_RECORD_STATUS_COLOR = {
1: "success",
2: "default",
};
const SEARCH_COL = { xs: 24, sm: 12, md: 8, lg: 6 };
const SEARCH_DEFAULT_VALUES = { filingType: "", filingRecordStatus: "" };
/** 约定式路由首字母小写registeredOrg/detail */
const DETAIL_PATH = "/container/supervision/basicInfo/registeredOrg/detail";
function RegisteredOrgListPage(props) {
const [searchForm] = Form.useForm();
const { tableProps, getData } = useTable(safeListRequest(props.registeredOrgList), { form: searchForm });
useEffect(() => {
getData();
}, []);
const goDetail = (id) => {
if (!id) {
return;
}
const query = `${DETAIL_PATH}?id=${encodeURIComponent(id)}`;
if (props.history?.push) {
props.history.push(query);
return;
}
window.location.href = `/certificate${query}`;
};
return (
<Page isShowAllAction={false}>
<PageHeader
title="已备案机构管理"
subTitle="监管端查看已备案评价机构及备案详情。"
/>
<Search
form={searchForm}
values={SEARCH_DEFAULT_VALUES}
options={[
{ name: "orgName", label: "评价机构名称", placeholder: "关键字搜索", colProps: SEARCH_COL },
{ name: "registerAddress", label: "注册地址", placeholder: "关键字搜索", colProps: SEARCH_COL },
formSelectField("filingType", "备案类型", REGISTERED_ORG_FILING_TYPE_SEARCH_OPTIONS, {
colProps: SEARCH_COL,
componentProps: { allowClear: false, showSearch: false },
}),
formSelectField("filingRecordStatus", "备案状态", REGISTERED_ORG_FILING_RECORD_STATUS_OPTIONS, {
colProps: SEARCH_COL,
componentProps: { allowClear: false, showSearch: false },
}),
]}
onFinish={getData}
/>
<TableList
{...tableProps}
columns={[
{ title: "评价机构名称", dataIndex: "orgName", ellipsis: true },
{ title: "注册地址", dataIndex: "registerAddress", ellipsis: true },
{ title: "服务企业数", dataIndex: "serviceEnterpriseCount", width: 100 },
{ title: "评价项目数", dataIndex: "evalProjectCount", width: 100 },
{ title: "备案类型", dataIndex: "filingType", width: 100 },
{
title: "备案状态",
width: 90,
render: (_, record) => (
<Tag color={FILING_RECORD_STATUS_COLOR[record.filingRecordStatusCode] || "default"}>
{record.filingRecordStatusName || "-"}
</Tag>
),
},
{
title: "操作",
width: 120,
render: (_, record) => (
<Button type="link" onClick={() => goDetail(record.id)}>查看备案信息</Button>
),
},
]}
/>
</Page>
);
}
export default Connect([NS_ORG_INFO], true)(RegisteredOrgListPage);

View File

@ -0,0 +1,5 @@
function RegisteredOrg(props) {
return props.children || null;
}
export default RegisteredOrg;

View File

@ -0,0 +1,5 @@
function BasicInfo(props) {
return <div>{props.children}</div>;
}
export default BasicInfo;

View File

@ -11,11 +11,38 @@ export function formatDateTime(value) {
/** 展示2026-01-23 */
export function formatDate(value) {
if (value == null || value === "") {
const normalized = normalizeDateValue(value);
if (normalized == null || normalized === "") {
return "-";
}
const d = dayjs(value);
return d.isValid() ? d.format("YYYY-MM-DD") : String(value);
const d = dayjs(normalized);
return d.isValid() ? d.format("YYYY-MM-DD") : String(normalized);
}
/** 兼容 Jackson LocalDateTime 数组 / 对象 / 字符串 */
export function normalizeDateValue(value) {
if (value == null || value === "") {
return null;
}
if (typeof value === "string" || typeof value === "number") {
return value;
}
if (dayjs.isDayjs(value)) {
return value;
}
if (Array.isArray(value)) {
const [y, mo, d, h = 0, mi = 0, s = 0] = value;
return new Date(y, mo - 1, d, h, mi, s);
}
if (typeof value === "object") {
const y = value.year ?? value.y;
const mo = value.monthValue ?? value.month ?? value.m;
const d = value.dayOfMonth ?? value.day ?? value.d;
if (y != null && mo != null && d != null) {
return new Date(y, mo - 1, d);
}
}
return null;
}
/** 提交 API日期 */