refactor: 重构存储与接口适配,统一金额单位与会话管理

- 新增配置、存储、金额转换工具服务
- 按userId隔离本地缓存,替换旧设备级存储
- 统一移动端金额单位为万元,新增转换适配逻辑
- 重构认证与请求逻辑,支持真实后端会话
- 调整搜索、帮助、融资模块的模拟开关与数据适配
- 更新文档说明开发状态与配置规则
This commit is contained in:
hujun
2026-09-22 17:25:52 +08:00
parent 4a69357606
commit dcf12a4471
25 changed files with 1619 additions and 143 deletions
+3 -3
View File
@@ -24,12 +24,12 @@
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
import BrandLogo from '@/components/BrandLogo.uvue'
import { API_BASE_URL, requestSmsCode, loginWithSms, loginWithPassword, saveSession } from '@/packages/account/services/auth.uts'
import { API_BASE_URL, requestSmsCode, loginWithSms, loginWithPassword } from '@/packages/account/services/auth.uts'
import { showAgreementNotice } from '@/services/assessment.uts'
import { consumeInitialLaunch } from '@/services/launch.uts'
onLoad(() => { consumeInitialLaunch() })
const { statusStyle, contentStyle, pageStyle } = usePageLayout()
const mode = ref('phone')
const mode = ref('password')
const phone = ref('')
const password = ref('')
const smsCode = ref('')
@@ -58,7 +58,7 @@
if (countdown.value <= 1) { countdown.value = 0; clearInterval(countdownTimer) } else countdown.value -= 1
}, 1000)
}
const finishLogin = (token: string) => { saveSession(token, phone.value); uni.redirectTo({ url: '/pages/home/home' }) }
const finishLogin = (_token: string) => { uni.redirectTo({ url: '/pages/home/home' }) }
const resendSms = async () => {
if (loading.value || countdown.value > 0 || !validPhone()) return
if (!agreed.value) { errorMessage.value = '请先阅读并勾选用户协议与隐私政策'; return }
+4 -2
View File
@@ -19,6 +19,8 @@
</template>
<script setup lang="uts">
import { getUserStorage, setUserStorage } from '@/services/storage.uts'
import { usePageLayout } from '@/services/layout.uts'
import AppHeader from '@/components/AppHeader.uvue'
import BottomNav from '@/components/BottomNav.uvue'
@@ -36,14 +38,14 @@
const unreadCount = computed((): number => notices.value.filter((item: Notice): boolean => item.unread).length)
onShow(() => {
notices.value.forEach((item: Notice): void => {
if (uni.getStorageSync('finance.notice.read.' + item.id) == true) item.unread = false
if (getUserStorage('finance.notice.read.' + item.id) == true) item.unread = false
})
})
const markRead = (id: string): void => {
const item = notices.value.find((value: Notice): boolean => value.id == id)
if (item == null) return
item.unread = false
uni.setStorageSync('finance.notice.read.' + id, true)
setUserStorage('finance.notice.read.' + id, true)
}
const openNotice = (item: Notice): void => {
markRead(item.id)
+4 -2
View File
@@ -32,6 +32,8 @@
</template>
<script setup lang="uts">
import { getUserStorage, setUserStorage } from '@/services/storage.uts'
import { usePageLayout } from '@/services/layout.uts'
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
@@ -58,7 +60,7 @@
}
const restore = (): void => {
today.value = localDate()
const stored = uni.getStorageSync('finance.points.checkin.days')
const stored = getUserStorage('finance.points.checkin.days')
if (typeof stored != 'string' || stored == '') { signDays.value = []; return }
const restored = (stored as string).split(',')
const unique: string[] = []
@@ -71,7 +73,7 @@
if (signedToday.value) { uni.showToast({ title: '今天已签到,明天再来', icon: 'none' }); return }
const next = signDays.value.slice()
next.unshift(today.value)
uni.setStorageSync('finance.points.checkin.days', next.join(','))
setUserStorage('finance.points.checkin.days', next.join(','))
signDays.value = next
recordsExpanded.value = true
uni.showToast({ title: '签到完成,体验积分 +10', icon: 'none' })
+10 -4
View File
@@ -15,11 +15,14 @@
</template>
<script setup lang="uts">
import { getUserStorage, setUserStorage } from '@/services/storage.uts'
import { usePageLayout } from '@/services/layout.uts'
import AppHeader from '@/components/AppHeader.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import AppIcon from '@/components/AppIcon.uvue'
import { clearSession, getSession } from '@/services/api.uts'
import { getSession } from '@/services/api.uts'
import { logoutAccount } from '@/packages/account/services/auth.uts'
import { activeAssessmentType, assessmentProgress, showAgreementNotice } from '@/services/assessment.uts'
import { DEFAULT_AVATAR, getAccountProfile, maskPhone } from '@/packages/account/services/account.uts'
const { contentStyle, pageStyle } = usePageLayout()
@@ -39,7 +42,7 @@
const type = activeAssessmentType()
displayName.value = loggedIn.value ? profile.nickname : '登录 / 注册'
completion.value = assessmentProgress(type).percent
const latest = uni.getStorageSync('latestAssessment')
const latest = getUserStorage('latestAssessment')
hasReport.value = latest != null && typeof latest == 'object'
})
const go = (url: string) => { uni.navigateTo({ url }) }
@@ -48,13 +51,16 @@
if (key == 'archive') go('/packages/finance/archive/archive')
else if (key == 'help') go('/packages/content/help/help')
else if (key == 'security') go(loggedIn.value ? '/packages/account/security/security' : '/packages/account/login/login')
else if (key == 'privacy') uni.showActionSheet({ itemList: ['查看隐私说明', '管理资料与授权'], success: (result) => { if (result.tapIndex == 0) showAgreementNotice('隐私说明'); else if (result.tapIndex == 1) { uni.setStorageSync('assessmentEditStep', 3); go('/packages/finance/assessment/assessment?type=' + activeAssessmentType()) } } })
else if (key == 'privacy') uni.showActionSheet({ itemList: ['查看隐私说明', '管理资料与授权'], success: (result) => { if (result.tapIndex == 0) showAgreementNotice('隐私说明'); else if (result.tapIndex == 1) { setUserStorage('assessmentEditStep', 3); go('/packages/finance/assessment/assessment?type=' + activeAssessmentType()) } } })
else if (key == 'about') uni.showModal({ title: '关于 AI融资', content: '版本 V1.0.0\n帮助您整理融资信息、了解融资知识与融资方案。评估结果仅供参考,不构成授信承诺。', showCancel: false, confirmText: '我知道了' })
}
const openAccount = () => { go(loggedIn.value ? '/packages/account/edit-profile/edit-profile' : '/packages/account/login/login') }
const logout = () => {
if (!loggedIn.value) { go('/packages/account/login/login'); return }
uni.showModal({ title: '退出登录', content: '退出后,本机保存的评估草稿仍会保留。', confirmText: '退出登录', success: (result) => { if (result.confirm) { clearSession(); uni.reLaunch({ url: '/packages/account/login/login' }) } } })
uni.showModal({ title: '退出登录', content: '退出后,本机保存的评估草稿仍会保留。', confirmText: '退出登录', success: (result) => { if (result.confirm) {
logoutAccount().then(() => { uni.reLaunch({ url: '/packages/account/login/login' }) })
.catch(() => { uni.showToast({ title: '退出失败,请检查网络后重试', icon: 'none' }) })
} }, fail: (error) => { console.error('logout confirmation failed', error); uni.showToast({ title: '暂时无法打开确认框,请重试', icon: 'none' }) } })
}
</script>
+7 -16
View File
@@ -1,4 +1,5 @@
import { API_BASE_URL, getSession } from '@/services/api.uts'
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 }
@@ -23,7 +24,7 @@ export function getAccountProfile(): AccountProfile {
const phone = session == null ? '' : draftText(session, 'phone')
let nickname = '融资用户'; let avatar = ''
if (phone.length > 0) {
const saved = uni.getStorageSync(profileKey(phone))
const saved = getUserStorage(profileKey(phone))
if (saved != null && typeof saved == 'object') {
nickname = draftText(saved as UTSJSONObject, 'nickname'); avatar = draftText(saved as UTSJSONObject, 'avatar')
} else {
@@ -35,19 +36,9 @@ export function getAccountProfile(): AccountProfile {
}
// 安全接口必须经过服务端认证。未配置服务时不模拟验证或修改成功。
function accountRequest(path: string, method: RequestMethod, payload: UTSJSONObject): Promise<UTSJSONObject> {
return new Promise<UTSJSONObject>((resolve, reject) => {
const session = requireAccount()
if (API_BASE_URL.length == 0) { reject(new Error('账户服务暂未开放,请稍后再试')); return }
uni.request({ url: API_BASE_URL + path, method, data: payload, header: { Authorization: 'Bearer ' + draftText(session, 'token') }, timeout: 10000,
success: (response) => {
if (!sameAccount(session)) { reject(new Error('登录状态已变化,请重新进入页面')); return }
if (response.statusCode == 401) { reject(new Error('登录已失效,请重新登录')); return }
if (response.statusCode < 200 || response.statusCode >= 300) { reject(new Error('操作失败,请核对信息后重试')); return }
resolve(response.data == null ? new UTSJSONObject() : response.data as UTSJSONObject)
}, fail: () => reject(new Error('网络连接失败,请稍后重试'))
})
})
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')
@@ -55,7 +46,7 @@ export async function saveAccountProfile(nickname: string, avatar: string): Prom
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('登录状态已变化,请重新进入页面')
uni.setStorageSync(profileKey(phone), { nickname: name, avatar })
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('验证码用途无效')
+30 -25
View File
@@ -1,35 +1,40 @@
import { API_BASE_URL, wait, requestJson } from '@/services/api.uts'
export { API_BASE_URL, saveSession } from '@/services/api.uts'
import { API_BASE_URL, requestJson, saveSession, clearSession, getSession } from '@/services/api.uts'
export { API_BASE_URL } from '@/services/api.uts'
import { setUserStorage } from '@/services/storage.uts'
export async function requestSmsCode(phone: string): Promise<boolean> {
if (API_BASE_URL.length == 0) {
await wait(350)
return phone.length == 11
}
await requestJson('/auth/sms-code', 'POST', { phone })
return true
await requestJson('/auth/sms-code', 'POST', { phone, purpose: 'login' })
return true
}
function acceptSession(result: UTSJSONObject, phone: string): string {
const token = result['token'] as string | null
const userId = result['userId'] as string | null
const expiresAt = result['expiresAt'] as string | null
if (token == null || userId == null || expiresAt == null) throw new Error('登录响应缺少会话信息')
saveSession(token, phone, userId, expiresAt)
const profile = result['profile'] as UTSJSONObject | null
if (profile != null) setUserStorage('finance.account.profile:' + phone, { nickname: profile['nickname'], avatar: profile['avatarUrl'] ?? '' })
return token
}
export async function loginWithSms(phone: string, code: string): Promise<string> {
if (API_BASE_URL.length == 0) {
await wait(300)
if (code.length != 6) throw new Error('验证码应为 6 位数字')
return 'demo-sms-token'
}
const result = await requestJson('/auth/sms-login', 'POST', { phone, code })
const token = result['token'] as string | null
if (token == null || token.length == 0) throw new Error('登录响应缺少 token')
return token
return acceptSession(await requestJson('/auth/sms-login', 'POST', { phone, code }), phone)
}
export async function loginWithPassword(phone: string, password: string): Promise<string> {
if (API_BASE_URL.length == 0) {
await wait(300)
return 'demo-password-token'
}
const result = await requestJson('/auth/password-login', 'POST', { phone, password })
const token = result['token'] as string | null
if (token == null || token.length == 0) throw new Error('登录响应缺少 token')
return token
return acceptSession(await requestJson('/auth/password-login', 'POST', { phone, password }), phone)
}
export async function logoutAccount(): Promise<void> {
const session = getSession()
if (session == null) { clearSession(); return }
try {
await requestJson('/auth/logout', 'POST', {})
} catch (error) {
// 401已清理会话,视为退出完成;网络失败保留会话以便重试撤销。
if (getSession() != null) throw error
}
const current = getSession()
if (current == null || current['token'] == session['token']) clearSession()
}