diff --git a/src/api/enterpriseInfo/adapter.js b/src/api/enterpriseInfo/adapter.js index 5aa4e91..dd2c0ad 100644 --- a/src/api/enterpriseInfo/adapter.js +++ b/src/api/enterpriseInfo/adapter.js @@ -1,10 +1,13 @@ /** 企业信息管理:前后端字段与分页格式适配 */ import { formatDate, formatDateTime, toApiDate, toApiDateTime } from "../../utils/dateFormat"; +import { asId, isIdFieldName, normalizeQueryIds } from "./idUtil"; +import { withOrgId } from "./orgContext"; const GENDER_NAME = { 1: "男", 2: "女" }; const RESIGN_AUDIT_STATUS_NAME = { 0: "未审核", 1: "已审核", 2: "已退回" }; +/** 分页查询参数:子表 org_id → org_info.id;机构信息管理 getInfo 不走此方法 */ export function toPageQuery(params = {}, fieldMap = {}) { const query = { current: params.pageIndex ?? params.current ?? 1, @@ -13,10 +16,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 +48,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 +58,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, @@ -125,8 +128,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 +151,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 +169,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 +179,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,13 +192,13 @@ 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, @@ -212,13 +215,14 @@ export function toStaffForm(data = {}) { export function fromStaffForm(values = {}) { const genderCode = values.gender; 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, @@ -238,8 +242,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 +263,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, @@ -281,7 +285,7 @@ export function toQualificationForm(data = {}) { ? [{ 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, @@ -324,8 +328,7 @@ export function fromQualificationForm(values = {}) { export function toEquipForm(data = {}) { const instrumentType = data.instrumentTypeName ?? data.instrumentTypeCode; return { - id: data.id, - instrumentCategory: instrumentType, + id: asId(data.id), instrumentType, deviceType: data.deviceTypeName ?? data.deviceTypeCode, deviceName: data.deviceName, @@ -368,8 +371,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 +382,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 +399,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 +416,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 +435,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 +445,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), diff --git a/src/api/enterpriseInfo/http.js b/src/api/enterpriseInfo/http.js index 331d638..7f17b50 100644 --- a/src/api/enterpriseInfo/http.js +++ b/src/api/enterpriseInfo/http.js @@ -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); diff --git a/src/api/enterpriseInfo/idUtil.js b/src/api/enterpriseInfo/idUtil.js new file mode 100644 index 0000000..16ae3f5 --- /dev/null +++ b/src/api/enterpriseInfo/idUtil.js @@ -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; diff --git a/src/api/enterpriseInfo/orgBootstrap.js b/src/api/enterpriseInfo/orgBootstrap.js new file mode 100644 index 0000000..3aaeab9 --- /dev/null +++ b/src/api/enterpriseInfo/orgBootstrap.js @@ -0,0 +1,57 @@ +/** + * 企业信息模块初始化与下拉数据拉取(绕过 DVA,避免 loading 状态导致页面重渲染死循环) + */ + +import { + fromPageResponse, + fromSingleResponse, + toDepartmentForm, + toOrgInfoForm, + toPageQuery, + toPositionForm, +} from "./adapter"; +import { apiGet } from "./http"; +import { getOrgInfoId, setOrgInfoId } from "./orgContext"; + +let ensureOrgPromise = null; + +/** 确保已缓存机构 id(全局只发起一次 getInfo) */ +export function ensureOrgContext() { + const cached = getOrgInfoId(); + if (cached) { + return Promise.resolve(cached); + } + if (!ensureOrgPromise) { + ensureOrgPromise = apiGet("/safety-eval/org-info/getInfo", {}, {}, { includeOrgContext: false }) + .then((res) => { + const result = fromSingleResponse(res, toOrgInfoForm); + const id = result?.data?.id ?? res?.data?.id; + if (id) { + setOrgInfoId(id); + } + return getOrgInfoId(); + }) + .catch(() => null) + .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); +} diff --git a/src/api/enterpriseInfo/orgContext.js b/src/api/enterpriseInfo/orgContext.js new file mode 100644 index 0000000..17e11c6 --- /dev/null +++ b/src/api/enterpriseInfo/orgContext.js @@ -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; +} diff --git a/src/api/orgDepartment/index.js b/src/api/orgDepartment/index.js index a312512..cfeaf7a 100644 --- a/src/api/orgDepartment/index.js +++ b/src/api/orgDepartment/index.js @@ -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, diff --git a/src/api/orgInfo/index.js b/src/api/orgInfo/index.js index 0104b2b..c266951 100644 --- a/src/api/orgInfo/index.js +++ b/src/api/orgInfo/index.js @@ -1,35 +1,52 @@ import { declareRequest } from "@cqsjjb/jjb-dva-runtime"; import { fromOrgInfoForm, - fromPageResponse, fromSingleResponse, toOrgInfoForm, } from "../enterpriseInfo/adapter"; import { apiGet, apiPost, safeAction } from "../enterpriseInfo/http"; +import { setOrgInfoId } from "../enterpriseInfo/orgContext"; + +function persistOrgInfoId(result, rawData) { + const id = result?.data?.id ?? rawData?.id; + if (id) { + setOrgInfoId(id); + } + 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; })); diff --git a/src/main.js b/src/main.js index ecdcb38..2fb5044 100644 --- a/src/main.js +++ b/src/main.js @@ -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" ); } diff --git a/src/pages/Container/EnterpriseInfo/DepartmentPosition/index.js b/src/pages/Container/EnterpriseInfo/DepartmentPosition/index.js index 83dd297..02d1fbd 100644 --- a/src/pages/Container/EnterpriseInfo/DepartmentPosition/index.js +++ b/src/pages/Container/EnterpriseInfo/DepartmentPosition/index.js @@ -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) { /> { diff --git a/src/pages/Container/EnterpriseInfo/PersonnelInfo/List/index.js b/src/pages/Container/EnterpriseInfo/PersonnelInfo/List/index.js index 3705821..f078349 100644 --- a/src/pages/Container/EnterpriseInfo/PersonnelInfo/List/index.js +++ b/src/pages/Container/EnterpriseInfo/PersonnelInfo/List/index.js @@ -1,12 +1,14 @@ 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 useTable from "zy-react-library/hooks/useTable"; import { NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO } from "~/enumerate/namespace"; +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"; @@ -15,6 +17,14 @@ import PageHeader from "../../components/PageHeader"; const GENDER_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); const [viewModalOpen, setViewModalOpen] = useState(false); @@ -24,23 +34,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 +146,17 @@ function PersonnelInfoPage(props) { width: 320, render: (_, record) => ( - - - - - + + ), }, @@ -158,7 +172,6 @@ function PersonnelInfoPage(props) { requestEdit={props.staffInfoEdit} requestDetails={props.staffInfoGet} deptOptions={deptOptions} - positionOptions={positionOptions} onCancel={() => { setFormModalOpen(false); setCurrentId(""); @@ -182,36 +195,83 @@ 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; - } - return positionOptions.filter((p) => p.deptId === watchedDeptId); - }, [positionOptions, watchedDeptId]); - - useEffect(() => { - if (!open) { + const loadPositionsByDept = async (deptId) => { + const id = asId(deptId); + if (!id) { + setDeptPositionOptions([]); return; } - if (!currentId) { - form.resetFields(); + const key = id; + if (inflightDeptRef.current === key) { + return; + } + 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)); + } + setDeptPositionOptions(options); + } + catch (err) { + console.warn("[PersonnelInfo] load positions by dept failed:", err); + setDeptPositionOptions([]); + } + finally { + if (inflightDeptRef.current === key) { + inflightDeptRef.current = null; + } + setPositionLoading(false); + } + }; + + useEffect(() => { + if (open && !wasOpenRef.current) { + if (!currentId) { + form.resetFields(); + setDeptPositionOptions([]); + } + } + 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((res) => { if (!cancelled && res?.data) { form.setFieldsValue({ ...res.data, birthDate: toDayjs(res.data.birthDate), }); + loadPositionsByDept(res.data.deptId); } }).finally(() => { if (!cancelled) { @@ -225,6 +285,7 @@ function StaffFormModal({ const handleCancel = () => { form.resetFields(); + setDeptPositionOptions([]); onCancel(); }; @@ -236,11 +297,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 +374,9 @@ function StaffFormModal({