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

dev
huwei 2026-06-26 16:22:25 +08:00
commit 4500684a55
19 changed files with 889 additions and 159 deletions

View File

@ -1,10 +1,33 @@
/** 企业信息管理:前后端字段与分页格式适配 */
import { formatDate, formatDateTime, toApiDate, toApiDateTime } from "../../utils/dateFormat";
import { resolveUploadFileIds } from "../../utils/mockUpload";
import { asId, isIdFieldName, normalizeQueryIds } from "./idUtil";
import { withOrgId } from "./orgContext";
const GENDER_NAME = { 1: "男", 2: "女" };
const RESIGN_AUDIT_STATUS_NAME = { 0: "未审核", 1: "已审核", 2: "已退回" };
const ENTERPRISE_STATUS_CODE = { 正常: 1, 停业: 2, 注销: 3 };
const FILING_TYPE_CODE = { 审核备案: "1", 确认备案: "2" };
const FILING_RECORD_STATUS_CODE = { 已备案: 1, 未备案: 2 };
const PERSON_TYPE_CODE = { 基础人员: "1", 专职评价师: "2" };
function parseAttachmentUrls(urls, defaultName = "附件.pdf") {
if (!urls) {
return [];
}
return String(urls)
.split(",")
.map((url) => url.trim())
.filter(Boolean)
.map((url, index) => ({
name: `${defaultName.replace(".pdf", "")}${index + 1}.pdf`,
fileName: `${defaultName.replace(".pdf", "")}${index + 1}.pdf`,
url,
}));
}
/** 分页查询参数:子表 org_id → org_info.id机构信息管理 getInfo 不走此方法 */
export function toPageQuery(params = {}, fieldMap = {}) {
const query = {
current: params.pageIndex ?? params.current ?? 1,
@ -13,10 +36,10 @@ export function toPageQuery(params = {}, fieldMap = {}) {
Object.entries(fieldMap).forEach(([from, to]) => {
const value = params[from];
if (value !== undefined && value !== null && value !== "") {
query[to] = value;
query[to] = isIdFieldName(to) ? asId(value) : value;
}
});
return query;
return withOrgId(normalizeQueryIds(query));
}
export function fromPageResponse(res, mapItem) {
@ -45,7 +68,7 @@ function pick(obj, keys) {
const next = {};
keys.forEach((key) => {
if (obj[key] !== undefined) {
next[key] = obj[key];
next[key] = isIdFieldName(key) ? asId(obj[key]) : obj[key];
}
});
return next;
@ -55,7 +78,7 @@ function pick(obj, keys) {
export function toOrgInfoForm(data = {}) {
return {
id: data.id,
id: asId(data.id),
orgName: data.unitName,
creditCode: data.creditCode,
safetyIndustryCategory: data.safetyIndustryCategoryName,
@ -83,16 +106,25 @@ export function toOrgInfoForm(data = {}) {
fullTimeEvaluatorCount: data.fulltimeEvaluatorCount,
registeredSafetyEngineerCount: data.registeredEngineerCount,
authStatusCode: data.authStatusCode,
enterpriseStatus: data.enterpriseStatusName,
enterpriseScale: data.enterpriseScaleName,
filingType: data.filingTypeName ?? "确认备案",
filingRecordStatus: data.filingRecordStatusName ?? "未备案",
attachments: parseAttachmentUrls(data.attachmentUrls),
};
}
export function fromOrgInfoForm(values = {}, options = {}) {
const { isDraft = false } = options;
const enterpriseStatus = values.enterpriseStatus;
const filingType = values.filingType ?? "确认备案";
const filingRecordStatus = values.filingRecordStatus ?? "未备案";
return {
...pick(values, ["id", "tenantId"]),
unitName: values.orgName,
creditCode: values.creditCode,
safetyIndustryCategoryName: values.safetyIndustryCategory,
districtCode: values.regionCountyName,
districtName: values.regionCountyName,
townStreet: values.regionStreetName,
villageCommunity: values.regionCommunityName,
@ -118,6 +150,15 @@ export function fromOrgInfoForm(values = {}, options = {}) {
registeredEngineerCount: values.registeredSafetyEngineerCount,
authStatusCode: isDraft ? 0 : 1,
authStatusName: isDraft ? "草稿" : "已提交",
enterpriseStatusCode: ENTERPRISE_STATUS_CODE[enterpriseStatus],
enterpriseStatusName: enterpriseStatus,
enterpriseScaleCode: values.enterpriseScale,
enterpriseScaleName: values.enterpriseScale,
filingTypeCode: FILING_TYPE_CODE[filingType] ?? filingType,
filingTypeName: filingType,
filingRecordStatusCode: FILING_RECORD_STATUS_CODE[filingRecordStatus],
filingRecordStatusName: filingRecordStatus,
attachmentUrls: resolveUploadFileIds(values.attachments),
};
}
@ -125,8 +166,8 @@ export function fromOrgInfoForm(values = {}, options = {}) {
export function toDepartmentForm(data = {}) {
return {
id: data.id,
parentId: data.parentId,
id: asId(data.id),
parentId: asId(data.parentId),
deptName: data.deptName,
leaderName: data.managerName,
leaderAccount: data.managerAccount,
@ -148,13 +189,14 @@ export function fromDepartmentForm(values = {}) {
export function buildDepartmentTree(departments = []) {
const map = new Map();
departments.forEach((item) => {
map.set(item.id, { ...toDepartmentForm(item), children: [] });
map.set(asId(item.id), { ...toDepartmentForm(item), children: [] });
});
const roots = [];
departments.forEach((item) => {
const node = map.get(item.id);
if (item.parentId != null && item.parentId !== 0 && map.has(item.parentId)) {
map.get(item.parentId).children.push(node);
const node = map.get(asId(item.id));
const parentId = asId(item.parentId);
if (parentId != null && parentId !== "0" && map.has(parentId)) {
map.get(parentId).children.push(node);
}
else {
roots.push(node);
@ -165,8 +207,8 @@ export function buildDepartmentTree(departments = []) {
export function toPositionForm(data = {}) {
return {
id: data.id,
deptId: data.deptId,
id: asId(data.id),
deptId: asId(data.deptId),
deptName: data.deptName,
positionName: data.positionName,
dutyDesc: data.dutyDesc,
@ -175,10 +217,9 @@ export function toPositionForm(data = {}) {
}
export function fromPositionForm(values = {}) {
const deptId = values.deptId != null && values.deptId !== "" ? Number(values.deptId) : undefined;
return {
...pick(values, ["id", "tenantId"]),
deptId,
deptId: asId(values.deptId),
positionName: values.positionName,
dutyDesc: values.dutyDesc,
remark: values.remark,
@ -189,20 +230,32 @@ export function fromPositionForm(values = {}) {
export function toStaffForm(data = {}) {
return {
id: data.id,
id: asId(data.id),
staffName: data.userName,
account: data.account,
gender: data.genderCode,
birthDate: data.birthDate,
deptId: data.deptId,
positionId: data.postId,
deptId: asId(data.deptId),
positionId: asId(data.postId),
idCardNo: data.idCardNo,
homeAddress: data.currentAddress,
officeAddress: data.officeAddress,
educationType: data.educationTypeName,
educationLevel: data.educationLevelName,
education: data.educationName ?? data.educationCode,
graduateSchool: data.graduateSchool,
major: data.major,
employmentStatus: data.employmentStatusCode,
personType: data.personTypeName ?? "基础人员",
qualScope: data.qualScope,
professionalLevel: data.professionalLevelName,
evaluatorCertNo: data.evaluatorCertNo,
titleName: data.titleName,
registerEngineerFlag: data.registerEngineerFlag ?? 2,
publications: data.publications,
abilityDeclaration: data.abilityDeclaration,
workExperience: data.workExperience,
proofMaterials: parseAttachmentUrls(data.proofMaterialUrl, "专业能力证明.pdf"),
deptName: data.deptName,
positionName: data.postName ?? data.positionName,
certNames: data.certNames,
@ -211,23 +264,46 @@ export function toStaffForm(data = {}) {
export function fromStaffForm(values = {}) {
const genderCode = values.gender;
const personType = values.personType ?? "基础人员";
const educationType = values.educationType;
const educationLevel = values.educationLevel;
const educationName = educationType && educationLevel
? `${educationType}-${educationLevel}`
: values.education;
return {
...pick(values, ["id", "tenantId", "deptId", "postId"]),
...pick(values, ["id", "tenantId"]),
userName: values.staffName,
account: values.account,
genderCode,
genderName: GENDER_NAME[genderCode] ?? values.genderName,
birthDate: toApiDate(values.birthDate),
postId: values.positionId ?? values.postId,
deptId: asId(values.deptId),
postId: asId(values.positionId ?? values.postId),
idCardNo: values.idCardNo,
currentAddress: values.homeAddress,
officeAddress: values.officeAddress,
educationName: values.education,
educationCode: values.education,
educationName,
educationCode: educationName,
educationTypeCode: educationType,
educationTypeName: educationType,
educationLevelCode: educationLevel,
educationLevelName: educationLevel,
graduateSchool: values.graduateSchool,
major: values.major,
employmentStatusCode: values.employmentStatus ?? 1,
employmentStatusName: values.employmentStatus === 2 ? "离职" : "在职",
personTypeCode: PERSON_TYPE_CODE[personType] ?? personType,
personTypeName: personType,
qualScope: values.qualScope,
professionalLevelCode: values.professionalLevel,
professionalLevelName: values.professionalLevel,
evaluatorCertNo: values.evaluatorCertNo,
titleName: values.titleName,
registerEngineerFlag: values.registerEngineerFlag ?? 2,
publications: values.publications,
abilityDeclaration: values.abilityDeclaration,
workExperience: values.workExperience,
proofMaterialUrl: resolveUploadFileIds(values.proofMaterials),
};
}
@ -238,8 +314,8 @@ export function toStaffCertForm(data = {}) {
? [{ url: data.certAttachmentUrl, fileName: data.certName }]
: data.certImgFiles || [];
return {
id: data.id,
staffId: data.personnelId,
id: asId(data.id),
staffId: asId(data.personnelId),
certName: data.certName,
certCategory: data.certCategoryName ?? data.certCategoryCode,
certWorkCategory: data.operationCategoryName ?? data.operationCategoryCode,
@ -259,7 +335,7 @@ export function fromStaffCertForm(values = {}) {
|| values.certImgFiles?.[0]?.url;
return {
...pick(values, ["id", "tenantId"]),
personnelId: values.staffId,
personnelId: asId(values.staffId),
certName: values.certName,
certCategoryName: values.certCategory,
certCategoryCode: values.certCategory,
@ -276,12 +352,27 @@ export function fromStaffCertForm(values = {}) {
// ─── 机构资质证书 ───
/** 资质证书 enableFlag(1启用/2禁用) ↔ 前端 enableStatus(1启用/0禁用) */
export function mapQualificationEnableStatus(flag) {
return Number(flag) === 1 ? 1 : 0;
}
export function mapQualificationEnableFlag(status) {
return Number(status) === 1 ? 1 : 2;
}
/** 列表/表单统一判断:优先 enableFlag兼容 enableStatus */
export function isQualificationEnabled(record = {}) {
const flag = record.enableFlag ?? record.enableStatus;
return mapQualificationEnableStatus(flag) === 1;
}
export function toQualificationForm(data = {}) {
const certImgs = data.certImageUrl
? [{ url: data.certImageUrl, fileName: data.certName }]
: data.certImgFiles || [];
return {
id: data.id,
id: asId(data.id),
certType: data.licenseTypeName ?? data.licenseTypeCode,
certName: data.certName,
certNo: data.certNo,
@ -290,7 +381,8 @@ export function toQualificationForm(data = {}) {
validStartDate: data.validStartDate,
validEndDate: data.validEndDate,
remark: data.remark ?? data.remarks,
enableStatus: data.enableFlag,
enableFlag: data.enableFlag,
enableStatus: mapQualificationEnableStatus(data.enableFlag),
issueDate: formatDate(data.issueDate),
validStartDate: formatDate(data.validStartDate),
validEndDate: formatDate(data.validEndDate),
@ -315,18 +407,32 @@ export function fromQualificationForm(values = {}) {
validEndDate: toApiDate(values.validEndDate ?? values.validDate?.[1]),
certImageUrl,
remark: values.remark,
enableFlag: values.enableStatus ?? 1,
enableFlag: mapQualificationEnableFlag(values.enableStatus),
};
}
// ─── 装备 ───
/** 装备 enableFlag(1启用/2禁用) ↔ 前端 enableStatus(1启用/0禁用) */
export function mapEquipEnableStatus(flag) {
return Number(flag) === 1 ? 1 : 0;
}
export function mapEquipEnableFlag(status) {
return Number(status) === 1 ? 1 : 2;
}
export function isEquipEnabled(record = {}) {
const flag = record.enableFlag ?? record.enableStatus;
return mapEquipEnableStatus(flag) === 1;
}
export function toEquipForm(data = {}) {
const instrumentType = data.instrumentTypeName ?? data.instrumentTypeCode;
return {
id: data.id,
instrumentCategory: instrumentType,
id: asId(data.id),
instrumentType,
instrumentCategory: instrumentType,
deviceType: data.deviceTypeName ?? data.deviceTypeCode,
deviceName: data.deviceName,
model: data.deviceModel,
@ -338,7 +444,8 @@ export function toEquipForm(data = {}) {
calibrationInitialValue: data.calibrationInitValue,
onSiteCalibrationType: data.fieldCalibrationTypeName ?? data.fieldCalibrationTypeCode,
isDualChannel: data.dualChannelFlag,
enableStatus: data.enableFlag,
enableFlag: data.enableFlag,
enableStatus: mapEquipEnableStatus(data.enableFlag),
};
}
@ -360,7 +467,7 @@ export function fromEquipForm(values = {}) {
fieldCalibrationTypeName: values.onSiteCalibrationType,
fieldCalibrationTypeCode: values.onSiteCalibrationType,
dualChannelFlag: values.isDualChannel,
enableFlag: values.enableStatus ?? 1,
enableFlag: mapEquipEnableFlag(values.enableStatus),
};
}
@ -368,8 +475,8 @@ export function fromEquipForm(values = {}) {
export function toChangeLogForm(data = {}) {
return {
id: data.id,
staffId: data.personnelId,
id: asId(data.id),
staffId: asId(data.personnelId),
changeItem: data.changeItem,
changeTime: formatDateTime(data.changeTime),
createBy: data.operatorName,
@ -379,8 +486,8 @@ export function toChangeLogForm(data = {}) {
export function toResignApplyForm(data = {}) {
const auditStatus = data.auditStatusCode ?? data.auditStatus ?? 0;
return {
id: data.id,
personnelId: data.personnelId,
id: asId(data.id),
personnelId: asId(data.personnelId),
applicantName: data.applicantName,
applyTime: formatDateTime(data.applyTime),
expectedLeaveDate: formatDate(data.expectedResignDate),
@ -396,10 +503,9 @@ export function toResignApplyForm(data = {}) {
}
export function fromResignApplyForm(values = {}) {
const personnelId = values.personnelId;
return {
...pick(values, ["id", "tenantId"]),
personnelId: personnelId != null && personnelId !== "" ? Number(personnelId) : undefined,
personnelId: asId(values.personnelId),
applicantName: values.applicantName,
applyTime: toApiDateTime(values.applyTime),
expectedResignDate: toApiDate(values.expectedLeaveDate),
@ -414,8 +520,8 @@ export function fromResignApplyForm(values = {}) {
export function fromResignAuditForm(values = {}) {
const auditStatus = values.auditStatus;
return {
id: values.id,
personnelId: values.personnelId,
id: asId(values.id),
personnelId: asId(values.personnelId),
auditStatusCode: auditStatus,
auditStatusName: auditStatus === 1 ? "已审核" : auditStatus === 2 ? "已退回" : "未审核",
rejectReason: values.rejectReason,
@ -433,8 +539,8 @@ export function toStaffChangeSummary(personnel = {}) {
: Number(resignStatusRaw);
}
return {
id: personnel.id,
staffId: personnel.id,
id: asId(personnel.id),
staffId: asId(personnel.id),
account: personnel.account,
staffName: personnel.userName ?? personnel.staffName,
deptName: personnel.deptName || "",
@ -443,7 +549,7 @@ export function toStaffChangeSummary(personnel = {}) {
employmentStatusName: personnel.employmentStatusName
|| (employmentCode === 2 ? "离职" : "在职"),
changeCount: Number(personnel.changeCount ?? 0),
resignApplyId: personnel.resignApplyId,
resignApplyId: asId(personnel.resignApplyId),
resignAuditStatus: resignStatus,
resignAuditStatusName: personnel.resignAuditStatusName
?? (resignStatus !== null ? RESIGN_AUDIT_STATUS_NAME[resignStatus] : undefined),

View File

@ -1,4 +1,25 @@
import { Get, Post } from "@cqsjjb/jjb-common-lib/http";
import { normalizeQueryIds, withIds, asId } from "./idUtil";
import { buildOrgInfoHeaders } from "./orgContext";
/**
* jjb-common-lib jjbCommonHttpConfig.header 存在时会完全替换请求头不再读 sessionStorage
* 因此 token / orgInfoId 都通过每次请求的 headers 参数显式传入
*
* @param options.includeOrgContext 默认 true机构 getInfo 查询应传 false不带 orgInfoId
*/
function buildRequestHeaders(extraHeaders = {}, options = {}) {
const { includeOrgContext = true } = options;
const headers = { ...extraHeaders };
const token = sessionStorage.getItem("token");
if (token && !headers.token) {
headers.token = token;
}
if (!includeOrgContext) {
return headers;
}
return buildOrgInfoHeaders(headers);
}
function parseHttpError(err) {
const data = err?.response?.data;
@ -14,9 +35,9 @@ function parseHttpError(err) {
}
/** 底层 HTTP供 adapter 层组合调用(不可嵌套 declareRequest */
export async function apiGet(path, params = {}) {
export async function apiGet(path, params = {}, headers = {}, options = {}) {
try {
return await Get(path, params);
return await Get(path, normalizeQueryIds(params), buildRequestHeaders(headers, options));
}
catch (err) {
return parseHttpError(err);
@ -24,10 +45,10 @@ export async function apiGet(path, params = {}) {
}
/** @ 前缀表示 Post 请求体不包裹 request 字段 */
export async function apiPost(path, data = {}) {
export async function apiPost(path, data = {}, headers = {}) {
const url = path.startsWith("@") ? path : `@${path}`;
try {
return await Post(url, data);
return await Post(url, withIds(data), buildRequestHeaders(headers));
}
catch (err) {
return parseHttpError(err);
@ -35,9 +56,9 @@ export async function apiPost(path, data = {}) {
}
export async function apiPostDelete(path, id) {
const url = `${path}?id=${encodeURIComponent(id)}`;
const url = `${path}?id=${encodeURIComponent(asId(id) ?? "")}`;
try {
return await Post(url, {});
return await Post(url, {}, buildRequestHeaders());
}
catch (err) {
return parseHttpError(err);

View File

@ -0,0 +1,57 @@
/**
* 雪花 ID 全局处理禁止 Number()统一字符串传递
* JS Number.MAX_SAFE_INTEGER = 9007199254740991雪花 id 超出后会精度丢失
*/
export function asId(value) {
if (value == null || value === "") {
return undefined;
}
return String(value);
}
/** 是否像主键/外键字段名 */
export function isIdFieldName(key) {
return key === "id" || key.endsWith("Id");
}
/** 对象内所有 *Id / id 字段转字符串 */
export function withIds(obj = {}) {
if (!obj || typeof obj !== "object") {
return obj;
}
const next = { ...obj };
Object.keys(next).forEach((key) => {
if (isIdFieldName(key) && next[key] != null && next[key] !== "") {
next[key] = asId(next[key]);
}
});
return next;
}
/** 分页/查询参数中的 id 字段转字符串 */
export function normalizeQueryIds(query = {}) {
const next = { ...query };
Object.keys(next).forEach((key) => {
if (isIdFieldName(key) && next[key] != null && next[key] !== "") {
next[key] = asId(next[key]);
}
});
return next;
}
/** 兼容历史 Number() 精度丢失:比较雪花 id 前 16 位 */
export function sameId(a, b) {
const sa = asId(a);
const sb = asId(b);
if (!sa || !sb) {
return false;
}
if (sa === sb) {
return true;
}
return sa.slice(0, 16) === sb.slice(0, 16);
}
/** @deprecated 使用 sameId */
export const sameDeptId = sameId;

View File

@ -0,0 +1,95 @@
/**
* 企业信息模块初始化与下拉数据拉取绕过 DVA避免 loading 状态导致页面重渲染死循环
*/
import {
fromPageResponse,
fromSingleResponse,
toDepartmentForm,
toOrgInfoForm,
toPageQuery,
toPositionForm,
} from "./adapter";
import { apiGet } from "./http";
import { clearOrgInfoId, getOrgInfoId, setOrgInfoId } from "./orgContext";
/** 机构信息管理页路径(无机构数据时唯一可访问页) */
export const ORG_INFO_PAGE_PATH = "/certificate/container/EnterpriseInfo/OrgInfo";
let ensureOrgPromise = null;
/** 最近一次 getInfo 转换结果,供机构信息页复用,避免重复请求 */
let lastOrgInfoDetail = null;
export function isOrgInfoPage(path = window.location.pathname) {
return path === ORG_INFO_PAGE_PATH || path.startsWith(`${ORG_INFO_PAGE_PATH}/`);
}
export function getOrgInfoDetail() {
return lastOrgInfoDetail;
}
export function setOrgInfoDetailCache(detail) {
lastOrgInfoDetail = detail;
}
function fetchOrgInfoContext() {
return apiGet("/safety-eval/org-info/getInfo", {}, {}, { includeOrgContext: false })
.then((res) => {
lastOrgInfoDetail = fromSingleResponse(res, toOrgInfoForm);
const id = lastOrgInfoDetail?.data?.id ?? res?.data?.id;
if (id) {
setOrgInfoId(id);
return { hasOrg: true, orgInfoId: String(id), detail: lastOrgInfoDetail };
}
clearOrgInfoId();
return { hasOrg: false, orgInfoId: null, detail: lastOrgInfoDetail };
})
.catch((err) => {
console.warn("[ensureOrgContext] getInfo failed:", err);
clearOrgInfoId();
lastOrgInfoDetail = { success: false, data: null };
return { hasOrg: false, orgInfoId: null, detail: lastOrgInfoDetail, networkError: true };
});
}
/**
* 确保已缓存机构 id子表分页/请求头 orgInfoId 依赖此值
* @param {{ force?: boolean }} options force=true 时忽略缓存并重新 getInfo进入企业信息模块时使用
* @returns {Promise<{ hasOrg: boolean, orgInfoId: string|null, detail?: object, networkError?: boolean }>}
*/
export function ensureOrgContext(options = {}) {
const { force = false } = options;
if (!force) {
const cached = getOrgInfoId();
if (cached) {
return Promise.resolve({ hasOrg: true, orgInfoId: cached, detail: lastOrgInfoDetail });
}
}
if (ensureOrgPromise) {
return ensureOrgPromise;
}
ensureOrgPromise = fetchOrgInfoContext().finally(() => {
ensureOrgPromise = null;
});
return ensureOrgPromise;
}
export async function fetchOrgDepartmentPage(params = {}) {
await ensureOrgContext();
const query = toPageQuery(params, { likeDeptName: "deptName" });
const res = await apiGet("/safety-eval/org-department/page", query);
return fromPageResponse(res, toDepartmentForm);
}
export async function fetchOrgPositionPage(params = {}) {
await ensureOrgContext();
const query = toPageQuery(params, {
eqDeptId: "deptId",
likePositionName: "positionName",
});
const res = await apiGet("/safety-eval/org-position/page", query);
return fromPageResponse(res, toPositionForm);
}

View File

@ -0,0 +1,48 @@
/** 当前登录用户关联的机构 id供企业信息模块接口请求头 orgInfoId 使用 */
const ORG_INFO_ID_KEY = "orgInfoId";
let memoryOrgInfoId = null;
export function getOrgInfoId() {
return memoryOrgInfoId || sessionStorage.getItem(ORG_INFO_ID_KEY) || null;
}
export function setOrgInfoId(id) {
if (id != null && id !== "") {
memoryOrgInfoId = String(id);
sessionStorage.setItem(ORG_INFO_ID_KEY, memoryOrgInfoId);
}
}
export function clearOrgInfoId() {
memoryOrgInfoId = null;
sessionStorage.removeItem(ORG_INFO_ID_KEY);
}
/** 分页/查询参数:前端缓存 orgInfoId → 后端字段 orgId */
export function withOrgId(params = {}) {
if (params.orgId != null && params.orgId !== "") {
return params;
}
const orgInfoId = getOrgInfoId();
if (orgInfoId) {
return { ...params, orgId: orgInfoId };
}
return params;
}
/** 合并业务请求头,仅通过每次请求的 headers 参数传递,不写入 jjbCommonHttpConfig */
export function buildOrgInfoHeaders(extraHeaders = {}) {
const id = extraHeaders.orgInfoId || getOrgInfoId();
if (!id) {
return extraHeaders;
}
return { ...extraHeaders, orgInfoId: String(id) };
}
// 页面刷新后从 sessionStorage 恢复
const storedOrgInfoId = sessionStorage.getItem(ORG_INFO_ID_KEY);
if (storedOrgInfoId) {
memoryOrgInfoId = storedOrgInfoId;
}

View File

@ -3,6 +3,7 @@ import {
fromEquipForm,
fromPageResponse,
fromSingleResponse,
mapEquipEnableFlag,
toEquipForm,
toPageQuery,
} from "../enterpriseInfo/adapter";
@ -15,6 +16,9 @@ export const equipInfoList = declareRequest("equipInfoLoading", safePageResult(a
deviceType: "deviceType",
enableStatus: "enableFlag",
});
if (query.enableFlag !== undefined && query.enableFlag !== "") {
query.enableFlag = mapEquipEnableFlag(query.enableFlag);
}
const res = await apiGet("/safety-eval/org-equipment/page", query);
return fromPageResponse(res, toEquipForm);
}));
@ -39,9 +43,12 @@ export const equipInfoRemove = declareRequest("equipInfoLoading", safeAction(asy
}));
export const equipInfoToggleStatus = declareRequest("equipInfoLoading", safeAction(async ({ id }) => {
const detailRes = await apiGet("/safety-eval/org-equipment/get", { id });
const current = toEquipForm(detailRes?.data || {});
const nextStatus = current.enableStatus === 1 ? 0 : 1;
const res = await apiPost("/safety-eval/org-equipment/modify", fromEquipForm({ ...current, id, enableStatus: nextStatus }));
return fromSingleResponse(res, toEquipForm);
const res = await apiGet("/safety-eval/org-equipment/get", { id });
const data = res?.data || {};
const nextFlag = Number(data.enableFlag) === 1 ? 2 : 1;
return apiPost("/safety-eval/org-equipment/modify", {
...data,
id,
enableFlag: nextFlag,
});
}));

View File

@ -15,7 +15,7 @@ export const orgDepartmentGet = declareRequest("orgDepartmentLoading", safeActio
}));
export const orgDepartmentTree = declareRequest("orgDepartmentLoading", safeAction(async () => {
const res = await apiGet("/safety-eval/org-department/page", { current: 1, size: 500 });
const res = await apiGet("/safety-eval/org-department/page", toPageQuery({ current: 1, size: 500 }));
const pageData = fromPageResponse(res, toDepartmentForm);
return {
success: true,

View File

@ -1,35 +1,54 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import {
fromOrgInfoForm,
fromPageResponse,
fromSingleResponse,
toOrgInfoForm,
} from "../enterpriseInfo/adapter";
import { apiGet, apiPost, safeAction } from "../enterpriseInfo/http";
import { setOrgInfoDetailCache } from "../enterpriseInfo/orgBootstrap";
import { setOrgInfoId } from "../enterpriseInfo/orgContext";
function persistOrgInfoId(result, rawData) {
const id = result?.data?.id ?? rawData?.id;
if (id) {
setOrgInfoId(id);
}
setOrgInfoDetailCache(result);
return result;
}
function buildSaveHeaders(payload) {
if (payload?.id) {
setOrgInfoId(payload.id);
return { orgInfoId: String(payload.id) };
}
return {};
}
export const orgInfoGet = declareRequest("orgInfoLoading", safeAction(async () => {
const pageRes = await apiGet("/safety-eval/org-info/page", { current: 1, size: 1 });
const pageData = fromPageResponse(pageRes, toOrgInfoForm);
const first = pageData.data?.[0];
if (!first?.id) {
return { success: true, data: null };
}
const detailRes = await apiGet("/safety-eval/org-info/get", { id: first.id });
return fromSingleResponse(detailRes, toOrgInfoForm);
const res = await apiGet("/safety-eval/org-info/getInfo", {}, {}, { includeOrgContext: false });
const result = fromSingleResponse(res, toOrgInfoForm);
return persistOrgInfoId(result, res?.data);
}));
export const orgInfoSave = declareRequest("orgInfoLoading", safeAction(async (values) => {
const payload = fromOrgInfoForm(values, { isDraft: false });
const headers = buildSaveHeaders(payload);
const res = payload.id
? await apiPost("/safety-eval/org-info/modify", payload)
: await apiPost("/safety-eval/org-info/save", payload);
return fromSingleResponse(res, toOrgInfoForm);
? await apiPost("/safety-eval/org-info/modify", payload, headers)
: await apiPost("/safety-eval/org-info/save", payload, headers);
const result = fromSingleResponse(res, toOrgInfoForm);
persistOrgInfoId(result, res?.data);
return result;
}));
export const orgInfoDraft = declareRequest("orgInfoLoading", safeAction(async (values) => {
const payload = fromOrgInfoForm(values, { isDraft: true });
const headers = buildSaveHeaders(payload);
const res = payload.id
? await apiPost("/safety-eval/org-info/modify", payload)
: await apiPost("/safety-eval/org-info/save", payload);
return fromSingleResponse(res, toOrgInfoForm);
? await apiPost("/safety-eval/org-info/modify", payload, headers)
: await apiPost("/safety-eval/org-info/save", payload, headers);
const result = fromSingleResponse(res, toOrgInfoForm);
persistOrgInfoId(result, res?.data);
return result;
}));

View File

@ -34,21 +34,21 @@ export const orgQualificationCertRemove = declareRequest("orgQualificationCertLo
}));
export const orgQualificationCertDisable = declareRequest("orgQualificationCertLoading", safeAction(async ({ id }) => {
const detailRes = await apiGet("/safety-eval/org-qualification/get", { id });
const payload = fromQualificationForm({
...toQualificationForm(detailRes?.data || {}),
const res = await apiGet("/safety-eval/org-qualification/get", { id });
const data = res?.data || {};
return apiPost("/safety-eval/org-qualification/modify", {
...data,
id,
enableStatus: 0,
enableFlag: 2,
});
return apiPost("/safety-eval/org-qualification/modify", payload);
}));
export const orgQualificationCertEnable = declareRequest("orgQualificationCertLoading", safeAction(async ({ id }) => {
const detailRes = await apiGet("/safety-eval/org-qualification/get", { id });
const payload = fromQualificationForm({
...toQualificationForm(detailRes?.data || {}),
const res = await apiGet("/safety-eval/org-qualification/get", { id });
const data = res?.data || {};
return apiPost("/safety-eval/org-qualification/modify", {
...data,
id,
enableStatus: 1,
enableFlag: 1,
});
return apiPost("/safety-eval/org-qualification/modify", payload);
}));

View File

@ -0,0 +1,91 @@
/** 企业信息管理模块下拉选项 */
/** 重庆市辖区(区/县) */
export const CHONGQING_DISTRICTS = [
"万州区", "涪陵区", "渝中区", "大渡口区", "江北区", "沙坪坝区", "九龙坡区", "南岸区",
"北碚区", "綦江区", "大足区", "渝北区", "巴南区", "黔江区", "长寿区", "江津区",
"合川区", "永川区", "南川区", "璧山区", "铜梁区", "潼南区", "荣昌区", "开州区",
"梁平区", "武隆区", "城口县", "丰都县", "垫江县", "忠县", "云阳县", "奉节县",
"巫山县", "巫溪县", "石柱土家族自治县", "秀山土家族苗族自治县", "酉阳土家族苗族自治县",
"彭水苗族土家族自治县",
].map((label) => ({ label, value: label }));
/** 企业状态 */
export const ENTERPRISE_STATUS_OPTIONS = [
{ label: "正常", value: "正常" },
{ label: "停业", value: "停业" },
{ label: "注销", value: "注销" },
];
/** 企业规模 */
export const ENTERPRISE_SCALE_OPTIONS = [
{ label: "大", value: "大" },
{ label: "中", value: "中" },
{ label: "小", value: "小" },
{ label: "微型", value: "微型" },
];
/** 备案类型 */
export const FILING_TYPE_OPTIONS = [
{ label: "审核备案", value: "审核备案" },
{ label: "确认备案", value: "确认备案" },
];
/** 备案状态(机构信息) */
export const FILING_RECORD_STATUS_OPTIONS = [
{ label: "已备案", value: "已备案" },
{ label: "未备案", value: "未备案" },
];
/** 资质范围 / 证照类型(行业) */
export const QUALIFICATION_INDUSTRY_OPTIONS = [
{ label: "煤炭开采业", value: "煤炭开采业" },
{ label: "金属、非金属矿及其他矿采选业", value: "金属、非金属矿及其他矿采选业" },
{ label: "陆地石油和天然气开采业", value: "陆地石油和天然气开采业" },
{ label: "陆上油气管道运输业", value: "陆上油气管道运输业" },
{ label: "石油加工业,化学原料、化学品及医药制造业", value: "石油加工业,化学原料、化学品及医药制造业" },
{ label: "烟花爆竹制造业", value: "烟花爆竹制造业" },
{ label: "金属冶炼", value: "金属冶炼" },
];
/** 人员类型 */
export const PERSON_TYPE_OPTIONS = [
{ label: "基础人员", value: "基础人员" },
{ label: "专职评价师", value: "专职评价师" },
];
/** 职业等级 */
export const PROFESSIONAL_LEVEL_OPTIONS = [
{
label: "三级安全评价师(对应职业技能等级三级 / 高级工,入门级)",
value: "三级安全评价师(对应职业技能等级三级 / 高级工,入门级)",
},
{
label: "二级安全评价师(对应职业技能等级二级 / 技师,中级)",
value: "二级安全评价师(对应职业技能等级二级 / 技师,中级)",
},
{
label: "一级安全评价师(对应职业技能等级一级 / 高级技师,最高级)",
value: "一级安全评价师(对应职业技能等级一级 / 高级技师,最高级)",
},
];
/** 学历类型 */
export const EDUCATION_TYPE_OPTIONS = [
{ label: "全日制", value: "全日制" },
{ label: "在职教育", value: "在职教育" },
];
/** 学历层次 */
export const EDUCATION_LEVEL_OPTIONS = [
{ label: "专科", value: "专科" },
{ label: "本科", value: "本科" },
{ label: "硕士", value: "硕士" },
{ label: "博士", value: "博士" },
];
/** 是否注册安全工程师 */
export const REGISTER_ENGINEER_OPTIONS = [
{ label: "是", value: 1 },
{ label: "否", value: 2 },
];

View File

@ -25,7 +25,7 @@ if (!window.__POWERED_BY_QIANKUN__) {
// 本地开发时注入 Token使 API 请求可携带认证信息
sessionStorage.setItem(
"token",
"jjb-saas-auth:oauth:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJjbGllbnRJZFwiOlwiWlFBUVBKXCIsXCJhY2NvdW50SWRcIjoyMDY5Njc0MjM3ODc0MDEyMTYwLFwidXNlclR5cGVFbnVtXCI6XCJQTEFURk9STVwiLFwidXNlcklkXCI6MjA2OTY3NDIzNzE3MzQzNjQxNixcInRlbmFudElkXCI6MjA2OTU5NjM5NzkyMDg0OTkyMCxcInRlbmFudE5hbWVcIjpcIumHjeW6huWuieWFqOivhOS7t1wiLFwidGVuYW50UGFyZW50SWRzXCI6XCIwLDIwNjk1OTYzOTc5MjA4NDk5MjBcIixcIm5hbWVcIjpcInRlc3QwMVwiLFwiYWNjZXNzVGlja2V0XCI6XCJ2VnVmZjB1ck4xN21yT1BHTHFLcHBDV3BoRXAxTk96eTc5elp4THV1T3VLTUc5M1c4cENNd0NQZTN3MmlcIixcInJlZnJlc2hUaWNrZXRcIjpcIlhIeTRLZjJITXNYVjJCVHdnaWxUU0JXbDMxRGpObnVYVTBhUmRCZTlYRHpVTHVub2RMdnRveFpBeUFHclwiLFwiZXhwaXJlSW5cIjo2MDQ4MDAsXCJyZWZyZXNoRXhwaXJlc0luXCI6NjA0ODAwLFwib3JnSWRcIjoyMDY5NTk2Mzk3OTIwODQ5OTIwLFwib3JnTmFtZVwiOlwi6YeN5bqG5a6J5YWo6K-E5Lu3XCIsXCJvcmdJZHNcIjpbMjA2OTU5NjM5NzkyMDg0OTkyMF0sXCJyb2xlc1R5cGVzXCI6W1wiR0xZSlNcIl0sXCJyb2xlSWRzXCI6WzIwNjk2NzEzMDc1OTg4OTMwNThdLFwic2NvcGVzXCI6W10sXCJycGNUeXBlRW51bVwiOlwiSFRUUFwiLFwiYmluZE1vYmlsZVNpZ25cIjpcIkZBTFNFXCJ9IiwiaXNzIjoicHJvLXNlcnZlciIsImV4cCI6MTc4Mjg4ODYwNH0.Rypiw0-5EWQ8V_r7dr2A6ZHd82lwBcICX2t4WqybcJ0"
"jjb-saas-auth:oauth:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJjbGllbnRJZFwiOlwiWlFBUVBKXCIsXCJhY2NvdW50SWRcIjoyMDY5Njc0MjM3ODc0MDEyMTYwLFwidXNlclR5cGVFbnVtXCI6XCJQTEFURk9STVwiLFwidXNlcklkXCI6MjA2OTY3NDIzNzE3MzQzNjQxNixcInRlbmFudElkXCI6MjA2OTU5NjM5NzkyMDg0OTkyMCxcInRlbmFudE5hbWVcIjpcIumHjeW6huWuieWFqOivhOS7t1wiLFwidGVuYW50UGFyZW50SWRzXCI6XCIwLDIwNjk1OTYzOTc5MjA4NDk5MjBcIixcIm5hbWVcIjpcInRlc3QwMVwiLFwiYWNjZXNzVGlja2V0XCI6XCJ2Umg1RHRlNWJWcnc3NGxoZVgzRVVGYlVUV2l0MVJFbkVIc2ZWZnlGYnJwWDd1bmNsMGdsSmtJMWhvWWVcIixcInJlZnJlc2hUaWNrZXRcIjpcIlJva2VKeEcwYmIwYk1pMm8xUG53MjJpRkh6V21FSHVtWkFwSFI5dmptYmUyYm9DYkoyNnE3UmxIbUNlUFwiLFwiZXhwaXJlSW5cIjo2MDQ4MDAsXCJyZWZyZXNoRXhwaXJlc0luXCI6NjA0ODAwLFwib3JnSWRcIjoyMDY5NTk2Mzk3OTIwODQ5OTIwLFwib3JnTmFtZVwiOlwi6YeN5bqG5a6J5YWo6K-E5Lu3XCIsXCJvcmdJZHNcIjpbMjA2OTU5NjM5NzkyMDg0OTkyMF0sXCJyb2xlc1R5cGVzXCI6W1wiR0xZSlNcIl0sXCJyb2xlSWRzXCI6WzIwNjk2NzEzMDc1OTg4OTMwNThdLFwic2NvcGVzXCI6W10sXCJycGNUeXBlRW51bVwiOlwiSFRUUFwiLFwiYmluZE1vYmlsZVNpZ25cIjpcIkZBTFNFXCJ9IiwiaXNzIjoicHJvLXNlcnZlciIsImV4cCI6MTc4Mjk3MTE5Nn0.TagXXQBNyBioQQqhRFHwfgStlQmLi1s5StPOp-gSyzw"
);
}

View File

@ -7,12 +7,13 @@ import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO } from "~/enumerate/namespace";
import { asId, sameId } from "~/api/enterpriseInfo/idUtil";
import { safeListRequest, safeRequest } from "~/utils";
import PageHeader from "../components/PageHeader";
function findDeptNode(nodes = [], id) {
for (const node of nodes) {
if (String(node.id) === String(id)) {
if (sameId(node.id, id)) {
return node;
}
if (node.children?.length) {
@ -30,7 +31,7 @@ function toSelectedDept(node) {
return null;
}
return {
id: node.id,
id: asId(node.id),
deptName: node.deptName || node.title,
leaderAccount: node.leaderAccount,
leaderName: node.leaderName,
@ -71,7 +72,7 @@ function DepartmentPositionPage(props) {
form: positionForm,
transform: (formData) => ({
...formData,
eqDeptId: selectedDept?.id != null ? Number(selectedDept.id) : undefined,
eqDeptId: asId(selectedDept?.id),
}),
},
);
@ -136,7 +137,7 @@ function DepartmentPositionPage(props) {
const res = await props.orgDepartmentRemove({ id });
if (res?.success !== false) {
message.success("删除成功");
if (String(selectedDept?.id) === String(id)) {
if (sameId(selectedDept?.id, id)) {
setSelectedDept(null);
}
loadTree();
@ -174,7 +175,7 @@ function DepartmentPositionPage(props) {
};
const openDeptModal = (record) => {
setCurrentId(record?.id || "");
setCurrentId(asId(record?.id) || "");
setDeptModalOpen(true);
};
@ -183,17 +184,17 @@ function DepartmentPositionPage(props) {
message.warning("请先选择部门");
return;
}
setCurrentId(record?.id || "");
setCurrentId(asId(record?.id) || "");
modalForm.resetFields();
if (record) {
modalForm.setFieldsValue({
...record,
deptId: record.deptId || selectedDept.id,
deptId: asId(record.deptId || selectedDept.id),
deptName: record.deptName || selectedDept.deptName,
});
}
else {
modalForm.setFieldsValue({ deptId: selectedDept.id, deptName: selectedDept.deptName });
modalForm.setFieldsValue({ deptId: asId(selectedDept.id), deptName: selectedDept.deptName });
}
setPositionModalOpen(true);
};
@ -204,7 +205,7 @@ function DepartmentPositionPage(props) {
if (currentId) {
payload.id = currentId;
}
payload.deptId = values.deptId || selectedDept?.id;
payload.deptId = asId(values.deptId || selectedDept?.id);
const res = await request(payload);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
@ -232,7 +233,7 @@ function DepartmentPositionPage(props) {
/>
</Space>
<Tree
selectedKeys={selectedDept?.id ? [selectedDept.id] : []}
selectedKeys={selectedDept?.id ? [asId(selectedDept.id)] : []}
treeData={filteredTree}
fieldNames={{ title: "deptName", key: "id", children: "children" }}
onSelect={(_, { node }) => {

View File

@ -8,11 +8,11 @@ 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 { NS_EQUIP_INFO } from "~/enumerate/namespace";
import { isEquipEnabled } from "~/api/enterpriseInfo/adapter";
import { safeListRequest, safeRequest } from "~/utils";
import { positiveNumberRule } from "~/utils/validators";
import PageHeader from "../components/PageHeader";
const ENABLE_STATUS = { 1: "启用", 0: "禁用" };
const DUAL_CHANNEL = { 1: "是", 0: "否" };
function EquipInfoPage(props) {
@ -43,17 +43,21 @@ function EquipInfoPage(props) {
});
};
const onToggleStatus = (id, enableStatus) => {
const onToggleStatus = (id, record) => {
const enabled = isEquipEnabled(record);
Modal.confirm({
title: "提示",
content: enableStatus === 1 ? "是否停用" : "是否启用",
content: enabled ? "是否停用" : "是否启用",
okText: "是",
cancelText: "否",
onOk: async () => {
const res = await props.equipInfoToggleStatus({ id });
if (res?.success !== false) {
message.success("操作成功");
getData();
await getData();
}
else {
message.error(res?.message || "操作失败");
}
},
});
@ -75,12 +79,13 @@ function EquipInfoPage(props) {
name: "enableStatus",
label: "设备状态",
render: FORM_ITEM_RENDER_ENUM.SELECT,
options: Object.entries(ENABLE_STATUS).map(([value, label]) => ({ label, value: Number(value) })),
options: [{ label: "启用", value: 1 }, { label: "禁用", value: 0 }],
},
]}
onFinish={getData}
/>
<Table
{...tableProps}
toolBarRender={() => (
<Button
type="primary"
@ -103,8 +108,8 @@ function EquipInfoPage(props) {
{ title: "校准初值", dataIndex: "calibrationInitialValue" },
{
title: "设备状态",
dataIndex: "enableStatus",
render: (v) => ENABLE_STATUS[v] ?? "-",
width: 90,
render: (_, record) => (isEquipEnabled(record) ? "启用" : "禁用"),
},
{
title: "操作",
@ -117,15 +122,14 @@ function EquipInfoPage(props) {
<Button type="link" onClick={() => { setCurrentId(record.id); setFormModalOpen(true); }}>
编辑
</Button>
<Button type="link" onClick={() => onToggleStatus(record.id, record.enableStatus)}>
{record.enableStatus === 1 ? "禁用" : "启用"}
<Button type="link" onClick={() => onToggleStatus(record.id, record)}>
{isEquipEnabled(record) ? "禁用" : "启用"}
</Button>
<Button danger type="link" onClick={() => onDelete(record.id)}>删除</Button>
</Space>
),
},
]}
{...tableProps}
/>
{formModalOpen && (

View File

@ -3,9 +3,20 @@ 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,
@ -28,7 +39,7 @@ function OrgInfoPage(props) {
const loadDetail = async () => {
try {
const res = await props.orgInfoGet().catch(() => null);
const res = getOrgInfoDetail();
if (res?.data?.id) {
setDetail(res.data);
form.setFieldsValue(res.data);
@ -39,6 +50,11 @@ function OrgInfoPage(props) {
setDetail({});
setHasExistingData(false);
setEditing(true);
form.setFieldsValue({
filingType: "确认备案",
filingRecordStatus: "未备案",
attachments: mockUploadFileList("资质申请书.pdf"),
});
}
}
catch (err) {
@ -118,9 +134,10 @@ function OrgInfoPage(props) {
rules: [{ required: true, message: "请输入安全生产监管行业类别" }],
},
{
name: "regionCountyName",
label: "所属(县、区)",
rules: [{ required: true, message: "请输入所属县区" }],
...formSelectField("regionCountyName", "属地", CHONGQING_DISTRICTS, {
rules: [{ required: true, message: "请选择属地" }],
colProps: { span: 12 },
}),
},
{
name: "regionStreetName",
@ -259,6 +276,18 @@ function OrgInfoPage(props) {
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 (

View File

@ -1,12 +1,23 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, DatePicker, Descriptions, Form, Input, message, Modal, Row, Col, Select, Space } from "antd";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useRef, useState } from "react";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
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 Upload from "zy-react-library/components/Upload";
import useTable from "zy-react-library/hooks/useTable";
import { NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO } from "~/enumerate/namespace";
import {
EDUCATION_LEVEL_OPTIONS,
EDUCATION_TYPE_OPTIONS,
PERSON_TYPE_OPTIONS,
PROFESSIONAL_LEVEL_OPTIONS,
QUALIFICATION_INDUSTRY_OPTIONS,
REGISTER_ENGINEER_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { ensureOrgContext, fetchOrgDepartmentPage, fetchOrgPositionPage } from "~/api/enterpriseInfo/orgBootstrap";
import { asId, sameId } from "~/api/enterpriseInfo/idUtil";
import { getBirthDateFromIdCard, safeListRequest, safeRequest } from "~/utils";
import { toDayjs } from "~/utils/dateFormat";
import { formSelectField } from "~/utils/enterpriseForm";
@ -14,6 +25,15 @@ import { idCardRule, mobileRule } from "~/utils/validators";
import PageHeader from "../../components/PageHeader";
const GENDER_MAP = { 1: "男", 2: "女" };
const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" };
function mapPositionOptions(list = []) {
return list.map((p) => ({
label: p.positionName,
value: asId(p.id),
deptId: asId(p.deptId),
}));
}
function PersonnelInfoPage(props) {
const [formModalOpen, setFormModalOpen] = useState(false);
@ -24,23 +44,27 @@ function PersonnelInfoPage(props) {
const [positionOptions, setPositionOptions] = useState([]);
useEffect(() => {
let cancelled = false;
(async () => {
try {
await ensureOrgContext();
const [deptRes, posRes] = await Promise.all([
props.orgDepartmentList?.({ pageIndex: 1, pageSize: 200 }),
props.orgPositionList?.({ pageIndex: 1, pageSize: 200 }),
fetchOrgDepartmentPage({ pageIndex: 1, pageSize: 200 }),
fetchOrgPositionPage({ pageIndex: 1, pageSize: 200 }),
]);
setDeptOptions((deptRes?.data || []).map((d) => ({ label: d.deptName, value: d.id })));
setPositionOptions((posRes?.data || []).map((p) => ({
label: p.positionName,
value: p.id,
deptId: p.deptId,
})));
if (cancelled) {
return;
}
setDeptOptions((deptRes?.data || []).map((d) => ({ label: d.deptName, value: asId(d.id) })));
setPositionOptions(mapPositionOptions(posRes?.data));
}
catch (err) {
console.warn("[PersonnelInfo] load dept/position options failed:", err);
}
})();
return () => {
cancelled = true;
};
}, []);
const { tableProps, getData } = useTable(safeListRequest(props.staffInfoList), {
@ -132,17 +156,17 @@ function PersonnelInfoPage(props) {
width: 320,
render: (_, record) => (
<Space wrap>
<Button type="link" onClick={() => { setCurrentId(record.id); setViewModalOpen(true); }}>
<Button type="link" onClick={() => { setCurrentId(asId(record.id)); setViewModalOpen(true); }}>
查看
</Button>
<Button type="link" onClick={() => { setCurrentId(record.id); setFormModalOpen(true); }}>
<Button type="link" onClick={() => { setCurrentId(asId(record.id)); setFormModalOpen(true); }}>
编辑
</Button>
<Button type="link" onClick={() => goCertificate(record.id, record.staffName)}>
<Button type="link" onClick={() => goCertificate(asId(record.id), record.staffName)}>
证书
</Button>
<Button type="link" onClick={() => onResetPassword(record.id)}>重置密码</Button>
<Button danger type="link" onClick={() => onDelete(record.id)}>删除</Button>
<Button type="link" onClick={() => onResetPassword(asId(record.id))}>重置密码</Button>
<Button danger type="link" onClick={() => onDelete(asId(record.id))}>删除</Button>
</Space>
),
},
@ -158,7 +182,6 @@ function PersonnelInfoPage(props) {
requestEdit={props.staffInfoEdit}
requestDetails={props.staffInfoGet}
deptOptions={deptOptions}
positionOptions={positionOptions}
onCancel={() => {
setFormModalOpen(false);
setCurrentId("");
@ -182,36 +205,100 @@ function PersonnelInfoPage(props) {
}
function StaffFormModal({
open, currentId, requestAdd, requestEdit, requestDetails, deptOptions, positionOptions, onCancel, onSuccess,
open, currentId, requestAdd, requestEdit, requestDetails, deptOptions, onCancel, onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [deptPositionOptions, setDeptPositionOptions] = useState([]);
const [positionLoading, setPositionLoading] = useState(false);
const watchedDeptId = Form.useWatch("deptId", form);
const wasOpenRef = useRef(false);
const inflightDeptRef = useRef(null);
const requestDetailsRef = useRef(requestDetails);
requestDetailsRef.current = requestDetails;
const filteredPositionOptions = useMemo(() => {
if (!watchedDeptId) {
return positionOptions;
const loadPositionsByDept = async (deptId, currentStaff = null) => {
const id = asId(deptId);
if (!id) {
setDeptPositionOptions([]);
return [];
}
return positionOptions.filter((p) => p.deptId === watchedDeptId);
}, [positionOptions, watchedDeptId]);
const key = id;
if (inflightDeptRef.current === key) {
return deptPositionOptions;
}
inflightDeptRef.current = key;
setPositionLoading(true);
try {
let res = await fetchOrgPositionPage({
pageIndex: 1,
pageSize: 200,
eqDeptId: id,
});
let options = mapPositionOptions(res?.data);
// 兼容历史岗位 dept_id 因 Number() 精度丢失与部门 id 不完全一致
if (!options.length) {
const allRes = await fetchOrgPositionPage({ pageIndex: 1, pageSize: 500 });
options = mapPositionOptions(allRes?.data).filter((p) => sameId(p.deptId, id));
}
const positionId = asId(currentStaff?.positionId);
const positionName = currentStaff?.positionName;
if (positionId && positionName && !options.some((item) => sameId(item.value, positionId))) {
options = [{ label: positionName, value: positionId, deptId: id }, ...options];
}
setDeptPositionOptions(options);
return options;
}
catch (err) {
console.warn("[PersonnelInfo] load positions by dept failed:", err);
setDeptPositionOptions([]);
return [];
}
finally {
if (inflightDeptRef.current === key) {
inflightDeptRef.current = null;
}
setPositionLoading(false);
}
};
useEffect(() => {
if (!open) {
return;
if (open && !wasOpenRef.current) {
if (!currentId) {
form.resetFields();
setDeptPositionOptions([]);
form.setFieldsValue({
personType: "基础人员",
registerEngineerFlag: 2,
});
}
}
if (!currentId) {
form.resetFields();
if (!open) {
setDeptPositionOptions([]);
}
wasOpenRef.current = open;
}, [open, currentId, form]);
useEffect(() => {
if (!open || !currentId) {
return;
}
let cancelled = false;
setDetailLoading(true);
safeRequest(requestDetails, { id: currentId }).then((res) => {
safeRequest(requestDetailsRef.current, { id: currentId }).then(async (res) => {
if (!cancelled && res?.data) {
form.setFieldsValue({
...res.data,
birthDate: toDayjs(res.data.birthDate),
});
const data = res.data;
await loadPositionsByDept(data.deptId, data);
if (!cancelled) {
form.setFieldsValue({
...data,
deptId: asId(data.deptId),
positionId: asId(data.positionId),
birthDate: toDayjs(data.birthDate),
proofMaterials: data.proofMaterials?.length ? data.proofMaterials : [],
});
}
}
}).finally(() => {
if (!cancelled) {
@ -225,6 +312,7 @@ function StaffFormModal({
const handleCancel = () => {
form.resetFields();
setDeptPositionOptions([]);
onCancel();
};
@ -236,11 +324,8 @@ function StaffFormModal({
}
}
if ("deptId" in changed) {
const posId = form.getFieldValue("positionId");
const stillValid = positionOptions.some((p) => p.value === posId && p.deptId === changed.deptId);
if (!stillValid) {
form.setFieldValue("positionId", undefined);
}
form.setFieldValue("positionId", undefined);
loadPositionsByDept(changed.deptId);
}
};
@ -316,7 +401,9 @@ function StaffFormModal({
<Form.Item name="positionId" label="岗位" rules={[{ required: true, message: "请选择岗位" }]}>
<Select
placeholder="请选择岗位"
options={filteredPositionOptions}
options={deptPositionOptions}
loading={positionLoading}
disabled={!watchedDeptId}
allowClear
showSearch
optionFilterProp="label"
@ -329,8 +416,63 @@ function StaffFormModal({
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="education" label="学历">
<Input placeholder="请输入学历" />
<Form.Item name="personType" label="人员类型" initialValue="基础人员">
<Select placeholder="请选择人员类型" options={PERSON_TYPE_OPTIONS} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="qualScope" label="资质范围">
<Select placeholder="请选择资质范围" allowClear options={QUALIFICATION_INDUSTRY_OPTIONS} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="professionalLevel" label="职业等级">
<Select placeholder="请选择职业等级" allowClear options={PROFESSIONAL_LEVEL_OPTIONS} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="evaluatorCertNo" label="证书编号">
<Input placeholder="请输入安全评价师证书编号" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="educationType" label="学历类型">
<Select placeholder="请选择学历类型" allowClear options={EDUCATION_TYPE_OPTIONS} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="educationLevel" label="学历层次">
<Select placeholder="请选择学历层次" allowClear options={EDUCATION_LEVEL_OPTIONS} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="titleName" label="职称">
<Input placeholder="请输入职称" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="registerEngineerFlag" label="是否注册安全工程师" initialValue={2}>
<Select placeholder="请选择" options={REGISTER_ENGINEER_OPTIONS} />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="publications" label="出版学术专著、专利、获奖、发表学术论文等">
<Input.TextArea rows={2} placeholder="请输入" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="abilityDeclaration" label="自我申报的专业能力及认定方式">
<Input.TextArea rows={2} placeholder="请输入" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="workExperience" label="主要学习工作经历">
<Input.TextArea rows={3} placeholder="请输入" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="proofMaterials" label="申报专业能力证明材料">
<Upload maxCount={5} />
</Form.Item>
</Col>
<Col span={12}>
@ -402,12 +544,34 @@ function StaffViewModal({ open, currentId, requestDetails, onCancel }) {
<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="学历">{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>
);

View File

@ -13,6 +13,9 @@ import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useTable from "zy-react-library/hooks/useTable";
import { NS_ORG_QUALIFICATION_CERT } from "~/enumerate/namespace";
import { QUALIFICATION_INDUSTRY_OPTIONS } from "~/enumerate/enterpriseOptions";
import { formSelectField } from "~/utils/enterpriseForm";
import { isQualificationEnabled } from "~/api/enterpriseInfo/adapter";
import { safeGetFiles, safeListRequest, safeRequest } from "~/utils";
import { toDayjs } from "~/utils/dateFormat";
import { mockUploadFileList, resolveUploadFileId } from "~/utils/mockUpload";
@ -59,7 +62,10 @@ function QualificationCertPage(props) {
const res = await props.orgQualificationCertDisable({ id });
if (res?.success !== false) {
message.success("操作成功");
getData();
await getData();
}
else {
message.error(res?.message || "操作失败");
}
},
});
@ -75,7 +81,10 @@ function QualificationCertPage(props) {
const res = await props.orgQualificationCertEnable({ id });
if (res?.success !== false) {
message.success("操作成功");
getData();
await getData();
}
else {
message.error(res?.message || "操作失败");
}
},
});
@ -100,6 +109,7 @@ function QualificationCertPage(props) {
onFinish={getData}
/>
<Table
{...tableProps}
toolBarRender={() => (
<Button
type="primary"
@ -123,9 +133,8 @@ function QualificationCertPage(props) {
{ title: "证书编号", dataIndex: "certNo" },
{
title: "状态",
dataIndex: "enableStatus",
width: 80,
render: (v) => (v === 0 ? "禁用" : "启用"),
render: (_, record) => (isQualificationEnabled(record) ? "启用" : "禁用"),
},
{
title: "照片",
@ -162,20 +171,19 @@ function QualificationCertPage(props) {
<Button danger type="link" onClick={() => onDelete(record.id)}>
删除
</Button>
{record.enableStatus === 0 ? (
<Button type="link" onClick={() => onEnable(record.id)}>
启用
</Button>
) : (
{isQualificationEnabled(record) ? (
<Button type="link" onClick={() => onDisable(record.id)}>
禁用
</Button>
) : (
<Button type="link" onClick={() => onEnable(record.id)}>
启用
</Button>
)}
</Space>
),
},
]}
{...tableProps}
/>
{addModalOpen && (
@ -316,11 +324,10 @@ function CertFormModal({
showActionButtons={false}
onFinish={handleSubmit}
options={[
{
name: "certType",
label: "证照类型",
formSelectField("certType", "证照类型", QUALIFICATION_INDUSTRY_OPTIONS, {
rules: [{ required: true, message: "请选择证照类型" }],
},
colProps: { span: 24 },
}),
{
name: "certName",
label: "证书名称",

View File

@ -7,6 +7,7 @@ import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import Upload from "zy-react-library/components/Upload";
import useTable from "zy-react-library/hooks/useTable";
import { asId } from "~/api/enterpriseInfo/idUtil";
import { NS_STAFF_INFO, NS_STAFF_RESIGNATION_APPLY } from "~/enumerate/namespace";
import { safeListRequest, safeRequest } from "~/utils";
import { formSelectField, getResignAuditStatusLabel } from "~/utils/enterpriseForm";
@ -34,7 +35,7 @@ function ResignationApplyPage(props) {
if (!cancelled) {
setStaffOptions((res?.data || []).map((s) => ({
label: `${s.staffName}${s.account}`,
value: String(s.id),
value: asId(s.id),
staffName: s.staffName,
account: s.account,
})));
@ -96,7 +97,7 @@ function ResignationApplyPage(props) {
<Button
type="link"
onClick={() => {
setCurrentId(record.id);
setCurrentId(asId(record.id));
setViewModalOpen(true);
}}
>
@ -151,6 +152,8 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
const handleSubmit = async (values) => {
try {
setSubmitting(true);
const staff = staffOptions.find((s) => String(s.value) === String(values.personnelId));
values.applicantName = staff?.staffName || values.applicantName;
values.attachmentId = resolveUploadFileId(values.resignReport);
const res = await requestAdd(values);
if (res?.success !== false) {

View File

@ -1,4 +1,55 @@
import { message, Spin } from "antd";
import { useEffect, useState } from "react";
import {
ensureOrgContext,
isOrgInfoPage,
ORG_INFO_PAGE_PATH,
} from "~/api/enterpriseInfo/orgBootstrap";
/**
* 企业信息模块入口进入时 getInfo有机构则缓存 orgInfoId无机构则仅允许访问机构信息管理页
*/
function EnterpriseInfo(props) {
const [checking, setChecking] = useState(true);
const onOrgInfoPage = isOrgInfoPage();
useEffect(() => {
let cancelled = false;
ensureOrgContext({ force: true })
.then((ctx) => {
if (cancelled) {
return;
}
if (ctx?.networkError && onOrgInfoPage) {
message.warning("机构信息加载失败,请确认后端服务已启动");
}
if (!ctx?.hasOrg && !onOrgInfoPage) {
message.warning("请先完善机构信息");
window.location.replace(ORG_INFO_PAGE_PATH);
return;
}
setChecking(false);
})
.catch((err) => {
console.warn("[EnterpriseInfo] ensureOrgContext failed:", err);
if (!cancelled) {
if (onOrgInfoPage) {
message.warning("机构信息加载失败,请确认后端服务已启动");
}
setChecking(false);
}
});
return () => {
cancelled = true;
};
}, [onOrgInfoPage]);
if (checking) {
return <Spin fullscreen tip="正在加载机构信息..." />;
}
return (
<div>
{props.children}

View File

@ -11,6 +11,33 @@ export function resolveUploadFileId(_files) {
return DEFAULT_UPLOAD_FILE_URL;
}
/** 多附件:逗号拼接 URL */
export function resolveUploadFileIds(files) {
if (!files?.length) {
return undefined;
}
const urls = files.map((file) => file?.url || file?.response?.url).filter(Boolean);
if (!urls.length) {
return DEFAULT_UPLOAD_FILE_URL;
}
return urls.join(",");
}
export function parseUploadFileList(urls, defaultName = "附件.pdf") {
if (!urls) {
return [];
}
return String(urls)
.split(",")
.map((url) => url.trim())
.filter(Boolean)
.map((url, index) => ({
name: `${defaultName.replace(".pdf", "")}${index + 1}.pdf`,
fileName: `${defaultName.replace(".pdf", "")}${index + 1}.pdf`,
url,
}));
}
export function mockUploadFileList(name = "附件.pdf") {
return [{ name, fileName: name, url: DEFAULT_UPLOAD_FILE_URL }];
}