实现我的 Tab、对齐 Android 流程并接入 OSS 上传

This commit is contained in:
2026-07-06 16:01:05 +08:00
parent caf737b09b
commit b7bc62e94b
36 changed files with 4077 additions and 57 deletions

View File

@ -5,14 +5,15 @@
import Foundation
/// Token 广
/// Token广
enum AuthSessionHelper {
/// Token SceneDelegate
/// Token SceneDelegate
static func completeLogin(
with response: V9AuthResponse,
username: String,
privacyAgreementAccepted: Bool
privacyAgreementAccepted: Bool,
selectedAccount: AccountSwitchAccount
) {
let token = response.token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !token.isEmpty else { return }
@ -20,7 +21,98 @@ enum AuthSessionHelper {
AppStore.shared.saveToken(token)
AppStore.shared.lastLoginUsername = username
AppStore.shared.privacyAgreementAccepted = privacyAgreementAccepted
applyAccountSession(from: selectedAccount, in: response)
NotificationCenter.default.post(name: NotificationName.userDidLogin, object: nil)
}
///
static func applyAccountSwitch(
with response: V9AuthResponse,
account: AccountSwitchAccount
) {
let token = response.token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !token.isEmpty else { return }
AppStore.shared.saveToken(token)
applyAccountSession(from: account, in: response)
NotificationCenter.default.post(name: NotificationName.accountDidSwitch, object: nil)
}
/// v9 AppStore
static func applyAccountSession(from account: AccountSwitchAccount, in response: V9AuthResponse) {
AppStore.shared.applyAccount(account)
if let scenicUser = response.scenicUsers.first(where: { matches($0, account: account) }) {
applyScenicUser(scenicUser)
return
}
if let storeUser = response.storeUsers.first(where: { matches($0, account: account) }) {
applyStoreUser(storeUser)
}
}
/// v9 `isCurrent`
static func resolveCurrentAccount(from response: V9AuthResponse) -> AccountSwitchAccount? {
let accounts = response.accounts.filter { $0.businessUserId > 0 }
if let current = accounts.first(where: \.isCurrent) {
return current
}
return accounts.first
}
private static func applyScenicUser(_ user: V9ScenicUser) {
AppStore.shared.userId = String(user.businessUserId)
AppStore.shared.accountType = V9ScenicUser.accountTypeValue
AppStore.shared.accountDisplayName = firstNonEmpty(
user.scenicName, user.nickname, user.realName, user.username
)
AppStore.shared.userName = firstNonEmpty(user.nickname, user.username, user.realName)
AppStore.shared.realName = firstNonEmpty(user.realName, user.nickname, user.username)
AppStore.shared.phone = user.phone
AppStore.shared.currentScenicId = user.scenicId
AppStore.shared.currentScenicName = user.scenicName
AppStore.shared.currentStoreId = 0
if !user.appRoleCode.isEmpty {
AppStore.shared.roleCode = user.appRoleCode
}
if !user.appRoleName.isEmpty {
AppStore.shared.roleName = user.appRoleName
}
}
private static func applyStoreUser(_ user: V9StoreUser) {
AppStore.shared.userId = String(user.businessUserId)
AppStore.shared.accountType = V9StoreUser.accountTypeValue
AppStore.shared.accountDisplayName = firstNonEmpty(
user.storeName, user.scenicName, user.realName, user.userName, user.username
)
AppStore.shared.userName = firstNonEmpty(user.userName, user.username, user.realName)
AppStore.shared.realName = firstNonEmpty(user.realName, user.userName, user.username)
AppStore.shared.phone = user.phone
AppStore.shared.avatar = user.avatar
AppStore.shared.currentScenicId = user.scenicId
AppStore.shared.currentScenicName = user.scenicName
AppStore.shared.currentStoreId = user.storeId
if !user.appRoleCode.isEmpty {
AppStore.shared.roleCode = user.appRoleCode
}
if !user.appRoleName.isEmpty {
AppStore.shared.roleName = user.appRoleName
}
}
private static func matches(_ user: V9ScenicUser, account: AccountSwitchAccount) -> Bool {
user.businessUserId == account.businessUserId
|| account.id == "\(V9ScenicUser.accountTypeValue)_\(user.businessUserId)"
}
private static func matches(_ user: V9StoreUser, account: AccountSwitchAccount) -> Bool {
user.businessUserId == account.businessUserId
|| account.id == "\(V9StoreUser.accountTypeValue)_\(user.businessUserId)"
}
private static func firstNonEmpty(_ values: String...) -> String {
values.first { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } ?? ""
}
}

View File

@ -0,0 +1,88 @@
//
// AppRoleCode.swift
// suixinkan
//
import Foundation
/// Android `AppRoleCode`
enum AppRoleCode: String, CaseIterable, Equatable {
case driver = "driver"
case floristBuilder = "florist_builder"
case clerk = "clerk"
case liveStream = "live_stream"
case frontDesk = "front_desk"
case makeupArtist = "makeup_artist"
case photographerAssistant = "photographer_assistant"
case assistantManager = "assistant_manager"
case scenicOperation = "scenic_operation"
case scenicAdmin = "scenic_admin"
case scenicCS = "scenic_cs"
case editor = "editor"
case storeAdmin = "store_admin"
case photographer = "photographer"
var displayName: String {
switch self {
case .driver: "司机"
case .floristBuilder: "搭建花艺师"
case .clerk: "店员"
case .liveStream: "直播"
case .frontDesk: "前台"
case .makeupArtist: "化妆师"
case .photographerAssistant: "摄影师助理"
case .assistantManager: "副店长"
case .scenicOperation: "景区运营"
case .scenicAdmin: "景区管理员"
case .scenicCS: "飞手"
case .editor: "剪辑师"
case .storeAdmin: "店铺管理员"
case .photographer: "摄影师"
}
}
var isPhotographer: Bool {
self == .photographer
}
/// role_code
static func fromCode(_ code: String?) -> AppRoleCode? {
let normalized = code?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !normalized.isEmpty else { return nil }
return AppRoleCode(rawValue: normalized)
}
/// role_id
static func fromLegacyRoleId(_ roleId: Int) -> AppRoleCode? {
switch roleId {
case 41: .photographer
case 46: .storeAdmin
case 47: .editor
case 52: .scenicCS
case 53: .scenicAdmin
case 54: .scenicOperation
default: nil
}
}
/// role_name
static func fromRoleName(_ roleName: String?) -> AppRoleCode? {
let name = roleName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !name.isEmpty else { return nil }
if name.contains("摄影师助理") { return .photographerAssistant }
if name.contains("副店长") { return .assistantManager }
if name.contains("搭建花艺") || name.contains("花艺师") { return .floristBuilder }
if name.contains("店铺管理") || name.contains("店长") { return .storeAdmin }
if name.contains("景区管理") { return .scenicAdmin }
if name.contains("景区运营") { return .scenicOperation }
if name.contains("景区客服") || name.contains("飞手") { return .scenicCS }
if name.contains("剪辑") { return .editor }
if name.contains("化妆师") { return .makeupArtist }
if name.contains("摄影师") { return .photographer }
if name.contains("司机") { return .driver }
if name.contains("店员") { return .clerk }
if name.contains("直播") { return .liveStream }
if name.contains("前台") { return .frontDesk }
return AppRoleCode.allCases.first { name.contains($0.displayName) }
}
}

View File

@ -55,7 +55,7 @@ enum LoginValidationError: Equatable {
///
enum LoginResolution {
case completed(V9AuthResponse)
case completed(V9AuthResponse, AccountSwitchAccount)
case needsAccountSelection(AccountSelectionPayload)
}
@ -201,6 +201,8 @@ struct V9ScenicUser: Decodable, Equatable {
let phone: String
let scenicId: Int
let scenicName: String
let appRoleCode: String
let appRoleName: String
let isCurrent: Bool
var businessUserId: Int {
@ -241,6 +243,8 @@ struct V9ScenicUser: Decodable, Equatable {
case phone
case scenicId = "scenic_id"
case scenicName = "scenic_name"
case appRoleCode = "app_role_code"
case appRoleName = "app_role_name"
case isCurrent = "is_current"
}
@ -257,6 +261,8 @@ struct V9ScenicUser: Decodable, Equatable {
phone = try container.decodeLossyString(forKey: .phone)
scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
scenicName = try container.decodeLossyString(forKey: .scenicName)
appRoleCode = try container.decodeLossyString(forKey: .appRoleCode)
appRoleName = try container.decodeLossyString(forKey: .appRoleName)
isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
}
}
@ -278,6 +284,8 @@ struct V9StoreUser: Decodable, Equatable {
let scenicName: String
let storeId: Int
let storeName: String
let appRoleCode: String
let appRoleName: String
let isCurrent: Bool
var businessUserId: Int {
@ -320,6 +328,8 @@ struct V9StoreUser: Decodable, Equatable {
case scenicName = "scenic_name"
case storeId = "store_id"
case storeName = "store_name"
case appRoleCode = "app_role_code"
case appRoleName = "app_role_name"
case isCurrent = "is_current"
}
@ -338,6 +348,8 @@ struct V9StoreUser: Decodable, Equatable {
scenicName = try container.decodeLossyString(forKey: .scenicName)
storeId = try container.decodeLossyInt(forKey: .storeId) ?? 0
storeName = try container.decodeLossyString(forKey: .storeName)
appRoleCode = try container.decodeLossyString(forKey: .appRoleCode)
appRoleName = try container.decodeLossyString(forKey: .appRoleName)
isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
}
}