import Vue from "vue"; import { get, postJ, putJ, deleteApi } from "@/utils/request"; import { formatDate } from "@/utils/hr/format"; /* ========================================================================= * 字典 / 选项(与 PC 端 kd-aiot-frontend-hr 员工档案表单口径一致) * ========================================================================= */ /** * 员工状态:1 正式在职 / 6 试用期 / 7 待岗 / 8 已离职 * 口径来源:hr_user_status 字典实测(与 PC kd-aiot-frontend-hr 的 * utils/employee-status-dict.js 一致;swagger 注释「1全职…6离职」与实际数据不符)。 * 运行时以字典为准,这里只作字典拉取失败时的兜底,避免静默串码。 */ export const EMPLOYEE_STATUS_OPTIONS = [ { value: 1, label: "正式在职" }, { value: 6, label: "试用期" }, { value: 7, label: "待岗" }, { value: 8, label: "已离职" }, ]; export const EMPLOYEE_STATUS_LABEL = EMPLOYEE_STATUS_OPTIONS.reduce((acc, item) => { acc[item.value] = item.label; return acc; }, {}); export const SEX_CODE = { 男: 1, 女: 2, 未知: 0 }; export const SEX_OPTIONS = ["男", "女", "未知"]; export function sexLabel(value) { const map = { 1: "男", 2: "女", 0: "未知" }; return map[value] != null ? map[value] : value == null ? "" : String(value); } export const MARITAL_OPTIONS = ["未婚", "已婚", "离异", "丧偶"]; export const EDUCATION_OPTIONS = ["小学", "初中", "高中", "中专", "大专", "本科", "硕士", "博士"]; export const DEGREE_OPTIONS = ["无", "学士", "硕士", "博士"]; export const ETHNIC_OPTIONS = ["汉族", "土家族", "苗族", "侗族", "回族", "瑶族", "壮族", "满族", "其他"]; export const SOCIAL_SECURITY_OPTIONS = ["四险", "四险一金", "五险", "五险一金", "六险一金"]; export const STOP_REASON_OPTIONS = ["离职", "调转", "其他"]; /* 业务记录枚举:后端原始码 ↔ 中文(与 PC 端 userChange/regularization/resignation/salarySlip 映射一致) */ export const CHANGE_TYPE_LABELS = { DEPARTMENT_TRANSFER: "部门调动", POSITION_TRANSFER: "岗位调动", GRADE_CHANGE: "职级变更", PROMOTION: "晋升", DEMOTION: "降职", SALARY_CHANGE: "薪资变更", MANAGER_CHANGE: "上级变更", WORK_LOCATION_CHANGE: "工作地点变更", FACTORY_TRANSFER: "工厂调动", LEGAL_ENTITY_TRANSFER: "法人实体转移", EMPLOYMENT_TYPE_CHANGE: "雇佣类型变更", COMPOSITE_CHANGE: "复合变更", }; export const APPROVAL_STATUS_LABELS = { DRAFT: "草稿", PENDING: "审批中", APPROVING: "审批中", APPROVED: "已通过", REJECTED: "已驳回", WITHDRAWN: "已撤回", CANCELLED: "已取消", }; export const EFFECTIVE_STATUS_LABELS = { PENDING: "待生效", PENDING_EFFECTIVE: "待生效", SCHEDULED: "已排期", EFFECTIVE: "已生效", INVALID: "已失效", FAILED: "生效失败", CANCELLED: "已取消", }; export const REGULARIZATION_TYPE_LABELS = { NORMAL: "按期转正", EARLY: "提前转正", EXTENDED: "延期转正", }; export const REGULARIZATION_RESULT_LABELS = { NORMAL: "按期转正", EARLY: "提前转正", EXTENDED: "延期", FAILED: "不通过", }; export const RESIGNATION_TYPE_LABELS = { VOLUNTARY: "主动离职", NEGOTIATED: "协商解除", PROBATION_FAILED: "试用不合格", PERFORMANCE_FAILED: "绩效不合格", DISMISSED: "辞退", CONTRACT_EXPIRED: "合同到期", RETIREMENT: "退休", OTHER: "其他", }; export const PAYSLIP_STATUS_LABELS = { PUBLISHED: "已发布", VIEWED: "已查看", CONFIRMED: "已确认", APPEALED: "已申诉", CORRECTED: "已更正", CLOSED: "已关闭", }; /** 合同状态 / 签署状态枚举(对齐 PC src/api/hr/contractStatus.js,避免详情里显示英文码) */ export const CONTRACT_STATUS_LABELS = { DRAFT: "草稿", PENDING_APPROVAL: "审批中", PENDING_SIGN: "待签署", SIGNED_WAIT_EFFECTIVE: "已签待生效", EFFECTIVE: "生效", EXPIRING: "临期", EXPIRED: "已到期", RENEWED: "已续签", CHANGED: "已变更", TERMINATED: "已解除", ENDED: "已终止", CANCELLED: "已作废", SIGNING: "签署中", PENDING_EFFECTIVE: "已签待生效", VOIDED: "已作废", ARCHIVED: "已归档", }; export const CONTRACT_SIGNING_STATUS_LABELS = { NOT_STARTED: "未签署", COMPLETED: "已签署", REJECTED: "已拒签", EXPIRED: "已过期", CANCELLED: "已取消", UNSIGNED: "未签署", PENDING: "待签署", PENDING_SIGN: "待签署", SIGNING: "签署中", SIGNED: "已签署", }; /** 由出生日期算年龄(接口不返回 age 时兜底,对齐 PC 的 calculateAge) */ export function calcAgeByBirthday(birthday) { const text = String(birthday == null ? "" : birthday).replace(/-/g, "/").slice(0, 10); if (!text) return ""; const date = new Date(text); if (isNaN(date.getTime())) return ""; const now = new Date(); let age = now.getFullYear() - date.getFullYear(); const monthDiff = now.getMonth() - date.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < date.getDate())) age -= 1; return age >= 0 && age < 130 ? String(age) : ""; } /** 证照类型:名称 ↔ 后端编码 */ export const CERT_TYPE_CODE = { 身份证: 1, 驾驶证: 2, 军官证: 3, 护照: 4, 特种作业证: 5, 职称证: 6, 职业资格证: 7, 特种设备操作证: 8, 其他: 9, }; export const CERT_TYPE_OPTIONS = Object.keys(CERT_TYPE_CODE); const CERT_LEVEL_CODE = { 初级: "1", 中级: "2", 高级: "3" }; const CERT_LEVEL_LABEL = { 1: "初级", 2: "中级", 3: "高级", 初级: "初级", 中级: "中级", 高级: "高级" }; /** 岗位津贴单位:字典码 ↔ 展示名 */ export const ALLOWANCE_UNIT_OPTIONS = [ { value: "month", label: "元/月" }, { value: "quarter", label: "元/季度" }, { value: "year", label: "元/年" }, ]; const ALLOWANCE_UNIT_LABEL = ALLOWANCE_UNIT_OPTIONS.reduce((acc, item) => { acc[item.value] = item.label; return acc; }, {}); export function allowanceUnitLabel(value) { if (value == null || value === "") return ""; return ALLOWANCE_UNIT_LABEL[String(value)] || String(value); } /** 业务记录枚举翻译:命中映射取中文,否则原样返回(空值返回空串) */ export function enumLabel(map, code) { if (code == null || code === "") return ""; const key = String(code).trim(); return map[key] || map[key.toUpperCase()] || key; } /* ========================================================================= * 字典归一(/system/dict/getByCode/{code} 原始返回结构有多种) * ========================================================================= */ /** 字典返回体 → 数组(兼容 list / records / rows / data 包装与纯数组) */ function dictList(raw) { if (Array.isArray(raw)) return raw; if (raw && typeof raw === "object") { const inner = raw.list || raw.records || raw.rows || raw.data; if (Array.isArray(inner)) return inner; } return []; } /** * 字典项归一,兼容三类结构: * ① 标准字段:{ label / value / name / dictLabel / dictValue / code } * ② 单键值对:[{6:"试用期"}, {1:"正式在职"}] —— 数字码在 key 上、文案在 value 上 * ③ 纯字符串数组 * @returns {Array<{label:string, value:any, key:string, id:any}>} */ export function normalizeDictItems(raw) { const out = []; dictList(raw).forEach((item) => { if (item === null || item === undefined) return; if (typeof item !== "object") { const text = String(item).trim(); if (text) out.push({ label: text, value: text, key: "", id: undefined }); return; } const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== ""); const hasStd = item.label != null || item.dictLabel != null || item.name != null || item.value != null || item.dictValue != null || item.code != null; if (hasStd) { const rawLabel = item.label != null ? item.label : item.dictLabel != null ? item.dictLabel : item.name != null ? item.name : item.value != null ? item.value : item.dictValue != null ? item.dictValue : ""; const label = String(rawLabel).trim(); if (!label) return; const id = item.id != null ? item.id : item.dictId; const key = item.key != null ? String(item.key) : item.dictCode != null ? String(item.dictCode) : id != null ? String(id) : ""; const value = item.value != null ? item.value : item.dictValue != null ? item.dictValue : item.code != null ? item.code : key; out.push({ label, value, key, id }); return; } // 单键值对:{6:"试用期"} if (entries.length === 1) { const [k, v] = entries[0]; const label = String(v).trim(); if (!label) return; out.push({ label, value: k, key: String(k), id: undefined }); } }); return out; } /** 员工状态字典 → 下拉项(value 一律用数字状态码,与接口 status 对齐) */ export function dictToStatusOptions(raw) { return normalizeDictItems(raw) .map((item) => { const key = String(item.key); const value = String(item.value); const code = /^\d+$/.test(key) ? Number(key) : /^\d+$/.test(value) ? Number(value) : NaN; return Number.isFinite(code) ? { label: item.label, value: code } : null; }) .filter(Boolean); } /** 岗位性质字典 → 下拉项(value 取字典主键,与 PC 表单一致:提交的 positionType 就是它) */ export function dictToPositionNatureOptions(raw) { return normalizeDictItems(raw) .map((item) => { const value = item.id != null ? String(item.id) : item.key ? String(item.key) : String(item.value || ""); return value ? { label: item.label, value } : null; }) .filter(Boolean); } /* ========================================================================= * 基础工具 * ========================================================================= */ function txt(value) { return value == null ? "" : String(value); } function trim(value) { return txt(value).trim(); } function date10(value) { return value ? formatDate(value) : ""; } /** * 主键一律按**字符串**回传。 * 接口 GET 回来的 id 都是字符串(雪花 id 19 位,超出 Number.MAX_SAFE_INTEGER, * 转成 number 末尾几位会被四舍五入),提交时必须原样送字符串,否则会出现 * 「查回来是 2096754981489143810、存进去变成 2096754981489143800」。 * 原先这里用的是 num() = Number(value),把 id / 机构 / 部门 / 主管 / 工厂全转成了数字。 */ export function keepIdString(value) { if (value === null || value === undefined || value === "") return undefined; const text = String(value).trim(); return text || undefined; } /** 去掉 undefined / null / 空串(保留 0 / false),对齐 PC 端 compact */ function compact(obj = {}) { const out = {}; Object.keys(obj).forEach((key) => { const value = obj[key]; if (value === undefined || value === null || value === "") return; out[key] = value; }); return out; } function currentUserRaw() { return uni.getStorageSync("userInfo") || {}; } /* ========================================================================= * 读取接口 * ========================================================================= */ /** * 拉取当前登录人的扩展资料(部门、岗位、角色等) * GET /system/account/getLoginUser */ export async function fetchCurrentUser(params = {}, loding = false) { const res = await get(Vue.prototype.apiUrl + "/system/account/getLoginUser", params, loding); if (res.code == 0) return res.data || null; return Promise.reject(new Error(res.message)); } /** 把 profile.postId("1111,2222")解析成岗位名称字符串 */ async function resolvePostIds(postIdString) { if (!postIdString) return ""; const ids = String(postIdString) .split(",") .map((s) => s.trim()) .filter(Boolean); if (!ids.length) return ""; const res = await postJ(Vue.prototype.apiUrl + "/hr/position/page", { pageNum: 1, size: 1000, status: 1 }, false); if (Number(res.code) !== 0) return ""; const pageData = res.data || {}; const records = Array.isArray(pageData.list) ? pageData.list : Array.isArray(pageData) ? pageData : []; return ids .map((id) => { const match = records.find((r) => String(r.id) === id); return match ? String(match.positionName || match.name || "") : ""; }) .filter(Boolean) .join("、"); } /** * 员工档案详情(把 user 子对象摊平到顶层,并回填岗位名称) * GET /main/users/:userId/profile */ export async function fetchUserDetail(userId, loding = true) { if (!userId && userId !== 0) throw new Error("缺少用户ID"); const res = await get(Vue.prototype.apiUrl + `/main/users/${userId}/profile`, {}, loding); if (Number(res.code) !== 0) return Promise.reject(new Error(res.message)); const data = res.data; if (!data) return null; const profile = data.user && typeof data.user === "object" ? { ...data, ...data.user } : data; try { const names = await resolvePostIds(profile.postId); if (names) profile.position = names; } catch (error) { // 岗位解析失败不影响其他字段 } return profile; } /** 拉取完整档案聚合视图(基础档案 + 履历 + 业务摘要) */ export async function fetchUserProfile(userId, loding = true) { if (!userId && userId !== 0) throw new Error("缺少用户ID"); const res = await get(Vue.prototype.apiUrl + `/main/users/${userId}/profile`, {}, loding); if (Number(res.code) !== 0) return Promise.reject(new Error(res.message)); const data = res.data; if (!data) return null; const profile = data.user && typeof data.user === "object" ? { ...data, ...data.user } : data; return { ...data, ...profile }; } /** 拉取主管档案并把姓名回填到 vo.managerName */ export async function resolveManagerName(vo) { if (!vo || typeof vo !== "object") return vo; const raw = vo.user && typeof vo.user === "object" ? vo.user : vo; const managerId = raw.managerUserId != null ? raw.managerUserId : vo.managerUserId; if (managerId === undefined || managerId === null || managerId === "") return vo; try { const managerVo = await fetchUserDetail(managerId, false); if (managerVo) { const name = (managerVo.user && managerVo.user.name) || managerVo.name || ""; raw.managerName = String(name || ""); } } catch (error) { // 主管档案解析失败不阻塞主流程 } return vo; } /** 修改档案成功后,重新拉一次当前用户的 profile 接口并写回本地缓存 */ export async function refetchMyProfile() { const login = currentUserRaw(); const userId = login.userId || login.id; if (!userId && userId !== 0) throw new Error("缺少用户ID,无法刷新档案"); const vo = await fetchUserDetail(userId); if (vo) { await resolveManagerName(vo); uni.setStorageSync("userInfo", { ...login, ...vo }); } return vo; } /** 当前登录人 userId */ export function currentUserId() { const login = currentUserRaw(); return login.userId || login.id || ""; } /** 工厂(厂区)列表,用于「所属工厂」下拉 */ export async function listFactories(loding = false) { try { const res = await get( Vue.prototype.apiUrl + "/main/factoryarea/page", { pageNum: 1, size: 999, type: 1 }, loding ); if (Number(res.code) !== 0) return []; const data = res.data || {}; const rows = Array.isArray(data.list) ? data.list : Array.isArray(data) ? data : []; return rows.map((item) => ({ id: item.id != null ? item.id : item.factoryId, name: item.name || item.factoryName || item.areaName || "", })); } catch (error) { return []; } } /* ========================================================================= * 工作履历(独立接口 /main/users/{userId}/work-experiences) * ========================================================================= */ export function serializeWorkExperience(item = {}) { return compact({ formerCompanyName: trim(item.company), formerDepartment: trim(item.department), formerPosition: trim(item.position), resignationReason: trim(item.leaveReason), workDescription: trim(item.description), workStartDate: item.startDate || "", workEndDate: item.endDate || "", }); } export async function listWorkExperiences(userId, loding = false) { const uid = userId || currentUserId(); if (!uid) return []; const res = await get(Vue.prototype.apiUrl + `/main/users/${uid}/work-experiences`, {}, loding); if (Number(res.code) !== 0) return []; return Array.isArray(res.data) ? res.data : []; } export async function saveWorkExperience(userId, payload = {}) { const uid = userId || currentUserId(); const res = await postJ(Vue.prototype.apiUrl + `/main/users/${uid}/work-experiences`, payload, false); if (res.code == 0) return res.data; return Promise.reject(new Error(res.message)); } export async function updateWorkExperience(userId, workExperienceId, payload = {}) { const uid = userId || currentUserId(); const res = await putJ( Vue.prototype.apiUrl + `/main/users/${uid}/work-experiences/${workExperienceId}`, payload, false ); if (res.code == 0) return res.data; return Promise.reject(new Error(res.message)); } export async function removeWorkExperience(userId, workExperienceId) { const uid = userId || currentUserId(); const res = await deleteApi( Vue.prototype.apiUrl + `/main/users/${uid}/work-experiences/${workExperienceId}`, {}, false ); if (res.code == 0) return res.data; return Promise.reject(new Error(res.message)); } /** * 以「页面当前列表」为准同步工作履历:缺 id 的新增、有 id 的更新、页面已删除的移除。 * 返回同步后的最新列表。 */ export async function syncWorkExperiences(userId, jobs = [], existing = []) { const uid = userId || currentUserId(); if (!uid) return jobs; const next = (jobs || []).filter((item) => item.company && item.startDate && item.endDate); const keptIds = new Set(next.map((item) => item.id).filter(Boolean).map(String)); const removals = (existing || []) .filter((item) => item.id && !keptIds.has(String(item.id))) .map((item) => removeWorkExperience(uid, item.id)); await Promise.all(removals); const saved = await Promise.all( next.map((item) => { const payload = serializeWorkExperience(item); if (item.id) return updateWorkExperience(uid, item.id, payload).then(() => ({ ...item })); return saveWorkExperience(uid, payload).then((id) => ({ ...item, id: id || item.id })); }) ); return saved; } /* ========================================================================= * 档案 VO → 页面表单模型 * ========================================================================= */ function adaptBankCards(raw = {}) { if (Array.isArray(raw.bankCardList) && raw.bankCardList.length) { return raw.bankCardList.map((item, index) => ({ key: `bank_${item.id != null ? item.id : index}`, id: item.id, bankName: txt(item.openingBank), bankBranch: txt(item.bankBranch), bankAccount: txt(item.cardNumber), bankAccountName: txt(item.accountName || raw.name), })); } if (raw.openingBank || raw.cardNumber) { return [ { key: "bank_legacy", id: undefined, bankName: txt(raw.openingBank), bankBranch: txt(raw.bankBranch), bankAccount: txt(raw.cardNumber), bankAccountName: txt(raw.name), }, ]; } return []; } function adaptSocialSecurity(raw = {}) { if (!Array.isArray(raw.socialSecurityList)) return []; return raw.socialSecurityList.map((item, index) => ({ key: `social_${item.id != null ? item.id : index}`, id: item.id, type: txt(item.socialType), base: item.socialBase === null || item.socialBase === undefined || item.socialBase === "" ? "" : String(item.socialBase), insuredDate: date10(item.insuredDate), stopDate: date10(item.stopDate), stopReason: txt(item.stopRecord), isEnabled: Number(item.isEnabled) === 1 ? 1 : 0, })); } function adaptCertificates(raw = {}) { const levelLabel = { 1: "初级", 2: "中级", 3: "高级" }; const typeLabel = Object.keys(CERT_TYPE_CODE).reduce((acc, name) => { acc[String(CERT_TYPE_CODE[name])] = name; return acc; }, {}); if (!Array.isArray(raw.identityPhotos)) return []; return raw.identityPhotos.map((item, index) => ({ key: `cert_${item.id != null ? item.id : index}`, id: item.id, hrCertificateId: item.hrCertificateId, type: typeLabel[String(item.type)] || "其他", number: txt(item.code), level: item.level == null || item.level === "" ? "" : levelLabel[item.level] || String(item.level), status: Number(item.status) === 0 ? "失效" : "有效", startDate: date10(item.validityStartTime), endDate: date10(item.validityEndTime), issueDate: date10(item.enactorTime), attachment: item.fileObj || item.fileIds || item.files || item.attachment || [], remark: txt(item.remark), holder: txt(item.holder || raw.name), holderId: item.holderId != null ? item.holderId : raw.id, holderType: item.holderType || "1", hasPositionAllowance: Number(item.hasPositionAllowance) === 1 ? 1 : 0, positionAllowanceAmount: item.positionAllowanceAmount === "" || item.positionAllowanceAmount == null ? "" : String(item.positionAllowanceAmount), positionAllowanceUnit: item.positionAllowancePaymentUnit == null ? "" : String(item.positionAllowancePaymentUnit), })); } function adaptWorkExperiences(list = []) { return (list || []).map((item, index) => ({ key: `job_${item.id != null ? item.id : index}`, id: item.id, startDate: date10(item.workStartDate), endDate: date10(item.workEndDate), company: txt(item.formerCompanyName), position: txt(item.formerPosition), department: txt(item.formerDepartment), leaveReason: txt(item.resignationReason), description: txt(item.workDescription), })); } function adaptContract(raw = {}) { const first = Array.isArray(raw.contracts) ? raw.contracts[0] : null; if (!first) { return { category: "", name: "", signDate: "", startDate: "", endDate: "", status: "", remark: "" }; } return { category: txt(first.contractType), name: txt(first.contractNo), signDate: date10(first.effectiveDate || first.startDate), startDate: date10(first.startDate), endDate: date10(first.endDate), // 状态:优先接口给的中文名,其次按枚举码翻译,最后才原样透出 status: txt(first.contractStatusName) || enumLabel(CONTRACT_STATUS_LABELS, first.contractStatus) || enumLabel(CONTRACT_SIGNING_STATUS_LABELS, first.signingStatus), remark: txt(first.remark), }; } /** 主岗位(primaryFlag 优先,其次第一条) */ export function pickPrimaryPosition(raw = {}) { const list = Array.isArray(raw.positionList) ? raw.positionList : []; if (!list.length) return null; return list.find((item) => Number(item.primaryFlag) === 1) || list[0]; } /** * 把档案聚合 VO 摊平成页面表单模型(字段命名对齐 PC 表单,便于复用载荷构造)。 * @param {object} raw 已摊平 user 字段的档案对象 * @param {object} extras { workExperiences } */ export function buildProfileForm(raw = {}, extras = {}) { const primary = pickPrimaryPosition(raw); const positionIds = txt(raw.postId) .split(",") .map((s) => s.trim()) .filter(Boolean); const deptIds = Array.isArray(raw.deptIds) ? raw.deptIds.filter((id) => id != null && id !== "") : []; const positionId = primary && primary.positionId != null ? String(primary.positionId) : positionIds[0] || ""; const birthDate = date10(raw.birthday); // 接口不给 age 时按出生日期兜底(PC 表单同步口径) const age = raw.age == null || raw.age === "" ? calcAgeByBirthday(birthDate) : String(raw.age); const statusCode = raw.status == null || raw.status === "" ? NaN : Number(raw.status); return { id: raw.id != null ? raw.id : raw.userId, accountId: raw.accountId, account: txt(raw.loginName || raw.account), employeeNo: txt(raw.jobNumber), name: txt(raw.name || raw.userName), gender: sexLabel(raw.sex), birthDate, age, idCard: txt(raw.idCard || raw.idNumber), ethnic: txt(raw.ethnic), politicalStatus: txt(raw.politicsStatus), maritalStatus: txt(raw.maritalStatus), nativePlace: txt(raw.birthplace), education: txt(raw.educationBackground), degree: txt(raw.degree), school: txt(raw.school), major: txt(raw.major), graduationDate: date10(raw.graduateDate), censusAddress: txt(raw.censusAddress), currentAddress: txt(raw.currentAddress), workAddress: txt(raw.address), phone: txt(raw.phone), email: txt(raw.email), wechat: txt(raw.wxId), emergencyContact: txt(raw.secondLinkName), emergencyRelation: txt(raw.secondLinkRelation), emergencyPhone: txt(raw.secondLinkPhone), groupId: raw.groupId != null && raw.groupId !== "" ? String(raw.groupId) : "", company: txt(raw.groupName), factoryId: raw.factoryId != null && raw.factoryId !== "" ? String(raw.factoryId) : raw.factoriesId != null ? String(raw.factoriesId) : "", factory: txt(raw.factoryName), deptIds, deptId: deptIds.length ? String(deptIds[deptIds.length - 1]) : "", deptName: txt(raw.deptName || raw.deptNames), positionSequenceId: primary && primary.positionSequenceId != null ? String(primary.positionSequenceId) : "", positionSequenceName: txt(primary && (primary.positionSequenceName || primary.sequenceName)), positionId, // 岗位名称:优先取岗位明细(positionList)里的名称,与 PC adaptPositionBindings 口径一致 positionName: txt( (Array.isArray(raw.positionList) ? raw.positionList .map((item) => (item && (item.positionName || item.name)) || "") .filter(Boolean) .join("、") : "") || raw.postName || raw.position ), // 岗位性质:码与名称分开存(码取字典主键,名称缺失时由页面按字典翻译),对齐 PC 口径 employmentType: txt( (primary && (primary.positionType != null ? primary.positionType : "")) || raw.positionType || raw.employmentType || raw.staffingType || "" ), employmentTypeName: txt( (primary && (primary.positionTypeName || "")) || raw.positionTypeName || raw.employmentTypeName || raw.staffingTypeName || "" ), staffingLevel: txt((primary && (primary.positionLevelName || primary.positionLevel)) || ""), managerId: raw.managerUserId != null && raw.managerUserId !== "" ? String(raw.managerUserId) : "", manager: txt(raw.managerUserName || raw.managerName), hireDate: date10(raw.joinDate || raw.hireDate), regularDate: date10(raw.positiveDate), // 员工状态:只认接口原始状态码,取不到就留空(不编造默认值,避免「已离职」被回显成「正式在职」) status: Number.isFinite(statusCode) && statusCode > 0 ? statusCode : "", statusName: txt(raw.statusName), enabled: Number(raw.isEnabled) !== 0, keyProcess: txt( raw.criticalTaskIds || (Array.isArray(raw.criticalTaskList) ? raw.criticalTaskList.map((item) => item.taskName || item.taskId).filter(Boolean).join(",") : "") ), bankAccounts: adaptBankCards(raw), socialSecurityRecords: adaptSocialSecurity(raw), certificates: adaptCertificates(raw), previousJobs: adaptWorkExperiences(extras.workExperiences || []), remarks: txt(raw.remark), contract: adaptContract(raw), }; } /* ========================================================================= * 表单模型 → /main/user/saveNew 载荷 * ========================================================================= */ /** 关键工序字符串 → CriticalTaskDTO 列表(数字视为已存在的 taskId) */ function buildCriticalTaskList(text, rawList = []) { const value = trim(text); if (!value) return []; const rows = Array.isArray(rawList) ? rawList : []; return value .split(/[,,]/) .map((item) => trim(item)) .filter(Boolean) .map((item) => { if (/^\d+$/.test(item)) return { taskId: item, taskName: item }; const matched = rows.find((row) => String(row.taskName) === item || String(row.taskId) === item); if (matched) return compact({ taskId: matched.taskId, taskName: matched.taskName || item }); return { taskName: item }; }); } /** * 页面表单 → 保存参数(对齐 PC 端 serializeEmployee 的字段口径)。 * @param {object} form 表单模型 * @param {object} options { raw, positionList } */ export function buildUserPayload(form = {}, options = {}) { const raw = options.raw || {}; const positionList = Array.isArray(options.positionList) ? options.positionList : []; const bankCardList = (form.bankAccounts || []) .filter((item) => item.bankName || item.bankBranch || item.bankAccount) .map((item) => ({ openingBank: trim(item.bankName), bankBranch: trim(item.bankBranch), cardNumber: trim(item.bankAccount), accountName: trim(item.bankAccountName || form.name), })) .filter((item) => item.accountName); const socialSecurityList = (form.socialSecurityRecords || []) .filter((item) => trim(item.type)) .map((item) => compact({ socialType: trim(item.type), socialBase: item.base === "" || item.base == null ? undefined : Number(item.base), insuredDate: item.insuredDate || "", stopDate: item.stopDate || "", stopRecord: trim(item.stopReason), isEnabled: Number(item.isEnabled) === 1 ? 1 : 0, }) ); const identityPhotos = (form.certificates || []) .filter((item) => trim(item.type) || trim(item.number)) .map((item) => compact({ id: keepIdString(item.id), type: CERT_TYPE_CODE[trim(item.type)] != null ? CERT_TYPE_CODE[trim(item.type)] : CERT_TYPE_CODE.其他, code: trim(item.number), level: CERT_LEVEL_CODE[trim(item.level)] || trim(item.level), status: item.status === "失效" ? 0 : 1, validityStartTime: item.startDate || "", validityEndTime: item.endDate || "", enactorTime: item.issueDate || "", remark: trim(item.remark), holderType: item.holderType || "1", holder: trim(item.holder || form.name), holderId: keepIdString(item.holderId != null ? item.holderId : form.id), hasPositionAllowance: Number(item.hasPositionAllowance) === 1 ? 1 : 0, positionAllowanceAmount: Number(item.hasPositionAllowance) === 1 && item.positionAllowanceAmount !== "" && item.positionAllowanceAmount != null ? Number(item.positionAllowanceAmount) : undefined, positionAllowancePaymentUnit: Number(item.hasPositionAllowance) === 1 && item.positionAllowanceUnit ? String(item.positionAllowanceUnit) : undefined, fileObj: Array.isArray(item.attachment) ? item.attachment : undefined, hrCertificateId: keepIdString(item.hrCertificateId), }) ); const deptIds = (Array.isArray(form.deptIds) && form.deptIds.length ? form.deptIds : form.deptId ? [form.deptId] : []) .map((id) => keepIdString(id)) .filter((id) => id != null); const payload = compact({ id: keepIdString(form.id), accountId: keepIdString(form.accountId), name: trim(form.name), jobNumber: trim(form.employeeNo), sex: SEX_CODE[form.gender] != null ? SEX_CODE[form.gender] : undefined, birthday: form.birthDate || "", age: form.age === "" || form.age == null ? undefined : Number(form.age), idCard: trim(form.idCard), idNumber: trim(form.idCard), ethnic: trim(form.ethnic), politicsStatus: trim(form.politicalStatus), maritalStatus: trim(form.maritalStatus), birthplace: trim(form.nativePlace), censusAddress: trim(form.censusAddress), currentAddress: trim(form.currentAddress), address: trim(form.workAddress), educationBackground: trim(form.education), degree: trim(form.degree), school: trim(form.school), major: trim(form.major), graduateDate: form.graduationDate || "", phone: trim(form.phone), email: trim(form.email), wxId: trim(form.wechat), // 员工状态只提交数字状态码;statusName 仅用于页面兜底显示,不回传避免写入过期快照 status: Number(form.status) > 0 ? Number(form.status) : undefined, joinDate: form.hireDate || "", positiveDate: form.regularDate || "", leaveDate: raw.leaveDate || "", isEnabled: form.enabled === false ? 0 : 1, bankCardList, secondLinkName: trim(form.emergencyContact), secondLinkPhone: trim(form.emergencyPhone), secondLinkRelation: trim(form.emergencyRelation), professionId: raw.professionId, workTypeId: raw.workTypeId, remark: trim(form.remarks), groupId: keepIdString(form.groupId), deptIds, deptName: trim(form.deptName), managerUserId: keepIdString(form.managerId), postId: keepIdString(form.positionId), postName: trim(form.positionName), positionList, factoryId: keepIdString(form.factoryId), criticalTaskList: buildCriticalTaskList(form.keyProcess, raw.criticalTaskList), socialSecurityList, identityPhotos, socialSecurityAccount: raw.socialSecurityAccount, socialSecurityAddress: raw.socialSecurityAddress, socialSecurityAddressId: raw.socialSecurityAddressId, accumulationFundAccount: raw.accumulationFundAccount, }); return payload; } /** * 新增 / 修改员工档案 * POST /main/user/saveNew */ export async function saveEmployee(payload = {}) { const res = await postJ(Vue.prototype.apiUrl + "/main/user/saveNew", payload, true); if (res.code == 0) return res.data; return Promise.reject(new Error(res.message)); } export { compact as compactPayload };