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:
parent
5ff979cc97
commit
90ad19aad1
@ -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 {
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user