模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -65,6 +65,17 @@ final class ProfileAPI {
|
||||
)
|
||||
}
|
||||
|
||||
/// 上传摄影师头像,对齐 Android multipart 头像更新接口。
|
||||
func uploadAvatar(data: Data, fileName: String) async throws {
|
||||
let _: EmptyPayload = try await client.sendMultipart(
|
||||
path: "/api/yf-handset-app/photog/userinfo-update-avatar",
|
||||
fileName: fileName,
|
||||
mimeType: "image/jpeg",
|
||||
data: data,
|
||||
timeoutInterval: 60
|
||||
)
|
||||
}
|
||||
|
||||
/// 拉取当前登录账号可切换的景区账号和门店账号。
|
||||
func switchableAccounts() async throws -> V9AuthResponse {
|
||||
try await client.send(
|
||||
|
||||
@ -72,9 +72,9 @@ final class ProfileEditViewModel {
|
||||
try await api.updateUserInfo(nickname: nextNickname)
|
||||
}
|
||||
|
||||
AppStore.shared.userName = nextNickname
|
||||
AppStore.shared.session.userName = nextNickname
|
||||
if let uploadedAvatarURL {
|
||||
AppStore.shared.avatar = uploadedAvatarURL
|
||||
AppStore.shared.session.avatar = uploadedAvatarURL
|
||||
}
|
||||
|
||||
clearPendingAvatar()
|
||||
|
||||
@ -22,6 +22,9 @@ protocol ProfileSpaceSettingsAPI {
|
||||
|
||||
/// 删除个人日程。
|
||||
func deleteSchedule(id: Int) async throws
|
||||
|
||||
/// 上传裁剪后的摄影师头像。
|
||||
func uploadAvatar(data: Data, fileName: String) async throws
|
||||
}
|
||||
|
||||
extension ProfileAPI: ProfileSpaceSettingsAPI {}
|
||||
@ -85,6 +88,7 @@ final class ProfileSpaceSettingsViewModel {
|
||||
private(set) var scheduleMap: [String: [PhotographerSchedule]] = [:]
|
||||
private(set) var selectedDate: Date
|
||||
private(set) var isEditingProfile = false
|
||||
private(set) var isCertificationExpanded = false
|
||||
private(set) var isLoading = false
|
||||
private(set) var hasChanges = false
|
||||
|
||||
@ -109,7 +113,7 @@ final class ProfileSpaceSettingsViewModel {
|
||||
|
||||
/// 当前景区 ID。
|
||||
var scenicId: Int {
|
||||
appStore.currentScenicId
|
||||
appStore.session.currentScenicId
|
||||
}
|
||||
|
||||
/// 选中日期对应日程。
|
||||
@ -156,7 +160,7 @@ final class ProfileSpaceSettingsViewModel {
|
||||
do {
|
||||
try await api.updateUserInfo(nickname: newNickname, password: nil, avatar: nil)
|
||||
nickname = newNickname
|
||||
appStore.userName = newNickname
|
||||
appStore.session.userName = newNickname
|
||||
isEditingProfile = false
|
||||
onShowMessage?("修改成功")
|
||||
notifyStateChange()
|
||||
@ -175,10 +179,35 @@ final class ProfileSpaceSettingsViewModel {
|
||||
/// 更新头像 URL。
|
||||
func updateAvatarURL(_ url: String) {
|
||||
avatarURL = url
|
||||
appStore.avatar = url
|
||||
appStore.session.avatar = url
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 展开或收起景区认证标签。
|
||||
func toggleCertificationExpanded() {
|
||||
isCertificationExpanded.toggle()
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 上传头像并重新拉取个人空间配置。
|
||||
func uploadAvatar(
|
||||
data: Data,
|
||||
fileName: String,
|
||||
api: any ProfileSpaceSettingsAPI
|
||||
) async {
|
||||
setLoading(true)
|
||||
defer { setLoading(false) }
|
||||
do {
|
||||
try await api.uploadAvatar(data: data, fileName: fileName)
|
||||
onShowMessage?("上传成功")
|
||||
await load(api: api)
|
||||
} catch is CancellationError {
|
||||
return
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新编辑昵称。
|
||||
func updateEditingNickname(_ value: String) {
|
||||
editingNickname = value
|
||||
@ -225,41 +254,41 @@ final class ProfileSpaceSettingsViewModel {
|
||||
|
||||
/// 更新工作日开始时间。
|
||||
func updateBusinessStartTime(_ value: String) {
|
||||
businessStartTime = value
|
||||
if let end = businessEndTime, !Self.isStart(value, before: end) {
|
||||
onShowMessage?("开始时间不能大于结束时间")
|
||||
businessStartTime = nil
|
||||
return
|
||||
}
|
||||
businessStartTime = value
|
||||
updateHasChanges()
|
||||
}
|
||||
|
||||
/// 更新工作日结束时间。
|
||||
func updateBusinessEndTime(_ value: String) {
|
||||
businessEndTime = value
|
||||
if let start = businessStartTime, !Self.isStart(start, before: value) {
|
||||
onShowMessage?("结束时间不能小于开始时间")
|
||||
businessEndTime = nil
|
||||
return
|
||||
}
|
||||
businessEndTime = value
|
||||
updateHasChanges()
|
||||
}
|
||||
|
||||
/// 更新节假日开始时间。
|
||||
func updateHolidayStartTime(_ value: String) {
|
||||
holidayStartTime = value
|
||||
if let end = holidayEndTime, !Self.isStart(value, before: end) {
|
||||
onShowMessage?("开始时间不能大于结束时间")
|
||||
holidayStartTime = nil
|
||||
return
|
||||
}
|
||||
holidayStartTime = value
|
||||
updateHasChanges()
|
||||
}
|
||||
|
||||
/// 更新节假日结束时间。
|
||||
func updateHolidayEndTime(_ value: String) {
|
||||
holidayEndTime = value
|
||||
if let start = holidayStartTime, !Self.isStart(start, before: value) {
|
||||
onShowMessage?("结束时间不能小于开始时间")
|
||||
holidayEndTime = nil
|
||||
return
|
||||
}
|
||||
holidayEndTime = value
|
||||
updateHasChanges()
|
||||
}
|
||||
|
||||
@ -417,7 +446,7 @@ final class ProfileSpaceSettingsViewModel {
|
||||
editingNickname = response.nickname
|
||||
if !response.avatar.isEmpty {
|
||||
avatarURL = response.avatar
|
||||
appStore.avatar = response.avatar
|
||||
appStore.session.avatar = response.avatar
|
||||
}
|
||||
scenicCertification = response.scenicCertification
|
||||
introduction = response.description
|
||||
@ -430,8 +459,8 @@ final class ProfileSpaceSettingsViewModel {
|
||||
holidayEndTime = Self.shortTimeString(response.holidayEndTime)
|
||||
acceptOrderStatus = response.acceptOrderStatus
|
||||
scheduleMap = response.schedule
|
||||
appStore.userName = response.nickname
|
||||
appStore.realName = response.realName
|
||||
appStore.session.userName = response.nickname
|
||||
appStore.session.realName = response.realName
|
||||
originalSnapshot = currentSnapshot()
|
||||
hasChanges = false
|
||||
notifyStateChange()
|
||||
|
||||
@ -25,42 +25,42 @@ final class ProfileViewModel {
|
||||
}
|
||||
|
||||
var displayPhone: String {
|
||||
nonEmpty(userInfo?.phone) ?? AppStore.shared.phone.nonEmpty ?? "--"
|
||||
nonEmpty(userInfo?.phone) ?? AppStore.shared.session.phone.nonEmpty ?? "--"
|
||||
}
|
||||
|
||||
var displayAvatarURL: String {
|
||||
nonEmpty(userInfo?.avatar) ?? AppStore.shared.avatar.nonEmpty ?? ""
|
||||
nonEmpty(userInfo?.avatar) ?? AppStore.shared.session.avatar.nonEmpty ?? ""
|
||||
}
|
||||
|
||||
var displayUID: String {
|
||||
let uid = AppStore.shared.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let uid = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return uid.isEmpty ? "--" : uid
|
||||
}
|
||||
|
||||
var accountDisplayName: String {
|
||||
let name = AppStore.shared.accountDisplayName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let name = AppStore.shared.session.accountDisplayName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return name.isEmpty ? "--" : name
|
||||
}
|
||||
|
||||
/// 当前账号类型文案,如「门店账号」「景区账号」。
|
||||
var accountTypeLabel: String {
|
||||
let type = AppStore.shared.accountType.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if type == V9StoreUser.accountTypeValue {
|
||||
return "门店账号"
|
||||
switch AppStore.shared.session.accountType {
|
||||
case .storeUser:
|
||||
"门店账号"
|
||||
case .scenicUser:
|
||||
"景区账号"
|
||||
case .unknown:
|
||||
"--"
|
||||
}
|
||||
if type == V9ScenicUser.accountTypeValue {
|
||||
return "景区账号"
|
||||
}
|
||||
return "--"
|
||||
}
|
||||
|
||||
/// 当前是否为门店账号。
|
||||
var isStoreAccount: Bool {
|
||||
AppStore.shared.accountType == V9StoreUser.accountTypeValue
|
||||
AppStore.shared.session.accountType == .storeUser
|
||||
}
|
||||
|
||||
var currentScenicName: String {
|
||||
let name = AppStore.shared.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let name = AppStore.shared.session.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return name.isEmpty ? "--" : name
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ final class ProfileViewModel {
|
||||
}
|
||||
|
||||
var showPhotographerFields: Bool {
|
||||
if AppStore.shared.isPhotographerRole {
|
||||
if AppStore.shared.session.isPhotographerRole {
|
||||
return true
|
||||
}
|
||||
if let roleName = nonEmpty(userInfo?.roleName) {
|
||||
@ -108,9 +108,9 @@ final class ProfileViewModel {
|
||||
|
||||
let info = try await api.userInfo()
|
||||
userInfo = info
|
||||
AppStore.shared.applyUserInfo(info)
|
||||
AppStore.shared.session.applyUserInfo(info)
|
||||
if !info.roleName.isEmpty {
|
||||
AppStore.shared.roleName = info.roleName
|
||||
AppStore.shared.session.roleName = info.roleName
|
||||
}
|
||||
|
||||
if showPhotographerFields {
|
||||
@ -128,8 +128,8 @@ final class ProfileViewModel {
|
||||
/// 从 `AppStore` 同步头像与昵称,用于资料保存后的本地即时刷新。
|
||||
func applyLocalProfileUpdate(from store: AppStore = .shared) {
|
||||
let previous = userInfo ?? UserInfoResponse()
|
||||
let nextNickname = store.userName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let nextAvatar = store.avatar.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let nextNickname = store.session.userName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let nextAvatar = store.session.avatar.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
userInfo = UserInfoResponse(
|
||||
avatar: nextAvatar.isEmpty ? previous.avatar : nextAvatar,
|
||||
realName: previous.realName,
|
||||
|
||||
Reference in New Issue
Block a user