diff --git a/wake/Assets/Svg/Free.svg b/wake/Assets/Svg/Free.svg
new file mode 100644
index 0000000..fe90818
--- /dev/null
+++ b/wake/Assets/Svg/Free.svg
@@ -0,0 +1,24 @@
+
diff --git a/wake/ContentView.swift b/wake/ContentView.swift
index 250c013..cf44360 100644
--- a/wake/ContentView.swift
+++ b/wake/ContentView.swift
@@ -105,36 +105,6 @@ struct BlindBoxView: View {
case description
}
}
- // 会员信息
- struct MemberProfile: Codable {
- let materialCounter: MaterialCounter
- let userInfo: UserInfo
- let storiesCount: Int
- let conversationsCount: Int
- let remainPoints: Int
- let totalPoints: Int
- let usedBytes: Int
- let totalBytes: Int
- let titleRankings: [String]
- let medalInfos: [MedalInfo]
- let membershipLevel: String
- let membershipEndAt: String
-
- enum CodingKeys: String, CodingKey {
- case materialCounter = "material_counter"
- case userInfo = "user_info"
- case storiesCount = "stories_count"
- case conversationsCount = "conversations_count"
- case remainPoints = "remain_points"
- case totalPoints = "total_points"
- case usedBytes = "used_bytes"
- case totalBytes = "total_bytes"
- case titleRankings = "title_rankings"
- case medalInfos = "medal_infos"
- case membershipLevel = "membership_level"
- case membershipEndAt = "membership_end_at"
- }
- }
// 盲盒数量
struct BlindCount: Codable {
let availableQuantity: Int
@@ -144,59 +114,6 @@ struct BlindBoxView: View {
}
}
- struct MaterialCounter: Codable {
- let userId: Int64
- let totalCount: MediaCount
- let categoryCount: [String: MediaCount]
-
- enum CodingKeys: String, CodingKey {
- case userId = "user_id"
- case totalCount = "total_count"
- case categoryCount = "category_count"
- }
- }
-
- struct MediaCount: Codable {
- let videoCount: Int
- let photoCount: Int
- let liveCount: Int
- let videoLength: Double
- let coverUrl: String?
-
- enum CodingKeys: String, CodingKey {
- case videoCount = "video_count"
- case photoCount = "photo_count"
- case liveCount = "live_count"
- case videoLength = "video_length"
- case coverUrl = "cover_url"
- }
- }
-
- struct UserInfo: Codable {
- let userId: String
- let accessToken: String
- let avatarFileUrl: String?
- let nickname: String
- let account: String
- let email: String
- let refreshToken: String?
-
- enum CodingKeys: String, CodingKey {
- case userId = "user_id"
- case accessToken = "access_token"
- case avatarFileUrl = "avatar_file_url"
- case nickname
- case account
- case email
- case refreshToken = "refresh_token"
- }
- }
-
- struct MedalInfo: Codable, Identifiable {
- let id: Int
- let url: String
- }
-
// MARK: - BlindBox Response Model
struct BlindBoxData: Codable {
@@ -353,15 +270,13 @@ struct BlindBoxView: View {
NetworkService.shared.get(
path: "/membership/personal-center-info",
parameters: nil
- ) { (result: Result, NetworkError>) in
+ ) { (result: Result) in
DispatchQueue.main.async {
switch result {
case .success(let response):
self.memberProfile = response.data
print("✅ 成功获取会员信息:", response.data)
print("✅ 用户ID:", response.data.userInfo.userId)
- print("✅ 用户昵称:", response.data.userInfo.nickname)
- print("✅ 用户邮箱:", response.data.userInfo.email)
case .failure(let error):
print("❌ 获取会员信息失败:", error)
}
@@ -472,24 +387,6 @@ struct BlindBoxView: View {
}
}
- private func loadData() {
- // 会员信息
- NetworkService.shared.get(
- path: "/membership/personal-center-info",
- parameters: nil
- ) { (result: Result, NetworkError>) in
- DispatchQueue.main.async {
- switch result {
- case .success(let response):
- self.memberProfile = response.data
- print("✅ Successfully fetched user info:", response.data)
- case .failure(let error):
- print("❌ Failed to fetch user info:", error)
- }
- }
- }
- }
-
private func loadImage() {
guard !imageURL.isEmpty, let url = URL(string: imageURL) else {
print("⚠️ 图片URL无效或为空")
diff --git a/wake/Models/MemberProfile.swift b/wake/Models/MemberProfile.swift
new file mode 100644
index 0000000..e69d6a4
--- /dev/null
+++ b/wake/Models/MemberProfile.swift
@@ -0,0 +1,255 @@
+import Foundation
+
+// MARK: - MemberProfile Response
+struct MemberProfileResponse: Codable {
+ let code: Int
+ let data: MemberProfile
+
+ enum CodingKeys: String, CodingKey {
+ case code, data
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(code, forKey: .code)
+ try container.encode(data, forKey: .data)
+ }
+}
+
+// MARK: - MemberProfile
+struct MemberProfile: Codable {
+ let materialCounter: MaterialCounter
+ let userInfo: MemberUserInfo
+ let storiesCount: Int
+ let conversationsCount: Int
+ let remainPoints: Int
+ let totalPoints: Int
+ let usedBytes: Int
+ let totalBytes: Int
+ let titleRankings: [String]
+ let medalInfos: [MedalInfo]
+ let membershipLevel: String
+ let membershipEndAt: String
+
+ enum CodingKeys: String, CodingKey {
+ case materialCounter = "material_counter"
+ case userInfo = "user_info"
+ case storiesCount = "stories_count"
+ case conversationsCount = "conversations_count"
+ case remainPoints = "remain_points"
+ case totalPoints = "total_points"
+ case usedBytes = "used_bytes"
+ case totalBytes = "total_bytes"
+ case titleRankings = "title_rankings"
+ case medalInfos = "medal_infos"
+ case membershipLevel = "membership_level"
+ case membershipEndAt = "membership_end_at"
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ materialCounter = try container.decode(MaterialCounter.self, forKey: .materialCounter)
+ userInfo = try container.decode(MemberUserInfo.self, forKey: .userInfo)
+ storiesCount = try container.decode(Int.self, forKey: .storiesCount)
+ conversationsCount = try container.decode(Int.self, forKey: .conversationsCount)
+ remainPoints = try container.decode(Int.self, forKey: .remainPoints)
+ totalPoints = try container.decode(Int.self, forKey: .totalPoints)
+ usedBytes = try container.decode(Int.self, forKey: .usedBytes)
+ totalBytes = try container.decode(Int.self, forKey: .totalBytes)
+ titleRankings = try container.decode([String].self, forKey: .titleRankings)
+
+ if let medalInfos = try? container.decode([MedalInfo].self, forKey: .medalInfos) {
+ self.medalInfos = medalInfos
+ } else {
+ self.medalInfos = []
+ }
+
+ membershipLevel = try container.decode(String.self, forKey: .membershipLevel)
+ membershipEndAt = try container.decode(String.self, forKey: .membershipEndAt)
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(materialCounter, forKey: .materialCounter)
+ try container.encode(userInfo, forKey: .userInfo)
+ try container.encode(storiesCount, forKey: .storiesCount)
+ try container.encode(conversationsCount, forKey: .conversationsCount)
+ try container.encode(remainPoints, forKey: .remainPoints)
+ try container.encode(totalPoints, forKey: .totalPoints)
+ try container.encode(usedBytes, forKey: .usedBytes)
+ try container.encode(totalBytes, forKey: .totalBytes)
+ try container.encode(titleRankings, forKey: .titleRankings)
+ try container.encode(medalInfos, forKey: .medalInfos)
+ try container.encode(membershipLevel, forKey: .membershipLevel)
+ try container.encode(membershipEndAt, forKey: .membershipEndAt)
+ }
+}
+
+// MARK: - MemberUserInfo
+struct MemberUserInfo: Codable {
+ let userId: String
+ let accessToken: String
+ let avatarFileUrl: String?
+ let nickname: String
+ let account: String
+ let email: String
+ let refreshToken: String?
+
+ enum CodingKeys: String, CodingKey {
+ case userId = "user_id"
+ case accessToken = "access_token"
+ case avatarFileUrl = "avatar_file_url"
+ case nickname, account, email
+ case refreshToken = "refresh_token"
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(userId, forKey: .userId)
+ try container.encode(accessToken, forKey: .accessToken)
+ try container.encodeIfPresent(avatarFileUrl, forKey: .avatarFileUrl)
+ try container.encode(nickname, forKey: .nickname)
+ try container.encode(account, forKey: .account)
+ try container.encode(email, forKey: .email)
+ try container.encodeIfPresent(refreshToken, forKey: .refreshToken)
+ }
+}
+
+// MARK: - MaterialCounter
+struct MaterialCounter: Codable {
+ let userId: Int64
+ let totalCount: TotalCount
+ let categoryCount: [String: CategoryCount]
+
+ enum CodingKeys: String, CodingKey {
+ case userId = "user_id"
+ case totalCount = "total_count"
+ case categoryCount = "category_count"
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(userId, forKey: .userId)
+ try container.encode(totalCount, forKey: .totalCount)
+ try container.encode(categoryCount, forKey: .categoryCount)
+ }
+}
+
+// MARK: - TotalCount
+struct TotalCount: Codable {
+ let videoCount: Int
+ let photoCount: Int
+ let liveCount: Int
+ let videoLength: Double
+ let coverUrl: String?
+
+ enum CodingKeys: String, CodingKey {
+ case videoCount = "video_count"
+ case photoCount = "photo_count"
+ case liveCount = "live_count"
+ case videoLength = "video_length"
+ case coverUrl = "cover_url"
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(videoCount, forKey: .videoCount)
+ try container.encode(photoCount, forKey: .photoCount)
+ try container.encode(liveCount, forKey: .liveCount)
+ try container.encode(videoLength, forKey: .videoLength)
+ try container.encodeIfPresent(coverUrl, forKey: .coverUrl)
+ }
+}
+
+// MARK: - CategoryCount
+struct CategoryCount: Codable {
+ let videoCount: Int
+ let photoCount: Int
+ let liveCount: Int
+ let videoLength: Double
+ let coverUrl: String?
+
+ enum CodingKeys: String, CodingKey {
+ case videoCount = "video_count"
+ case photoCount = "photo_count"
+ case liveCount = "live_count"
+ case videoLength = "video_length"
+ case coverUrl = "cover_url"
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(videoCount, forKey: .videoCount)
+ try container.encode(photoCount, forKey: .photoCount)
+ try container.encode(liveCount, forKey: .liveCount)
+ try container.encode(videoLength, forKey: .videoLength)
+ try container.encodeIfPresent(coverUrl, forKey: .coverUrl)
+ }
+}
+
+// MARK: - MedalInfo
+struct MedalInfo: Codable, Identifiable {
+ let id: Int
+ let url: String
+
+ enum CodingKeys: String, CodingKey {
+ case id, url
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(id, forKey: .id)
+ try container.encode(url, forKey: .url)
+ }
+}
+
+// MARK: - API Response Wrapper
+struct MemberAPIResponse: Codable {
+ let code: Int
+ let message: String
+ let data: T
+
+ enum CodingKeys: String, CodingKey {
+ case code, message, data
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(code, forKey: .code)
+ try container.encode(message, forKey: .message)
+ try container.encode(data, forKey: .data)
+ }
+}
+
+// MARK: - Date Formatter
+class DateFormatterManager {
+ static let shared = DateFormatterManager()
+
+ let iso8601Full: ISO8601DateFormatter = {
+ let formatter = ISO8601DateFormatter()
+ formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+ return formatter
+ }()
+
+ private init() {}
+}
+
+// MARK: - JSON Decoder Extension
+extension JSONDecoder {
+ static let `default`: JSONDecoder = {
+ let decoder = JSONDecoder()
+ decoder.keyDecodingStrategy = .convertFromSnakeCase
+ decoder.dateDecodingStrategy = .custom { decoder -> Date in
+ let container = try decoder.singleValueContainer()
+ let dateString = try container.decode(String.self)
+
+ if let date = DateFormatterManager.shared.iso8601Full.date(from: dateString) {
+ return date
+ }
+
+ throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date string \(dateString)")
+ }
+ return decoder
+ }()
+}
diff --git a/wake/Utils/SVGImage.swift b/wake/Utils/SVGImage.swift
index a16299d..78ce50d 100644
--- a/wake/Utils/SVGImage.swift
+++ b/wake/Utils/SVGImage.swift
@@ -3,6 +3,7 @@ import WebKit
struct SVGImage: UIViewRepresentable {
let svgName: String
+ var shouldFill: Bool = false
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
@@ -11,20 +12,23 @@ struct SVGImage: UIViewRepresentable {
webView.scrollView.isScrollEnabled = false
webView.scrollView.contentInsetAdjustmentBehavior = .never
- // 1. 获取 SVG 文件路径(注意:移除了 inDirectory 参数)
guard let path = Bundle.main.path(forResource: svgName, ofType: "svg") else {
print("❌ 无法找到 SVG 文件: \(svgName).svg")
- // 打印所有可用的资源文件,用于调试
- if let resourcePath = Bundle.main.resourcePath {
- print("可用的资源文件: \(try? FileManager.default.contentsOfDirectory(atPath: resourcePath))")
- }
return webView
}
- // 2. 创建文件 URL
let fileURL = URL(fileURLWithPath: path)
- // 3. 创建 HTML 字符串
+ let svgStyle = shouldFill ? """
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ """ : """
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ """
+
let htmlString = """
@@ -39,30 +43,50 @@ struct SVGImage: UIViewRepresentable {
overflow: hidden;
background-color: transparent;
}
- svg {
- width: 100%;
- height: 100%;
- display: block;
+ .container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
- img {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
+ svg {
+ \(svgStyle)
+ display: block;
}
-
-
)
+
+