feat(ios): IOS-M1-05 我的页面资产摘要 + 存储空间

- ProfileView 新增学习资产行(KB数/知识点/复习卡)
- 新增存储空间行(已用/总量)
- 新增消息中心快捷入口
- UserService 新增 fetchAssets()/fetchStorage()
- ProfileViewModel 新增 loadStats()

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-05-29 19:50:26 +08:00
parent 5ff979cc97
commit 90ad19aad1
3 changed files with 72 additions and 37 deletions

View File

@ -85,6 +85,14 @@ class UserService {
func removeDevice(id: String) async throws -> GenericSuccessResponse {
return try await client.request("/users/me/devices/\(id)", method: "DELETE")
}
func fetchAssets() async throws -> AssetsResponse {
return try await client.request("/users/me/assets-summary")
}
func fetchStorage() async throws -> StorageResponse {
return try await client.request("/users/me/storage")
}
}
struct UserDevice: Codable, Identifiable {

View File

@ -44,7 +44,7 @@ struct ProfileView: View {
ZXProfileMenuRow(icon: "bubble.left.fill", title: "帮助与反馈", desc: "问题报告 · 功能建议")
}.foregroundColor(.primary)
}.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
achievementsSection.padding(.bottom, 120)
assetsSection.padding(.bottom, 120)
}.padding(.horizontal, 20)
}.scrollIndicators(.hidden)
}
@ -94,10 +94,37 @@ struct ProfileView: View {
.accessibilityLabel("编辑个人资料,\(profile?.nickname ?? "学习者")")
.accessibilityHint("双击查看和编辑个人资料")
}
private var achievementsSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("成就").font(.system(size: 15, weight: .bold)).foregroundColor(Color.zxF0)
HStack(spacing: 8) { ZXAchievementBadge(icon: "flame.fill", label: "连续 14 天", color: Color.zxOrange); ZXAchievementBadge(icon: "brain.head.profile", label: "费曼达人", color: Color.zxPurple); ZXAchievementBadge(icon: "books.vertical.fill", label: "知识收藏家", color: Color.zxTeal); ZXAchievementBadge(icon: "bolt.fill", label: "速学者", color: Color.zxYellow) }
private var assetsSection: some View {
VStack(spacing: 16) {
//
VStack(spacing: 0) {
NavigationLink(value: Route.libraryCreate) {
HStack {
Image(systemName: "books.vertical.fill").font(.system(size: 18)).foregroundColor(Color.zxPurple).frame(width: 36, height: 36).background(Color.zxPurpleBG(0.12)).clipShape(RoundedRectangle(cornerRadius: 10))
Text("学习资产").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Spacer()
Text("\(viewModel.kbCount)\(viewModel.itemCount)\(viewModel.cardCount)").font(.system(size: 12)).foregroundColor(Color.zxF04)
Image(systemName: "chevron.right").font(.system(size: 12)).foregroundColor(Color.zxF03)
}.padding(.horizontal, 16).padding(.vertical, 14)
}.foregroundColor(.primary)
ZXProfileDivider()
NavigationLink(value: Route.notificationList) {
HStack {
Image(systemName: "bell.fill").font(.system(size: 18)).foregroundColor(Color.zxOrange).frame(width: 36, height: 36).background(Color.zxOrange.opacity(0.12)).clipShape(RoundedRectangle(cornerRadius: 10))
Text("消息中心").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Spacer(); Image(systemName: "chevron.right").font(.system(size: 12)).foregroundColor(Color.zxF03)
}.padding(.horizontal, 16).padding(.vertical, 14)
}.foregroundColor(.primary)
ZXProfileDivider()
HStack {
Image(systemName: "externaldrive.fill").font(.system(size: 18)).foregroundColor(Color.zxTeal).frame(width: 36, height: 36).background(Color.zxTeal.opacity(0.12)).clipShape(RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 2) {
Text("存储空间").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0)
Text(viewModel.formattedStorage).font(.system(size: 11)).foregroundColor(Color.zxF04)
}
Spacer(); Image(systemName: "chevron.right").font(.system(size: 12)).foregroundColor(Color.zxF03)
}.padding(.horizontal, 16).padding(.vertical, 14)
}.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
}
}
}

View File

@ -7,49 +7,49 @@ class ProfileViewModel: ObservableObject {
@Published var preferences: UserPreferences?
@Published var profileData: UserProfileData?
@Published var summary: ActivitySummary?
@Published var kbCount = 0, itemCount = 0, cardCount = 0
@Published var usedBytes = 0, totalBytes = 1073741824
@Published var isLoading = false
@Published var errorMessage: String?
var formattedStorage: String {
if usedBytes < 1024 * 1024 { return "\(usedBytes / 1024) KB / \(totalBytes / 1024 / 1024 / 1024) GB" }
return String(format: "%.0f MB / %d GB", Double(usedBytes) / 1024 / 1024, totalBytes / 1024 / 1024 / 1024)
}
func loadAll() async {
isLoading = true
errorMessage = nil
isLoading = true; errorMessage = nil
await loadProfile()
isLoading = false
Task { await loadActivitySummary() }
Task { await loadStats() }
}
func loadProfile() async {
do {
let profile = try await UserService.shared.myProfile()
userProfile = profile
preferences = profile.preferences
profileData = profile.profile
} catch {
if userProfile == nil { errorMessage = "加载用户信息失败" }
do { let p = try await UserService.shared.myProfile(); userProfile = p; preferences = p.preferences; profileData = p.profile } catch { if userProfile == nil { errorMessage = "加载用户信息失败" } }
}
func updatePreferences(_ dto: UpdatePreferencesRequest) async { do { preferences = try await UserService.shared.updatePreferences(dto) } catch { errorMessage = "保存设置失败" } }
func updateProfileDetail(_ dto: UpdateProfileDataRequest) async { do { profileData = try await UserService.shared.updateProfileDetail(dto) } catch { errorMessage = "保存学习档案失败" } }
private func loadActivitySummary() async { do { summary = try await ActivityService.shared.summary() } catch {} }
private func loadStats() async {
async let a: AssetsResponse? = try? UserService.shared.fetchAssets()
async let s: StorageResponse? = try? UserService.shared.fetchStorage()
if let assets = await a { kbCount = assets.knowledgeBaseCount; itemCount = assets.knowledgeItemCount; cardCount = assets.reviewCardCount }
if let storage = await s { usedBytes = storage.usedBytes; totalBytes = storage.totalBytes }
}
}
func updatePreferences(_ dto: UpdatePreferencesRequest) async {
do {
preferences = try await UserService.shared.updatePreferences(dto)
} catch {
errorMessage = "保存设置失败"
}
// MARK: - Raw API models
struct AssetsResponse: Codable {
let knowledgeBaseCount: Int
let knowledgeItemCount: Int
let reviewCardCount: Int
}
func updateProfileDetail(_ dto: UpdateProfileDataRequest) async {
do {
profileData = try await UserService.shared.updateProfileDetail(dto)
} catch {
errorMessage = "保存学习档案失败"
}
}
private func loadActivitySummary() async {
do {
summary = try await ActivityService.shared.summary()
} catch {
// non-critical, stats remain at 0
}
}
struct StorageResponse: Codable {
let usedBytes: Int
let totalBytes: Int
let fileCount: Int
}