Files
airz-uniapp/packages/account/services/account.uts
T
hujun dcf12a4471 refactor: 重构存储与接口适配,统一金额单位与会话管理
- 新增配置、存储、金额转换工具服务
- 按userId隔离本地缓存,替换旧设备级存储
- 统一移动端金额单位为万元,新增转换适配逻辑
- 重构认证与请求逻辑,支持真实后端会话
- 调整搜索、帮助、融资模块的模拟开关与数据适配
- 更新文档说明开发状态与配置规则
2026-09-22 17:25:52 +08:00

95 lines
5.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getUserStorage, setUserStorage } from '@/services/storage.uts'
import { API_BASE_URL, getSession, requestJson } from '@/services/api.uts'
import { activeAssessmentType, loadAssessmentDraft, draftText } from '@/services/assessment.uts'
export type AccountProfile = { nickname: string, avatar: string, phone: string }
export const DEFAULT_AVATAR = '/packages/account/static/default-avatar.png'
const profileKey = (phone: string): string => 'finance.account.profile:' + phone
export const maskPhone = (phone: string): string => phone.length == 11 ? phone.substring(0, 3) + '****' + phone.substring(7) : phone
export const validPhone = (phone: string): boolean => /^1[3-9][0-9]{9}$/.test(phone)
export const validCode = (code: string): boolean => /^[0-9]{6}$/.test(code)
export const validNewPassword = (password: string): boolean => /^[!-~]{8,32}$/.test(password) && /[A-Za-z]/.test(password) && /[0-9]/.test(password)
export function requireAccount(): UTSJSONObject {
const session = getSession()
if (session == null || draftText(session, 'token').length == 0 || !validPhone(draftText(session, 'phone'))) throw new Error('请先登录后再操作')
return session
}
export function sameAccount(session: UTSJSONObject): boolean {
const current = getSession()
return current != null && draftText(current, 'token') == draftText(session, 'token') && draftText(current, 'phone') == draftText(session, 'phone')
}
export function getAccountProfile(): AccountProfile {
const session = getSession()
const phone = session == null ? '' : draftText(session, 'phone')
let nickname = '融资用户'; let avatar = ''
if (phone.length > 0) {
const saved = getUserStorage(profileKey(phone))
if (saved != null && typeof saved == 'object') {
nickname = draftText(saved as UTSJSONObject, 'nickname'); avatar = draftText(saved as UTSJSONObject, 'avatar')
} else {
const draft = loadAssessmentDraft(activeAssessmentType())
if (draft != null && draftText(draft, 'phone') == phone && draftText(draft, 'name').length > 0) nickname = draftText(draft, 'name')
}
}
return { phone, nickname: nickname.length > 0 ? nickname : '融资用户', avatar }
}
// 安全接口必须经过服务端认证。未配置服务时不模拟验证或修改成功。
async function accountRequest(path: string, method: RequestMethod, payload: UTSJSONObject): Promise<UTSJSONObject> {
requireAccount()
return await requestJson(path, method, payload)
}
export async function saveAccountProfile(nickname: string, avatar: string): Promise<void> {
const session = requireAccount(); const phone = draftText(session, 'phone')
const name = nickname.trim()
if (name.length < 2 || name.length > 20) throw new Error('昵称请输入 2–20 个字符')
if (API_BASE_URL.length > 0) await accountRequest('/account/profile', 'PUT', { nickname: name, avatar })
if (!sameAccount(session)) throw new Error('登录状态已变化,请重新进入页面')
setUserStorage(profileKey(phone), { nickname: name, avatar })
}
export async function requestSecurityCode(purpose: string, newPhone: string): Promise<void> {
if (!['password', 'phone-current', 'phone-new'].includes(purpose)) throw new Error('验证码用途无效')
if (purpose == 'phone-new' && !validPhone(newPhone)) throw new Error('请输入正确的新手机号')
await accountRequest('/account/security/sms-code', 'POST', { purpose, newPhone: purpose == 'phone-new' ? newPhone : '' })
}
export async function changeAccountPassword(mode: string, oldPassword: string, code: string, newPassword: string): Promise<void> {
if (!validNewPassword(newPassword)) throw new Error('新密码需为 8–32 位,包含字母和数字,可使用符号')
if (mode == 'password') {
if (oldPassword.length == 0) throw new Error('请输入当前密码')
if (oldPassword == newPassword) throw new Error('新密码不能与当前密码相同')
} else if (mode != 'sms' || !validCode(code)) throw new Error('请输入 6 位数字验证码')
await accountRequest('/account/password', 'PUT', { verification: mode, oldPassword: mode == 'password' ? oldPassword : '', code: mode == 'sms' ? code : '', newPassword })
}
export async function changeAccountPhone(newPhone: string, currentCode: string, newCode: string): Promise<void> {
const session = requireAccount()
if (!validPhone(newPhone)) throw new Error('请输入正确的新手机号')
if (newPhone == draftText(session, 'phone')) throw new Error('新手机号不能与当前手机号相同')
if (!validCode(currentCode) || !validCode(newCode)) throw new Error('请输入两组 6 位数字验证码')
await accountRequest('/account/phone', 'PUT', { newPhone, currentCode, newCode })
}
// 限制头像体积,避免把临时路径或过大的图片写入账户缓存。
export function readAvatar(path: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
// #ifdef WEB
fetch(path).then((response) => response.blob()).then((blob) => {
if (blob.size > 512000 || !['image/jpeg', 'image/png', 'image/webp'].includes(blob.type)) { reject(new Error('请选择 500KB 以内的 JPG、PNG 或 WebP 图片')); return }
const reader = new FileReader()
reader.onload = () => resolve(reader.result as string)
reader.onerror = () => reject(new Error('图片读取失败,请重新选择'))
reader.readAsDataURL(blob)
}).catch(() => reject(new Error('图片读取失败,请重新选择')))
// #endif
// #ifndef WEB
uni.getFileSystemManager().readFile({ filePath: path, encoding: 'base64', success: (result) => {
const data = result.data as string
if (data.length > 683000) { reject(new Error('请选择 500KB 以内的图片')); return }
const mime = data.startsWith('/9j/') ? 'image/jpeg' : (data.startsWith('iVBOR') ? 'image/png' : (data.startsWith('UklGR') ? 'image/webp' : ''))
if (mime.length == 0) { reject(new Error('请选择 JPG、PNG 或 WebP 图片')); return }
resolve('data:' + mime + ';base64,' + data)
}, fail: () => reject(new Error('图片读取失败,请重新选择')) })
// #endif
})
}