diff --git a/public/index.html b/public/index.html index bb7e4c9..1a8b3ef 100644 --- a/public/index.html +++ b/public/index.html @@ -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(); + } + }); + })(); <% for (const item of $scripts) { %> diff --git a/src/api/enterpriseInfo/adapter.js b/src/api/enterpriseInfo/adapter.js index 7caf1d8..fa5d6cd 100644 --- a/src/api/enterpriseInfo/adapter.js +++ b/src/api/enterpriseInfo/adapter.js @@ -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禁用) */ diff --git a/src/api/enterpriseInfo/http.js b/src/api/enterpriseInfo/http.js index 7f17b50..5d5d441 100644 --- a/src/api/enterpriseInfo/http.js +++ b/src/api/enterpriseInfo/http.js @@ -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); diff --git a/src/api/orgInfo/index.js b/src/api/orgInfo/index.js index f08f65b..967c986 100644 --- a/src/api/orgInfo/index.js +++ b/src/api/orgInfo/index.js @@ -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"; diff --git a/src/api/regulatorOrgInfo/index.js b/src/api/regulatorOrgInfo/index.js new file mode 100644 index 0000000..1ccd012 --- /dev/null +++ b/src/api/regulatorOrgInfo/index.js @@ -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); +} diff --git a/src/api/staffCertificate/index.js b/src/api/staffCertificate/index.js index f52c526..cd43b33 100644 --- a/src/api/staffCertificate/index.js +++ b/src/api/staffCertificate/index.js @@ -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); +} diff --git a/src/components/FilePreviewModal/index.js b/src/components/FilePreviewModal/index.js new file mode 100644 index 0000000..55198a4 --- /dev/null +++ b/src/components/FilePreviewModal/index.js @@ -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 ( +
+ 暂无上传文件 +
+ ); + } + + const type = getFilePreviewType(url, fileName); + if (type === "image") { + return {fileName; + } + if (type === "pdf") { + return ( +