storage.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. import { currentUser as mockCurrentUser } from '@/data/mock'
  2. const APPLY_KEY = 'aimill_hr_applications'
  3. const DRAFT_KEY = 'aimill_hr_application_drafts'
  4. const APPROVAL_KEY = 'aimill_hr_approval_results'
  5. const TRAINING_KEY = 'aimill_hr_training_progress'
  6. const PROFILE_KEY = 'aimill_hr_profile_changes'
  7. const ONBOARD_MATERIALS_KEY = 'aimill_hr_onboard_materials'
  8. const ONBOARD_LIST_KEY = 'aimill_hr_onboardings'
  9. const ONBOARD_INVITATION_KEY = 'aimill_hr_onboarding_invitations'
  10. const ONBOARD_DRAFT_KEY = 'aimill_hr_onboarding_drafts'
  11. const RESIGN_KEY = 'aimill_hr_resignations'
  12. const AUTH_KEY = 'aimill_hr_auth'
  13. const REMEMBER_KEY = 'aimill_hr_remembered_account'
  14. const PASSWORD_MEMO_KEY = 'passwordMemo'
  15. const PASSWORD_INFO_KEY = 'passInfo'
  16. const TOKEN_KEY = 'token'
  17. const USER_INFO_KEY = 'userInfo'
  18. const TREE_KEY = 'treeList'
  19. const AUTHORITIES_KEY = 'authorities'
  20. const CURRENT_USER_KEY = 'currentUser'
  21. const API_INFO_KEY = 'apiInfo'
  22. export const DEMO_ACCOUNT = 'ZY20230128'
  23. export const DEMO_PASSWORD = '123456'
  24. export function saveLoginSession(data, account) {
  25. const userInfo = data && typeof data === 'object' ? data : {}
  26. const token = userInfo.token || `demo-token-${Date.now()}`
  27. const normalizedAccount = String(account || userInfo.loginName || '').trim().toUpperCase()
  28. uni.setStorageSync(TOKEN_KEY, token)
  29. uni.setStorageSync(USER_INFO_KEY, { ...userInfo, token, loginName: normalizedAccount })
  30. uni.setStorageSync(AUTH_KEY, { account: normalizedAccount, loggedInAt: Date.now() })
  31. }
  32. export function login(account, password, remember = true) {
  33. const normalizedAccount = String(account || '').trim().toUpperCase()
  34. if (normalizedAccount !== DEMO_ACCOUNT || String(password) !== DEMO_PASSWORD) {
  35. return false
  36. }
  37. saveLoginSession({
  38. token: `demo-token-${Date.now()}`,
  39. sessionId: 'demo-session',
  40. userId: normalizedAccount,
  41. userName: '陈晓雨',
  42. loginName: normalizedAccount,
  43. }, normalizedAccount)
  44. saveRememberedCredentials({
  45. account: normalizedAccount,
  46. password,
  47. rememberPassword: remember,
  48. })
  49. savePermissionData([], [])
  50. return true
  51. }
  52. export function logout() {
  53. [AUTH_KEY, TOKEN_KEY, USER_INFO_KEY, TREE_KEY, AUTHORITIES_KEY, CURRENT_USER_KEY].forEach((key) =>
  54. uni.removeStorageSync(key),
  55. )
  56. }
  57. export function isLoggedIn() {
  58. return Boolean(uni.getStorageSync(TOKEN_KEY) && uni.getStorageSync(USER_INFO_KEY))
  59. }
  60. // 服务器模式下需要的是真实 token(demo 登录的是 demo-token- 开头,残留时视为无效)
  61. export function hasServerAuth() {
  62. const token = uni.getStorageSync(TOKEN_KEY)
  63. const userInfo = uni.getStorageSync(USER_INFO_KEY)
  64. return Boolean(token && userInfo && !String(token).startsWith('demo-token-'))
  65. }
  66. export function getRememberedAccount() {
  67. return getRememberedCredentials().account
  68. }
  69. export function saveRememberedCredentials({ account, password, rememberPassword }) {
  70. const normalizedAccount = String(account || '').trim()
  71. if (!rememberPassword) {
  72. uni.removeStorageSync(PASSWORD_MEMO_KEY)
  73. uni.removeStorageSync(PASSWORD_INFO_KEY)
  74. uni.removeStorageSync(REMEMBER_KEY)
  75. return
  76. }
  77. uni.setStorageSync(PASSWORD_MEMO_KEY, '1')
  78. uni.setStorageSync(PASSWORD_INFO_KEY, { username: normalizedAccount, passwd: String(password || '') })
  79. uni.setStorageSync(REMEMBER_KEY, normalizedAccount)
  80. }
  81. export function getRememberedCredentials() {
  82. const remembered = uni.getStorageSync(PASSWORD_INFO_KEY) || {}
  83. const rememberPassword = uni.getStorageSync(PASSWORD_MEMO_KEY) === '1'
  84. return {
  85. account: remembered.username || uni.getStorageSync(REMEMBER_KEY) || '',
  86. password: rememberPassword ? remembered.passwd || '' : '',
  87. rememberPassword,
  88. }
  89. }
  90. export function savePermissionData(tree, authorities) {
  91. uni.setStorageSync(TREE_KEY, JSON.stringify(Array.isArray(tree) ? tree : []))
  92. uni.setStorageSync(AUTHORITIES_KEY, JSON.stringify(Array.isArray(authorities) ? authorities : []))
  93. }
  94. export function getLoginUser() {
  95. return uni.getStorageSync(USER_INFO_KEY) || null
  96. }
  97. export function saveCurrentUser(data) {
  98. if (!data) return
  99. uni.setStorageSync(CURRENT_USER_KEY, data)
  100. }
  101. // 把 /system/account/getLoginUser 与 /main/users/{userId}/profile 合并后的 VO 映射成页面字段
  102. // - 部门:vo.deptName
  103. // - 岗位:fetchUserDetail 已用 vo.postId 解析并写入 vo.position;这里兜底再读 postName / positionList
  104. // - 入职时间:profile 用的是 joinDate,getLoginUser 用 entryDate,两边都兼容
  105. // - 紧急联系人:secondLinkName + secondLinkPhone(关系 secondLinkRelation 可选带入)
  106. function mapCurrentUser(vo) {
  107. if (!vo || typeof vo !== 'object') return null
  108. const name = String(vo.name || '')
  109. let position = vo.position || ''
  110. if (!position) position = vo.postName || ''
  111. if (!position && Array.isArray(vo.positionList)) {
  112. const primary = vo.positionList.find((p) => p && p.primaryFlag === 1)
  113. position = primary ? String(primary.positionName || '') : ''
  114. }
  115. const emergencyName = vo.secondLinkName || ''
  116. const emergencyPhone = vo.secondLinkPhone || ''
  117. const emergencyRelation = vo.secondLinkRelation || ''
  118. // 拼成 "姓名(关系)· 电话" 或 "姓名 · 电话"
  119. const emergencyParts = []
  120. if (emergencyName && emergencyRelation) emergencyParts.push(`${emergencyName}(${emergencyRelation})`)
  121. else if (emergencyName) emergencyParts.push(emergencyName)
  122. if (emergencyPhone) emergencyParts.push(emergencyPhone)
  123. const emergencyContact = emergencyParts.join(' · ')
  124. const rawBankCards = Array.isArray(vo.bankCardList) ? vo.bankCardList : []
  125. const bankCardList = rawBankCards.map((c) => ({
  126. id: String(c.id ?? c.cardId ?? c.cardNumber ?? ''),
  127. cardNumber: String(c.cardNumber ?? c.cardNo ?? ''),
  128. openingBank: String(c.openingBank ?? c.bankName ?? ''),
  129. branch: String(c.branch ?? c.subBranch ?? ''),
  130. }))
  131. return {
  132. id: vo.jobNumber || vo.loginName || '',
  133. name,
  134. initials: name ? name.charAt(0) : '',
  135. department: vo.deptName || '',
  136. position,
  137. company: vo.groupName || '',
  138. phone: vo.phone || '',
  139. entryDate: vo.joinDate || vo.entryDate || '',
  140. workAge: vo.workAge || '',
  141. emergencyContact,
  142. maritalStatus: vo.maritalStatus || vo.marital || '',
  143. emergencyName: emergencyName,
  144. emergencyPhone: emergencyPhone,
  145. emergencyRelation: emergencyRelation,
  146. bankCardList,
  147. raw: vo,
  148. }
  149. }
  150. function isServerMode() {
  151. const info = uni.getStorageSync(API_INFO_KEY)
  152. return Boolean(info && info.mode === 'server')
  153. }
  154. // 演示模式 → mock 数据;服务器模式 → 接口返回的数据;服务器模式未拉到则返回 null
  155. export function getCurrentUser() {
  156. if (!isServerMode()) return mockCurrentUser
  157. const stored = uni.getStorageSync(CURRENT_USER_KEY)
  158. return mapCurrentUser(stored)
  159. }
  160. export function getAuthCredentials() {
  161. const userInfo = uni.getStorageSync(USER_INFO_KEY) || {}
  162. return {
  163. token: userInfo.token || '',
  164. sessionId: userInfo.sessionId || '',
  165. }
  166. }
  167. export function clearDemoStorage() {
  168. [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) =>
  169. uni.removeStorageSync(key),
  170. )
  171. }
  172. // 入职记录持久化:mock 中的 defaultOnboardings 是只读示例,
  173. // 用户通过创建表单新增 / 编辑的记录存在 ONBOARD_LIST_KEY
  174. export function getCustomOnboardings() {
  175. const saved = uni.getStorageSync(ONBOARD_LIST_KEY)
  176. return Array.isArray(saved) ? saved : []
  177. }
  178. export function addOnboarding(staff) {
  179. if (!staff || !staff.id) return getCustomOnboardings()
  180. const list = getCustomOnboardings()
  181. list.unshift(staff)
  182. uni.setStorageSync(ONBOARD_LIST_KEY, list)
  183. return list
  184. }
  185. export function updateOnboarding(id, patch) {
  186. if (!id) return getCustomOnboardings()
  187. const list = getCustomOnboardings()
  188. const idx = list.findIndex((o) => o.id === id)
  189. if (idx >= 0) {
  190. list[idx] = { ...list[idx], ...patch }
  191. uni.setStorageSync(ONBOARD_LIST_KEY, list)
  192. }
  193. return list
  194. }
  195. export function removeOnboarding(id) {
  196. if (!id) return getCustomOnboardings()
  197. const list = getCustomOnboardings().filter((o) => o.id !== id)
  198. uni.setStorageSync(ONBOARD_LIST_KEY, list)
  199. return list
  200. }
  201. export function getOnboardingById(id, defaults = []) {
  202. const custom = getCustomOnboardings().find((o) => o.id === id)
  203. if (custom) return custom
  204. return defaults.find((o) => o.id === id) || null
  205. }
  206. // 把 mock 默认数据 + 用户新增的合并;用户数据优先
  207. export function mergeOnboardings(defaults = []) {
  208. const custom = getCustomOnboardings()
  209. const ids = new Set(custom.map((o) => o.id))
  210. return [...custom, ...defaults.filter((o) => !ids.has(o.id))]
  211. }
  212. // 入职邀请链接持久化(mock 中 defaultOnboardingInvitations 是只读示例)
  213. export function getOnboardingInvitations() {
  214. const saved = uni.getStorageSync(ONBOARD_INVITATION_KEY)
  215. return Array.isArray(saved) ? saved : []
  216. }
  217. export function addOnboardingInvitation(invite) {
  218. if (!invite || !invite.invitationId) return getOnboardingInvitations()
  219. const list = getOnboardingInvitations()
  220. list.unshift(invite)
  221. uni.setStorageSync(ONBOARD_INVITATION_KEY, list)
  222. return list
  223. }
  224. export function updateOnboardingInvitation(invitationId, patch) {
  225. if (!invitationId) return getOnboardingInvitations()
  226. const list = getOnboardingInvitations()
  227. const idx = list.findIndex((i) => i.invitationId === invitationId)
  228. if (idx >= 0) {
  229. list[idx] = { ...list[idx], ...patch }
  230. uni.setStorageSync(ONBOARD_INVITATION_KEY, list)
  231. }
  232. return list
  233. }
  234. export function getOnboardingInvitationById(invitationId) {
  235. return getOnboardingInvitations().find((i) => i.invitationId === invitationId) || null
  236. }
  237. // 入职创建表单草稿(按 latest-by-type 持久化一份)
  238. export function saveOnboardingDraft(draft) {
  239. if (!draft) return null
  240. uni.setStorageSync(ONBOARD_DRAFT_KEY, { ...draft, savedAt: Date.now() })
  241. return draft
  242. }
  243. export function loadOnboardingDraft() {
  244. return uni.getStorageSync(ONBOARD_DRAFT_KEY) || null
  245. }
  246. export function clearOnboardingDraft() {
  247. uni.removeStorageSync(ONBOARD_DRAFT_KEY)
  248. }
  249. // 入职材料上传:按 employeeId → materialKey → FileInfo[] 索引
  250. // FileInfo = { id, name, path, size, uploadedAt, sourceType }
  251. // 演示版只存元信息 + 本地临时路径,不上传到对象存储
  252. export function getOnboardMaterials(employeeId) {
  253. if (!employeeId) return {}
  254. const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {}
  255. const record = all[employeeId]
  256. return record && typeof record === 'object' ? record : {}
  257. }
  258. export function getOnboardMaterialList(employeeId, materialKey) {
  259. const record = getOnboardMaterials(employeeId)
  260. const list = record[materialKey]
  261. return Array.isArray(list) ? list : []
  262. }
  263. export function addOnboardMaterial(employeeId, materialKey, file) {
  264. if (!employeeId || !materialKey) return []
  265. const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {}
  266. const prev = all[employeeId] && typeof all[employeeId] === 'object' ? all[employeeId] : {}
  267. const list = Array.isArray(prev[materialKey]) ? prev[materialKey] : []
  268. const item = {
  269. id: `mat_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
  270. name: file.name || `材料_${materialKey}`,
  271. path: file.path || '',
  272. size: Number(file.size) || 0,
  273. sourceType: file.sourceType || 'album',
  274. uploadedAt: formatNow(),
  275. }
  276. const next = [...list, item]
  277. all[employeeId] = { ...prev, [materialKey]: next }
  278. uni.setStorageSync(ONBOARD_MATERIALS_KEY, all)
  279. return next
  280. }
  281. export function removeOnboardMaterial(employeeId, materialKey, fileId) {
  282. if (!employeeId || !materialKey) return []
  283. const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {}
  284. const prev = all[employeeId] && typeof all[employeeId] === 'object' ? all[employeeId] : {}
  285. const list = Array.isArray(prev[materialKey]) ? prev[materialKey] : []
  286. const next = list.filter((f) => f.id !== fileId)
  287. all[employeeId] = { ...prev, [materialKey]: next }
  288. uni.setStorageSync(ONBOARD_MATERIALS_KEY, all)
  289. return next
  290. }
  291. export function renameOnboardMaterial(employeeId, materialKey, fileId, newName) {
  292. const trimmed = String(newName || '').trim()
  293. if (!employeeId || !materialKey || !trimmed) return []
  294. const all = uni.getStorageSync(ONBOARD_MATERIALS_KEY) || {}
  295. const prev = all[employeeId] && typeof all[employeeId] === 'object' ? all[employeeId] : {}
  296. const list = Array.isArray(prev[materialKey]) ? prev[materialKey] : []
  297. const next = list.map((f) => (f.id === fileId ? { ...f, name: trimmed } : f))
  298. all[employeeId] = { ...prev, [materialKey]: next }
  299. uni.setStorageSync(ONBOARD_MATERIALS_KEY, all)
  300. return next
  301. }
  302. // 把临时路径读取为 base64 dataURL,用于在 storage 中持久化预览
  303. // - H5:tempFilePath 通常是 blob://,用 fetch + FileReader
  304. // - 小程序:通过 uni.getFileSystemManager.readFile({ encoding: 'base64' })
  305. // - App:由原生 chooseImage 返回本地绝对路径,读不到时回退空字符串
  306. export async function readFileAsBase64(tempPath) {
  307. if (!tempPath) return ''
  308. // 已是 dataURL:直接返回
  309. if (tempPath.startsWith('data:')) return tempPath
  310. // #ifdef H5
  311. try {
  312. const resp = await fetch(tempPath)
  313. const blob = await resp.blob()
  314. return await new Promise((resolve) => {
  315. const reader = new FileReader()
  316. reader.onload = () => resolve(String(reader.result || ''))
  317. reader.onerror = () => resolve('')
  318. reader.readAsDataURL(blob)
  319. })
  320. } catch (e) {
  321. return ''
  322. }
  323. // #endif
  324. // #ifdef MP-WEIXIN
  325. return new Promise((resolve) => {
  326. if (!uni.getFileSystemManager) return resolve('')
  327. uni.getFileSystemManager().readFile({
  328. filePath: tempPath,
  329. encoding: 'base64',
  330. success: (res) => {
  331. const ext = (tempPath.match(/\.(\w+)$/)?.[1] || 'jpg').toLowerCase()
  332. const mime = ext === 'png' ? 'image/png' : ext === 'gif' ? 'image/gif' : 'image/jpeg'
  333. resolve(`data:${mime};base64,${res.data}`)
  334. },
  335. fail: () => resolve(''),
  336. })
  337. })
  338. // #endif
  339. // #ifdef APP-PLUS
  340. return new Promise((resolve) => {
  341. if (!plus || !plus.io) return resolve('')
  342. plus.io.resolveLocalFileSystemURL(
  343. tempPath,
  344. (entry) => entry.file(
  345. (file) => {
  346. const reader = new plus.nativeUI.FileReader()
  347. reader.onloadend = (e) => resolve(`data:image/jpeg;base64,${e.target.result}`)
  348. reader.onerror = () => resolve('')
  349. reader.readAsDataURL(file)
  350. },
  351. () => resolve(''),
  352. ),
  353. () => resolve(''),
  354. )
  355. })
  356. // #endif
  357. }
  358. // 演示版上传:模拟 0-100% 进度回调。真实接入时替换为 uni.uploadFile({ url: realEndpoint })
  359. // 返回 { uploaded, url },url 在演示版等于原临时路径
  360. export function mockUpload(tempPath, { onProgress } = {}) {
  361. return new Promise((resolve) => {
  362. let progress = 0
  363. const tick = () => {
  364. progress += Math.floor(Math.random() * 25) + 15
  365. if (progress > 100) progress = 100
  366. onProgress?.(progress)
  367. if (progress >= 100) resolve({ uploaded: true, url: tempPath, mock: true })
  368. else setTimeout(tick, 120)
  369. }
  370. setTimeout(tick, 80)
  371. })
  372. }
  373. export function getApplications() {
  374. const saved = uni.getStorageSync(APPLY_KEY)
  375. return Array.isArray(saved) ? saved : []
  376. }
  377. export function addApplication(item) {
  378. const list = getApplications()
  379. // 若调用方已传 id(如服务器模式返回的单据主键),保留以便与后端对齐;
  380. // 否则按本地时间戳生成唯一 id
  381. const id = item && item.id ? item.id : Date.now()
  382. const { id: _omit, ...rest } = item || {}
  383. list.unshift({ ...rest, id, status: '审批中', createdAt: formatNow() })
  384. uni.setStorageSync(APPLY_KEY, list)
  385. return list
  386. }
  387. export function saveDraft(type, form) {
  388. const drafts = uni.getStorageSync(DRAFT_KEY) || {}
  389. drafts[type] = { ...form, savedAt: formatNow() }
  390. uni.setStorageSync(DRAFT_KEY, drafts)
  391. }
  392. export function getDraft(type) {
  393. const drafts = uni.getStorageSync(DRAFT_KEY) || {}
  394. return drafts[type] || null
  395. }
  396. export function removeDraft(type) {
  397. const drafts = uni.getStorageSync(DRAFT_KEY) || {}
  398. delete drafts[type]
  399. uni.setStorageSync(DRAFT_KEY, drafts)
  400. }
  401. export function getApprovalResults() {
  402. const saved = uni.getStorageSync(APPROVAL_KEY)
  403. return saved && typeof saved === 'object' ? saved : {}
  404. }
  405. export function setApprovalResult(id, status, comment = '') {
  406. const results = getApprovalResults()
  407. results[id] = { status, comment, handledAt: formatNow() }
  408. uni.setStorageSync(APPROVAL_KEY, results)
  409. return results[id]
  410. }
  411. export function getTrainingProgress(defaults) {
  412. const saved = uni.getStorageSync(TRAINING_KEY) || {}
  413. return defaults.map((item) => ({ ...item, progress: saved[item.id] ?? item.progress }))
  414. }
  415. export function setTrainingProgress(id, progress) {
  416. const saved = uni.getStorageSync(TRAINING_KEY) || {}
  417. saved[id] = progress
  418. uni.setStorageSync(TRAINING_KEY, saved)
  419. }
  420. export function saveProfileChange(item) {
  421. const list = uni.getStorageSync(PROFILE_KEY) || []
  422. list.unshift({ ...item, id: Date.now(), status: '审核中', createdAt: formatNow() })
  423. uni.setStorageSync(PROFILE_KEY, list)
  424. return list
  425. }
  426. export function getResignations() {
  427. const saved = uni.getStorageSync(RESIGN_KEY)
  428. return saved && typeof saved === 'object' ? saved : {}
  429. }
  430. export function getResignDetail(id) {
  431. if (!id) return null
  432. const all = getResignations()
  433. return all[id] && typeof all[id] === 'object' ? all[id] : null
  434. }
  435. export function setResignStage(id, stageKey, state = 'done') {
  436. if (!id || !stageKey) return null
  437. const all = getResignations()
  438. const prev = all[id] && typeof all[id] === 'object' ? all[id] : {}
  439. const stages = prev.stages && typeof prev.stages === 'object' ? prev.stages : {}
  440. stages[stageKey] = state
  441. all[id] = { ...prev, stages, updatedAt: formatNow() }
  442. uni.setStorageSync(RESIGN_KEY, all)
  443. return all[id]
  444. }
  445. export function setResignStatus(id, status) {
  446. if (!id || !status) return null
  447. const all = getResignations()
  448. const prev = all[id] && typeof all[id] === 'object' ? all[id] : {}
  449. all[id] = { ...prev, status, updatedAt: formatNow() }
  450. uni.setStorageSync(RESIGN_KEY, all)
  451. return all[id]
  452. }
  453. export function addResignAssetReturn(id, assetName, returned = true) {
  454. if (!id || !assetName) return null
  455. const all = getResignations()
  456. const prev = all[id] && typeof all[id] === 'object' ? all[id] : {}
  457. const map = prev.assetReturnMap && typeof prev.assetReturnMap === 'object' ? prev.assetReturnMap : {}
  458. map[assetName] = !!returned
  459. all[id] = { ...prev, assetReturnMap: map, updatedAt: formatNow() }
  460. uni.setStorageSync(RESIGN_KEY, all)
  461. return all[id]
  462. }
  463. export function setResignLocalState(id, patch) {
  464. if (!id || !patch || typeof patch !== 'object') return null
  465. const all = getResignations()
  466. const prev = all[id] && typeof all[id] === 'object' ? all[id] : {}
  467. all[id] = { ...prev, ...patch, updatedAt: formatNow() }
  468. uni.setStorageSync(RESIGN_KEY, all)
  469. return all[id]
  470. }
  471. export function formatNow() {
  472. const d = new Date()
  473. const pad = (n) => String(n).padStart(2, '0')
  474. return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
  475. }