import { currentUser as mockCurrentUser } from '@/data/mock' const APPLY_KEY = 'aimill_hr_applications' const DRAFT_KEY = 'aimill_hr_application_drafts' const APPROVAL_KEY = 'aimill_hr_approval_results' const TRAINING_KEY = 'aimill_hr_training_progress' const PROFILE_KEY = 'aimill_hr_profile_changes' const ONBOARD_MATERIALS_KEY = 'aimill_hr_onboard_materials' const ONBOARD_LIST_KEY = 'aimill_hr_onboardings' const ONBOARD_INVITATION_KEY = 'aimill_hr_onboarding_invitations' const ONBOARD_DRAFT_KEY = 'aimill_hr_onboarding_drafts' const RESIGN_KEY = 'aimill_hr_resignations' const AUTH_KEY = 'aimill_hr_auth' const REMEMBER_KEY = 'aimill_hr_remembered_account' const PASSWORD_MEMO_KEY = 'passwordMemo' const PASSWORD_INFO_KEY = 'passInfo' const TOKEN_KEY = 'token' const USER_INFO_KEY = 'userInfo' const TREE_KEY = 'treeList' const AUTHORITIES_KEY = 'authorities' const CURRENT_USER_KEY = 'currentUser' const API_INFO_KEY = 'apiInfo' export const DEMO_ACCOUNT = 'ZY20230128' export const DEMO_PASSWORD = '123456' export function saveLoginSession(data, account) { const userInfo = data && typeof data === 'object' ? data : {} const token = userInfo.token || `demo-token-${Date.now()}` const normalizedAccount = String(account || userInfo.loginName || '').trim().toUpperCase() uni.setStorageSync(TOKEN_KEY, token) uni.setStorageSync(USER_INFO_KEY, { ...userInfo, token, loginName: normalizedAccount }) uni.setStorageSync(AUTH_KEY, { account: normalizedAccount, loggedInAt: Date.now() }) } export function login(account, password, remember = true) { const normalizedAccount = String(account || '').trim().toUpperCase() if (normalizedAccount !== DEMO_ACCOUNT || String(password) !== DEMO_PASSWORD) { return false } saveLoginSession({ token: `demo-token-${Date.now()}`, sessionId: 'demo-session', userId: normalizedAccount, userName: '陈晓雨', loginName: normalizedAccount, }, normalizedAccount) saveRememberedCredentials({ account: normalizedAccount, password, rememberPassword: remember, }) savePermissionData([], []) return true } export function logout() { [AUTH_KEY, TOKEN_KEY, USER_INFO_KEY, TREE_KEY, AUTHORITIES_KEY, CURRENT_USER_KEY].forEach((key) => uni.removeStorageSync(key), ) } export function isLoggedIn() { return Boolean(uni.getStorageSync(TOKEN_KEY) && uni.getStorageSync(USER_INFO_KEY)) } // 服务器模式下需要的是真实 token(demo 登录的是 demo-token- 开头,残留时视为无效) export function hasServerAuth() { const token = uni.getStorageSync(TOKEN_KEY) const userInfo = uni.getStorageSync(USER_INFO_KEY) return Boolean(token && userInfo && !String(token).startsWith('demo-token-')) } export function getRememberedAccount() { return getRememberedCredentials().account } export function saveRememberedCredentials({ account, password, rememberPassword }) { const normalizedAccount = String(account || '').trim() if (!rememberPassword) { uni.removeStorageSync(PASSWORD_MEMO_KEY) uni.removeStorageSync(PASSWORD_INFO_KEY) uni.removeStorageSync(REMEMBER_KEY) return } uni.setStorageSync(PASSWORD_MEMO_KEY, '1') uni.setStorageSync(PASSWORD_INFO_KEY, { username: normalizedAccount, passwd: String(password || '') }) uni.setStorageSync(REMEMBER_KEY, normalizedAccount) } export function getRememberedCredentials() { const remembered = uni.getStorageSync(PASSWORD_INFO_KEY) || {} const rememberPassword = uni.getStorageSync(PASSWORD_MEMO_KEY) === '1' return { account: remembered.username || uni.getStorageSync(REMEMBER_KEY) || '', password: rememberPassword ? remembered.passwd || '' : '', rememberPassword, } } export function savePermissionData(tree, authorities) { uni.setStorageSync(TREE_KEY, JSON.stringify(Array.isArray(tree) ? tree : [])) uni.setStorageSync(AUTHORITIES_KEY, JSON.stringify(Array.isArray(authorities) ? authorities : [])) } export function getLoginUser() { return uni.getStorageSync(USER_INFO_KEY) || null } export function saveCurrentUser(data) { if (!data) return uni.setStorageSync(CURRENT_USER_KEY, data) } // 把 /system/account/getLoginUser 与 /main/users/{userId}/profile 合并后的 VO 映射成页面字段 // - 部门:vo.deptName // - 岗位:fetchUserDetail 已用 vo.postId 解析并写入 vo.position;这里兜底再读 postName / positionList // - 入职时间:profile 用的是 joinDate,getLoginUser 用 entryDate,两边都兼容 // - 紧急联系人:secondLinkName + secondLinkPhone(关系 secondLinkRelation 可选带入) function mapCurrentUser(vo) { if (!vo || typeof vo !== 'object') return null const name = String(vo.name || '') let position = vo.position || '' if (!position) position = vo.postName || '' if (!position && Array.isArray(vo.positionList)) { const primary = vo.positionList.find((p) => p && p.primaryFlag === 1) position = primary ? String(primary.positionName || '') : '' } const emergencyName = vo.secondLinkName || '' const emergencyPhone = vo.secondLinkPhone || '' const emergencyRelation = vo.secondLinkRelation || '' // 拼成 "姓名(关系)· 电话" 或 "姓名 · 电话" const emergencyParts = [] if (emergencyName && emergencyRelation) emergencyParts.push(`${emergencyName}(${emergencyRelation})`) else if (emergencyName) emergencyParts.push(emergencyName) if (emergencyPhone) emergencyParts.push(emergencyPhone) const emergencyContact = emergencyParts.join(' · ') const rawBankCards = Array.isArray(vo.bankCardList) ? vo.bankCardList : [] const bankCardList = rawBankCards.map((c) => ({ id: String(c.id ?? c.cardId ?? c.cardNumber ?? ''), cardNumber: String(c.cardNumber ?? c.cardNo ?? ''), openingBank: String(c.openingBank ?? c.bankName ?? ''), branch: String(c.branch ?? c.subBranch ?? ''), })) return { id: vo.jobNumber || vo.loginName || '', name, initials: name ? name.charAt(0) : '', department: vo.deptName || '', position, company: vo.groupName || '', phone: vo.phone || '', entryDate: vo.joinDate || vo.entryDate || '', workAge: vo.workAge || '', emergencyContact, maritalStatus: vo.maritalStatus || vo.marital || '', emergencyName: emergencyName, emergencyPhone: emergencyPhone, emergencyRelation: emergencyRelation, bankCardList, raw: vo, } } function isServerMode() { const info = uni.getStorageSync(API_INFO_KEY) return Boolean(info && info.mode === 'server') } // 演示模式 → mock 数据;服务器模式 → 接口返回的数据;服务器模式未拉到则返回 null export function getCurrentUser() { if (!isServerMode()) return mockCurrentUser const stored = uni.getStorageSync(CURRENT_USER_KEY) return mapCurrentUser(stored) } export function getAuthCredentials() { const userInfo = uni.getStorageSync(USER_INFO_KEY) || {} return { token: userInfo.token || '', sessionId: userInfo.sessionId || '', } } export function clearDemoStorage() { [APPLY_KEY, DRAFT_KEY, APPROVAL_KEY, TRAINING_KEY, PROFILE_KEY, ONBOARD_MATERIALS_KEY, ONBOARD_LIST_KEY, ONBOARD_INVITATION_KEY, ONBOARD_DRAFT_KEY, RESIGN_KEY].forEach((key) => uni.removeStorageSync(key), ) } // 入职记录持久化:mock 中的 defaultOnboardings 是只读示例, // 用户通过创建表单新增 / 编辑的记录存在 ONBOARD_LIST_KEY export function getCustomOnboardings() { const saved = uni.getStorageSync(ONBOARD_LIST_KEY) return Array.isArray(saved) ? saved : [] } export function addOnboarding(staff) { if (!staff || !staff.id) return getCustomOnboardings() const list = getCustomOnboardings() list.unshift(staff) uni.setStorageSync(ONBOARD_LIST_KEY, list) return list } export function updateOnboarding(id, patch) { if (!id) return getCustomOnboardings() const list = getCustomOnboardings() const idx = list.findIndex((o) => o.id === id) if (idx >= 0) { list[idx] = { ...list[idx], ...patch } uni.setStorageSync(ONBOARD_LIST_KEY, list) } return list } export function removeOnboarding(id) { if (!id) return getCustomOnboardings() const list = getCustomOnboardings().filter((o) => o.id !== id) uni.setStorageSync(ONBOARD_LIST_KEY, list) return list } export function getOnboardingById(id, defaults = []) { const custom = getCustomOnboardings().find((o) => o.id === id) if (custom) return custom return defaults.find((o) => o.id === id) || null } // 把 mock 默认数据 + 用户新增的合并;用户数据优先 export function mergeOnboardings(defaults = []) { const custom = getCustomOnboardings() const ids = new Set(custom.map((o) => o.id)) return [...custom, ...defaults.filter((o) => !ids.has(o.id))] } // 入职邀请链接持久化(mock 中 defaultOnboardingInvitations 是只读示例) export function getOnboardingInvitations() { const saved = uni.getStorageSync(ONBOARD_INVITATION_KEY) return Array.isArray(saved) ? saved : [] } export function addOnboardingInvitation(invite) { if (!invite || !invite.invitationId) return getOnboardingInvitations() const list = getOnboardingInvitations() list.unshift(invite) uni.setStorageSync(ONBOARD_INVITATION_KEY, list) return list } export function updateOnboardingInvitation(invitationId, patch) { if (!invitationId) return getOnboardingInvitations() const list = getOnboardingInvitations() const idx = list.findIndex((i) => i.invitationId === invitationId) if (idx >= 0) { list[idx] = { ...list[idx], ...patch } uni.setStorageSync(ONBOARD_INVITATION_KEY, list) } return list } export function getOnboardingInvitationById(invitationId) { return getOnboardingInvitations().find((i) => i.invitationId === invitationId) || null } // 入职创建表单草稿(按 latest-by-type 持久化一份) export function saveOnboardingDraft(draft) { if (!draft) return null uni.setStorageSync(ONBOARD_DRAFT_KEY, { ...draft, savedAt: Date.now() }) return draft } export function loadOnboardingDraft() { return uni.getStorageSync(ONBOARD_DRAFT_KEY) || null } export function clearOnboardingDraft() { uni.removeStorageSync(ONBOARD_DRAFT_KEY) } // 入职材料上传:按 employeeId → materialKey → FileInfo[] 索引 // FileInfo = { id, name, path, size, uploadedAt, sourceType } // 演示版只存元信息 + 本地临时路径,不上传到对象存储 export function getOnboardMaterials(employeeId) { if (!employeeId) return {} const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {} const record = all[employeeId] return record && typeof record === 'object' ? record : {} } export function getOnboardMaterialList(employeeId, materialKey) { const record = getOnboardMaterials(employeeId) const list = record[materialKey] return Array.isArray(list) ? list : [] } export function addOnboardMaterial(employeeId, materialKey, file) { if (!employeeId || !materialKey) return [] const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {} const prev = all[employeeId] && typeof all[employeeId] === 'object' ? all[employeeId] : {} const list = Array.isArray(prev[materialKey]) ? prev[materialKey] : [] const item = { id: `mat_${Date.now()}_${Math.floor(Math.random() * 1000)}`, name: file.name || `材料_${materialKey}`, path: file.path || '', size: Number(file.size) || 0, sourceType: file.sourceType || 'album', uploadedAt: formatNow(), } const next = [...list, item] all[employeeId] = { ...prev, [materialKey]: next } uni.setStorageSync(ONBOARD_MATERIALS_KEY, all) return next } export function removeOnboardMaterial(employeeId, materialKey, fileId) { if (!employeeId || !materialKey) return [] const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {} const prev = all[employeeId] && typeof all[employeeId] === 'object' ? all[employeeId] : {} const list = Array.isArray(prev[materialKey]) ? prev[materialKey] : [] const next = list.filter((f) => f.id !== fileId) all[employeeId] = { ...prev, [materialKey]: next } uni.setStorageSync(ONBOARD_MATERIALS_KEY, all) return next } export function renameOnboardMaterial(employeeId, materialKey, fileId, newName) { const trimmed = String(newName || '').trim() if (!employeeId || !materialKey || !trimmed) return [] const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {} const prev = all[employeeId] && typeof all[employeeId] === 'object' ? all[employeeId] : {} const list = Array.isArray(prev[materialKey]) ? prev[materialKey] : [] const next = list.map((f) => (f.id === fileId ? { ...f, name: trimmed } : f)) all[employeeId] = { ...prev, [materialKey]: next } uni.setStorageSync(ONBOARD_MATERIALS_KEY, all) return next } // 把临时路径读取为 base64 dataURL,用于在 storage 中持久化预览 // - H5:tempFilePath 通常是 blob://,用 fetch + FileReader // - 小程序:通过 uni.getFileSystemManager.readFile({ encoding: 'base64' }) // - App:由原生 chooseImage 返回本地绝对路径,读不到时回退空字符串 export async function readFileAsBase64(tempPath) { if (!tempPath) return '' // 已是 dataURL:直接返回 if (tempPath.startsWith('data:')) return tempPath // #ifdef H5 try { const resp = await fetch(tempPath) const blob = await resp.blob() return await new Promise((resolve) => { const reader = new FileReader() reader.onload = () => resolve(String(reader.result || '')) reader.onerror = () => resolve('') reader.readAsDataURL(blob) }) } catch (e) { return '' } // #endif // #ifdef MP-WEIXIN return new Promise((resolve) => { if (!uni.getFileSystemManager) return resolve('') uni.getFileSystemManager().readFile({ filePath: tempPath, encoding: 'base64', success: (res) => { const ext = (tempPath.match(/\.(\w+)$/)?.[1] || 'jpg').toLowerCase() const mime = ext === 'png' ? 'image/png' : ext === 'gif' ? 'image/gif' : 'image/jpeg' resolve(`data:${mime};base64,${res.data}`) }, fail: () => resolve(''), }) }) // #endif // #ifdef APP-PLUS return new Promise((resolve) => { if (!plus || !plus.io) return resolve('') plus.io.resolveLocalFileSystemURL( tempPath, (entry) => entry.file( (file) => { const reader = new plus.nativeUI.FileReader() reader.onloadend = (e) => resolve(`data:image/jpeg;base64,${e.target.result}`) reader.onerror = () => resolve('') reader.readAsDataURL(file) }, () => resolve(''), ), () => resolve(''), ) }) // #endif } // 演示版上传:模拟 0-100% 进度回调。真实接入时替换为 uni.uploadFile({ url: realEndpoint }) // 返回 { uploaded, url },url 在演示版等于原临时路径 export function mockUpload(tempPath, { onProgress } = {}) { return new Promise((resolve) => { let progress = 0 const tick = () => { progress += Math.floor(Math.random() * 25) + 15 if (progress > 100) progress = 100 onProgress?.(progress) if (progress >= 100) resolve({ uploaded: true, url: tempPath, mock: true }) else setTimeout(tick, 120) } setTimeout(tick, 80) }) } export function getApplications() { const saved = uni.getStorageSync(APPLY_KEY) return Array.isArray(saved) ? saved : [] } export function addApplication(item) { const list = getApplications() // 若调用方已传 id(如服务器模式返回的单据主键),保留以便与后端对齐; // 否则按本地时间戳生成唯一 id const id = item && item.id ? item.id : Date.now() const { id: _omit, ...rest } = item || {} list.unshift({ ...rest, id, status: '审批中', createdAt: formatNow() }) uni.setStorageSync(APPLY_KEY, list) return list } export function saveDraft(type, form) { const drafts = uni.getStorageSync(DRAFT_KEY) || {} drafts[type] = { ...form, savedAt: formatNow() } uni.setStorageSync(DRAFT_KEY, drafts) } export function getDraft(type) { const drafts = uni.getStorageSync(DRAFT_KEY) || {} return drafts[type] || null } export function removeDraft(type) { const drafts = uni.getStorageSync(DRAFT_KEY) || {} delete drafts[type] uni.setStorageSync(DRAFT_KEY, drafts) } export function getApprovalResults() { const saved = uni.getStorageSync(APPROVAL_KEY) return saved && typeof saved === 'object' ? saved : {} } export function setApprovalResult(id, status, comment = '') { const results = getApprovalResults() results[id] = { status, comment, handledAt: formatNow() } uni.setStorageSync(APPROVAL_KEY, results) return results[id] } export function getTrainingProgress(defaults) { const saved = uni.getStorageSync(TRAINING_KEY) || {} return defaults.map((item) => ({ ...item, progress: saved[item.id] ?? item.progress })) } export function setTrainingProgress(id, progress) { const saved = uni.getStorageSync(TRAINING_KEY) || {} saved[id] = progress uni.setStorageSync(TRAINING_KEY, saved) } export function saveProfileChange(item) { const list = uni.getStorageSync(PROFILE_KEY) || [] list.unshift({ ...item, id: Date.now(), status: '审核中', createdAt: formatNow() }) uni.setStorageSync(PROFILE_KEY, list) return list } export function getResignations() { const saved = uni.getStorageSync(RESIGN_KEY) return saved && typeof saved === 'object' ? saved : {} } export function getResignDetail(id) { if (!id) return null const all = getResignations() return all[id] && typeof all[id] === 'object' ? all[id] : null } export function setResignStage(id, stageKey, state = 'done') { if (!id || !stageKey) return null const all = getResignations() const prev = all[id] && typeof all[id] === 'object' ? all[id] : {} const stages = prev.stages && typeof prev.stages === 'object' ? prev.stages : {} stages[stageKey] = state all[id] = { ...prev, stages, updatedAt: formatNow() } uni.setStorageSync(RESIGN_KEY, all) return all[id] } export function setResignStatus(id, status) { if (!id || !status) return null const all = getResignations() const prev = all[id] && typeof all[id] === 'object' ? all[id] : {} all[id] = { ...prev, status, updatedAt: formatNow() } uni.setStorageSync(RESIGN_KEY, all) return all[id] } export function addResignAssetReturn(id, assetName, returned = true) { if (!id || !assetName) return null const all = getResignations() const prev = all[id] && typeof all[id] === 'object' ? all[id] : {} const map = prev.assetReturnMap && typeof prev.assetReturnMap === 'object' ? prev.assetReturnMap : {} map[assetName] = !!returned all[id] = { ...prev, assetReturnMap: map, updatedAt: formatNow() } uni.setStorageSync(RESIGN_KEY, all) return all[id] } export function setResignLocalState(id, patch) { if (!id || !patch || typeof patch !== 'object') return null const all = getResignations() const prev = all[id] && typeof all[id] === 'object' ? all[id] : {} all[id] = { ...prev, ...patch, updatedAt: formatNow() } uni.setStorageSync(RESIGN_KEY, all) return all[id] } export function formatNow() { const d = new Date() const pad = (n) => String(n).padStart(2, '0') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` }