模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -18,9 +18,9 @@ enum AuthSessionHelper {
|
||||
let token = response.token.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !token.isEmpty else { return }
|
||||
|
||||
AppStore.shared.saveToken(token)
|
||||
AppStore.shared.lastLoginUsername = username
|
||||
AppStore.shared.privacyAgreementAccepted = privacyAgreementAccepted
|
||||
AppStore.shared.session.saveToken(token)
|
||||
AppStore.shared.session.lastLoginUsername = username
|
||||
AppStore.shared.session.privacyAgreementAccepted = privacyAgreementAccepted
|
||||
applyAccountSession(from: selectedAccount, in: response)
|
||||
|
||||
if privacyAgreementAccepted {
|
||||
@ -39,14 +39,14 @@ enum AuthSessionHelper {
|
||||
let token = response.token.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !token.isEmpty else { return }
|
||||
|
||||
AppStore.shared.saveToken(token)
|
||||
AppStore.shared.session.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)
|
||||
AppStore.shared.session.applyAccount(account)
|
||||
|
||||
if let scenicUser = response.scenicUsers.first(where: { matches($0, account: account) }) {
|
||||
applyScenicUser(scenicUser)
|
||||
@ -67,43 +67,43 @@ enum AuthSessionHelper {
|
||||
}
|
||||
|
||||
private static func applyScenicUser(_ user: V9ScenicUser) {
|
||||
AppStore.shared.userId = String(user.businessUserId)
|
||||
AppStore.shared.accountType = V9ScenicUser.accountTypeValue
|
||||
AppStore.shared.accountDisplayName = firstNonEmpty(
|
||||
AppStore.shared.session.userId = String(user.businessUserId)
|
||||
AppStore.shared.session.accountType = .scenicUser
|
||||
AppStore.shared.session.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
|
||||
AppStore.shared.session.userName = firstNonEmpty(user.nickname, user.username, user.realName)
|
||||
AppStore.shared.session.realName = firstNonEmpty(user.realName, user.nickname, user.username)
|
||||
AppStore.shared.session.phone = user.phone
|
||||
AppStore.shared.session.currentScenicId = user.scenicId
|
||||
AppStore.shared.session.currentScenicName = user.scenicName
|
||||
AppStore.shared.session.currentStoreId = 0
|
||||
if !user.appRoleCode.isEmpty {
|
||||
AppStore.shared.roleCode = user.appRoleCode
|
||||
AppStore.shared.session.roleCode = user.appRoleCode
|
||||
}
|
||||
if !user.appRoleName.isEmpty {
|
||||
AppStore.shared.roleName = user.appRoleName
|
||||
AppStore.shared.session.roleName = user.appRoleName
|
||||
}
|
||||
}
|
||||
|
||||
private static func applyStoreUser(_ user: V9StoreUser) {
|
||||
AppStore.shared.userId = String(user.businessUserId)
|
||||
AppStore.shared.accountType = V9StoreUser.accountTypeValue
|
||||
AppStore.shared.accountDisplayName = firstNonEmpty(
|
||||
AppStore.shared.session.userId = String(user.businessUserId)
|
||||
AppStore.shared.session.accountType = .storeUser
|
||||
AppStore.shared.session.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
|
||||
AppStore.shared.session.userName = firstNonEmpty(user.userName, user.username, user.realName)
|
||||
AppStore.shared.session.realName = firstNonEmpty(user.realName, user.userName, user.username)
|
||||
AppStore.shared.session.phone = user.phone
|
||||
AppStore.shared.session.avatar = user.avatar
|
||||
AppStore.shared.session.currentScenicId = user.scenicId
|
||||
AppStore.shared.session.currentScenicName = user.scenicName
|
||||
AppStore.shared.session.currentStoreId = user.storeId
|
||||
if !user.appRoleCode.isEmpty {
|
||||
AppStore.shared.roleCode = user.appRoleCode
|
||||
AppStore.shared.session.roleCode = user.appRoleCode
|
||||
}
|
||||
if !user.appRoleName.isEmpty {
|
||||
AppStore.shared.roleName = user.appRoleName
|
||||
AppStore.shared.session.roleName = user.appRoleName
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ final class CloudTransferManager {
|
||||
api: NetworkServices.shared.cloudDriveAPI,
|
||||
downloader: CloudURLSessionDownloader(),
|
||||
photoSaver: CloudPhotoLibrarySaver(),
|
||||
scenicIdProvider: { AppStore.shared.currentScenicId }
|
||||
scenicIdProvider: { AppStore.shared.session.currentScenicId }
|
||||
)
|
||||
|
||||
private(set) var uploadTasks: [CloudTransferTask] = []
|
||||
|
||||
@ -33,7 +33,7 @@ final class CooperationAcquirerViewModel {
|
||||
}
|
||||
|
||||
func loadAcquirers(api: OrderAPI, initial: Bool = false) async {
|
||||
let storeId = appStore.currentStoreId
|
||||
let storeId = appStore.session.currentStoreId
|
||||
let storeIdParam = storeId > 0 ? String(storeId) : nil
|
||||
do {
|
||||
let response = try await api.cooperativeSalers(storeId: storeIdParam, pageSize: "20")
|
||||
|
||||
@ -24,8 +24,8 @@ final class HomeLocationStateStore {
|
||||
|
||||
init(store: AppStore = .shared) {
|
||||
self.store = store
|
||||
reminderMinutes = store.locationReminderMinutes
|
||||
isOnline = store.onlineStatus
|
||||
reminderMinutes = store.location.reminderMinutes
|
||||
isOnline = store.location.onlineStatus
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -44,18 +44,18 @@ final class HomeLocationStateStore {
|
||||
|
||||
/// 启动时恢复在线倒计时。
|
||||
func restoreStateIfNeeded() {
|
||||
reminderMinutes = store.locationReminderMinutes
|
||||
guard store.onlineStatus else {
|
||||
reminderMinutes = store.location.reminderMinutes
|
||||
guard store.location.onlineStatus else {
|
||||
isOnline = false
|
||||
stopCountdown()
|
||||
notifyChange()
|
||||
return
|
||||
}
|
||||
let lastTime = store.lastLocationReportTime
|
||||
let lastTime = store.location.lastReportTime
|
||||
guard lastTime > 0 else {
|
||||
isOnline = false
|
||||
store.onlineStatus = false
|
||||
store.clearLastLocationReportTime()
|
||||
store.location.onlineStatus = false
|
||||
store.location.clearLastReportTime()
|
||||
stopCountdown()
|
||||
notifyChange()
|
||||
return
|
||||
@ -65,7 +65,7 @@ final class HomeLocationStateStore {
|
||||
let elapsed = now - lastTime
|
||||
if elapsed >= Self.onlineDurationMillis {
|
||||
updateOnlineStatus(false)
|
||||
store.clearLastLocationReportTime()
|
||||
store.location.clearLastReportTime()
|
||||
notifyChange()
|
||||
return
|
||||
}
|
||||
@ -81,10 +81,10 @@ final class HomeLocationStateStore {
|
||||
/// 更新在线状态并持久化。
|
||||
func updateOnlineStatus(_ online: Bool) {
|
||||
isOnline = online
|
||||
store.onlineStatus = online
|
||||
store.location.onlineStatus = online
|
||||
if !online {
|
||||
stopCountdown()
|
||||
store.clearLastLocationReportTime()
|
||||
store.location.clearLastReportTime()
|
||||
}
|
||||
notifyChange()
|
||||
}
|
||||
@ -92,16 +92,16 @@ final class HomeLocationStateStore {
|
||||
/// 更新提醒分钟数。
|
||||
func updateReminderMinutes(_ minutes: Int) {
|
||||
reminderMinutes = minutes
|
||||
store.locationReminderMinutes = minutes
|
||||
store.location.reminderMinutes = minutes
|
||||
notifyChange()
|
||||
}
|
||||
|
||||
/// 开始新的位置上报会话。
|
||||
func startLocationReport(at timestamp: Int64 = Int64(Date().timeIntervalSince1970 * 1000)) {
|
||||
anchorTimestamp = timestamp
|
||||
store.lastLocationReportTime = timestamp
|
||||
store.location.lastReportTime = timestamp
|
||||
isOnline = true
|
||||
store.onlineStatus = true
|
||||
store.location.onlineStatus = true
|
||||
elapsedSeconds = 0
|
||||
nextReportCountdownSeconds = Self.onlineDurationMillis / 1000
|
||||
startCountdown(anchorTime: timestamp)
|
||||
@ -156,7 +156,7 @@ final class HomeLocationStateStore {
|
||||
if remaining <= 0 {
|
||||
self.nextReportCountdownSeconds = 0
|
||||
self.updateOnlineStatus(false)
|
||||
self.store.clearLastLocationReportTime()
|
||||
self.store.location.clearLastReportTime()
|
||||
return
|
||||
}
|
||||
self.nextReportCountdownSeconds = max(0, remaining / 1000)
|
||||
|
||||
@ -10,10 +10,10 @@ enum HomeMenuCatalog {
|
||||
|
||||
/// 全部已知菜单项,对齐 Android `Constants.menuList`。
|
||||
static let allItems: [HomeMenuItem] = [
|
||||
HomeMenuItem(uri: "space_settings", title: "空间设置", iconName: "person.crop.square.fill"),
|
||||
HomeMenuItem(uri: "space_settings", title: "空间设置", iconName: "home_menu_space"),
|
||||
HomeMenuItem(uri: "wallet", title: "我的钱包", iconName: "creditcard.fill"),
|
||||
HomeMenuItem(uri: "cloud_management", title: "相册云盘", iconName: "icloud.fill"),
|
||||
HomeMenuItem(uri: "asset_management", title: "素材管理", iconName: "photo.on.rectangle.angled"),
|
||||
HomeMenuItem(uri: "asset_management", title: "素材管理", iconName: "home_menu_material"),
|
||||
HomeMenuItem(uri: "task_management", title: "任务管理", iconName: "checklist"),
|
||||
HomeMenuItem(uri: "task_management_editor", title: "任务管理", iconName: "checklist"),
|
||||
HomeMenuItem(uri: "schedule_management", title: "日程管理", iconName: "calendar"),
|
||||
|
||||
@ -21,7 +21,7 @@ enum HomeRouteHandler {
|
||||
return
|
||||
}
|
||||
|
||||
if uri != "pilot_controller", uri != "wallet", AppStore.shared.currentScenicId <= 0 {
|
||||
if uri != "pilot_controller", uri != "wallet", AppStore.shared.session.currentScenicId <= 0 {
|
||||
showToast("请先选择景区", from: viewController)
|
||||
return
|
||||
}
|
||||
@ -87,7 +87,7 @@ enum HomeRouteHandler {
|
||||
|
||||
@MainActor
|
||||
private static func handleOperatingArea(from viewController: UIViewController, storeItem: StoreItem?) {
|
||||
guard let role = AppStore.shared.currentAppRole else {
|
||||
guard let role = AppStore.shared.session.currentAppRole else {
|
||||
showToast("当前账号暂不支持运营区域", from: viewController)
|
||||
return
|
||||
}
|
||||
@ -99,7 +99,7 @@ enum HomeRouteHandler {
|
||||
}
|
||||
showDeveloping(from: viewController)
|
||||
case .scenicAdmin:
|
||||
guard AppStore.shared.currentScenicId > 0 else {
|
||||
guard AppStore.shared.session.currentScenicId > 0 else {
|
||||
showToast("请先选择景区", from: viewController)
|
||||
return
|
||||
}
|
||||
@ -110,7 +110,7 @@ enum HomeRouteHandler {
|
||||
}
|
||||
|
||||
private static func hasCooperationOrderPermission() -> Bool {
|
||||
let permissions = AppStore.shared.permissionItems()
|
||||
let permissions = AppStore.shared.permissions.permissionItems()
|
||||
return permissions.contains { $0.uri == "cooperation_order" }
|
||||
|| permissions.contains(where: { containsCooperationOrder(in: $0) })
|
||||
}
|
||||
|
||||
@ -27,10 +27,10 @@ final class AllFunctionsViewModel {
|
||||
|
||||
/// 加载全部功能并按常用配置拆分。
|
||||
func loadFunctions() {
|
||||
let permissions = appStore.permissionItems()
|
||||
let permissions = appStore.permissions.permissionItems()
|
||||
let allMenus = HomeMenuCatalog.visibleMenus(from: permissions)
|
||||
let accountScope = appStore.accountCachePrefix
|
||||
let roleCode = appStore.roleCode
|
||||
let accountScope = appStore.session.accountCachePrefix
|
||||
let roleCode = appStore.session.roleCode
|
||||
let savedURIs = commonMenuStore.savedCommonURIs(accountScope: accountScope, roleCode: roleCode)
|
||||
let defaultURIs = HomeCommonMenuStore.defaultCommonURIs(from: permissions)
|
||||
let commonURIs = savedURIs.isEmpty ? defaultURIs : savedURIs
|
||||
@ -71,8 +71,8 @@ final class AllFunctionsViewModel {
|
||||
private func persistCommonMenus() {
|
||||
commonMenuStore.saveCommonURIs(
|
||||
commonMenus.map(\.uri),
|
||||
accountScope: appStore.accountCachePrefix,
|
||||
roleCode: appStore.roleCode
|
||||
accountScope: appStore.session.accountCachePrefix,
|
||||
roleCode: appStore.session.roleCode
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ final class HomeViewModel {
|
||||
|
||||
/// 拉取 role-permission 并刷新菜单。
|
||||
func loadPermissions(api: HomeAPI, force: Bool = false) async {
|
||||
if !force, !appStore.permissionItems().isEmpty {
|
||||
if !force, !appStore.permissions.permissionItems().isEmpty {
|
||||
rebuildCommonMenus()
|
||||
await loadStoreListIfNeeded(api: api)
|
||||
notifyStateChange()
|
||||
@ -104,21 +104,21 @@ final class HomeViewModel {
|
||||
do {
|
||||
let rolePermissionList = try await api.rolePermissions()
|
||||
if rolePermissionList.isEmpty {
|
||||
appStore.savePermissionItems([])
|
||||
appStore.permissions.savePermissionItems([])
|
||||
showPermissionDialog = true
|
||||
rebuildCommonMenus()
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
|
||||
appStore.saveRolePermissionList(rolePermissionList)
|
||||
let matched = appStore.matchRolePermissionItem(in: rolePermissionList) ?? rolePermissionList[0]
|
||||
appStore.permissions.saveRolePermissionList(rolePermissionList)
|
||||
let matched = appStore.permissions.matchRolePermissionItem(in: rolePermissionList) ?? rolePermissionList[0]
|
||||
if !matched.role.name.isEmpty {
|
||||
appStore.roleName = matched.role.name
|
||||
appStore.session.roleName = matched.role.name
|
||||
}
|
||||
appStore.saveRoleScenicList(matched.scenic)
|
||||
appStore.permissions.saveRoleScenicList(matched.scenic)
|
||||
let permissions = matched.role.permission.map(HomePermissionItem.init)
|
||||
appStore.savePermissionItems(permissions)
|
||||
appStore.permissions.savePermissionItems(permissions)
|
||||
showPermissionDialog = false
|
||||
refreshLocalDisplayState()
|
||||
rebuildCommonMenus()
|
||||
@ -127,7 +127,7 @@ final class HomeViewModel {
|
||||
} catch is CancellationError {
|
||||
return
|
||||
} catch {
|
||||
appStore.savePermissionItems([])
|
||||
appStore.permissions.savePermissionItems([])
|
||||
showPermissionDialog = true
|
||||
rebuildCommonMenus()
|
||||
onShowMessage?(error.localizedDescription)
|
||||
@ -142,7 +142,7 @@ final class HomeViewModel {
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
storeItem = nil
|
||||
notifyStateChange()
|
||||
return
|
||||
@ -150,13 +150,13 @@ final class HomeViewModel {
|
||||
|
||||
do {
|
||||
let response = try await api.storeList()
|
||||
let scenicId = appStore.currentScenicId
|
||||
let savedStoreId = appStore.currentStoreId
|
||||
let scenicId = appStore.session.currentScenicId
|
||||
let savedStoreId = appStore.session.currentStoreId
|
||||
let savedMatch = savedStoreId > 0 ? response.list.first { $0.id == savedStoreId } : nil
|
||||
let matched = savedMatch ?? response.list.first { $0.scenicId == scenicId }
|
||||
storeItem = matched
|
||||
if let matched {
|
||||
appStore.currentStoreId = matched.id
|
||||
appStore.session.currentStoreId = matched.id
|
||||
}
|
||||
notifyStateChange()
|
||||
} catch is CancellationError {
|
||||
@ -173,7 +173,7 @@ final class HomeViewModel {
|
||||
showScenicDialog = false
|
||||
return
|
||||
}
|
||||
let hasScenic = appStore.currentScenicId > 0
|
||||
let hasScenic = appStore.session.currentScenicId > 0
|
||||
showScenicDialog = !hasScenic
|
||||
if showScenicDialog || dismissedLocationTimeoutDialog {
|
||||
showLocationTimeoutDialog = false
|
||||
@ -196,7 +196,7 @@ final class HomeViewModel {
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
let staffId = appStore.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let staffId = appStore.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !staffId.isEmpty else {
|
||||
showLocationTimeoutDialog = true
|
||||
notifyStateChange()
|
||||
@ -225,25 +225,25 @@ final class HomeViewModel {
|
||||
|
||||
/// 刷新景区名与角色展示状态,对齐 Android `initCurrentScenicName`。
|
||||
func refreshLocalDisplayState() {
|
||||
let scenicId = appStore.currentScenicId
|
||||
let scenicName = appStore.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let scenicId = appStore.session.currentScenicId
|
||||
let scenicName = appStore.session.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if scenicId > 0, !scenicName.isEmpty {
|
||||
currentScenicName = scenicName
|
||||
} else {
|
||||
currentScenicName = ""
|
||||
appStore.currentScenicId = 0
|
||||
appStore.currentScenicName = ""
|
||||
appStore.session.currentScenicId = 0
|
||||
appStore.session.currentScenicName = ""
|
||||
}
|
||||
currentAppRole = appStore.currentAppRole
|
||||
currentAppRole = appStore.session.currentAppRole
|
||||
isMinimalTopRole = currentAppRole?.usesHomeMinimalTopLayout ?? false
|
||||
}
|
||||
|
||||
/// 重建常用应用列表。
|
||||
func rebuildCommonMenus() {
|
||||
let permissions = appStore.permissionItems()
|
||||
let permissions = appStore.permissions.permissionItems()
|
||||
let allMenus = HomeMenuCatalog.visibleMenus(from: permissions)
|
||||
let accountScope = appStore.accountCachePrefix
|
||||
let roleCode = appStore.roleCode
|
||||
let accountScope = appStore.session.accountCachePrefix
|
||||
let roleCode = appStore.session.roleCode
|
||||
let savedURIs = commonMenuStore.savedCommonURIs(accountScope: accountScope, roleCode: roleCode)
|
||||
|
||||
if savedURIs.isEmpty, !permissions.isEmpty {
|
||||
|
||||
@ -21,7 +21,7 @@ final class PermissionApplyStatusViewModel {
|
||||
init(applyCode: String, appStore: AppStore = .shared) {
|
||||
self.applyCode = applyCode
|
||||
self.appStore = appStore
|
||||
rolePermissionList = appStore.rolePermissionList()
|
||||
rolePermissionList = appStore.permissions.rolePermissionList()
|
||||
}
|
||||
|
||||
func loadPendingData(api: HomeAPI) async {
|
||||
|
||||
@ -45,7 +45,7 @@ final class PermissionApplyViewModel {
|
||||
}
|
||||
|
||||
func loadRolesFromLocal() {
|
||||
let rolePermissionList = appStore.rolePermissionList()
|
||||
let rolePermissionList = appStore.permissions.rolePermissionList()
|
||||
var seen = Set<Int>()
|
||||
availableRoles = rolePermissionList.compactMap { item in
|
||||
guard !seen.contains(item.role.id) else { return nil }
|
||||
@ -158,7 +158,7 @@ final class PermissionApplyViewModel {
|
||||
|
||||
/// 已有角色下已开通的景区列表。
|
||||
func existingScenics(for roleId: Int) -> [ScenicInfo] {
|
||||
appStore.rolePermissionList()
|
||||
appStore.permissions.rolePermissionList()
|
||||
.filter { $0.role.id == roleId }
|
||||
.flatMap(\.scenic)
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ final class ScenicSelectionViewModel {
|
||||
searchQuery: searchQuery,
|
||||
currentLat: currentLat,
|
||||
currentLng: currentLng,
|
||||
selectedScenicId: appStore.currentScenicId
|
||||
selectedScenicId: appStore.session.currentScenicId
|
||||
)
|
||||
notifyStateChange()
|
||||
}
|
||||
@ -73,8 +73,8 @@ final class ScenicSelectionViewModel {
|
||||
}
|
||||
|
||||
func selectSpot(_ spot: ScenicSpotDisplayItem) {
|
||||
appStore.currentScenicId = spot.id
|
||||
appStore.currentScenicName = spot.name
|
||||
appStore.session.currentScenicId = spot.id
|
||||
appStore.session.currentScenicName = spot.name
|
||||
NotificationCenter.default.post(
|
||||
name: NotificationName.scenicDidChange,
|
||||
object: nil,
|
||||
@ -109,11 +109,11 @@ final class ScenicSelectionViewModel {
|
||||
}
|
||||
|
||||
private func resolvedScenicList() -> [ScenicInfo] {
|
||||
let cached = appStore.roleScenicList()
|
||||
let cached = appStore.permissions.roleScenicList()
|
||||
if !cached.isEmpty { return cached }
|
||||
let rolePermissionList = appStore.rolePermissionList()
|
||||
let rolePermissionList = appStore.permissions.rolePermissionList()
|
||||
guard !rolePermissionList.isEmpty else { return [] }
|
||||
return appStore.matchRolePermissionItem(in: rolePermissionList)?.scenic ?? []
|
||||
return appStore.permissions.matchRolePermissionItem(in: rolePermissionList)?.scenic ?? []
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
|
||||
@ -34,7 +34,7 @@ final class LiveManageViewModel {
|
||||
private let pageSize = 10
|
||||
|
||||
/// 初始化直播管理 ViewModel。
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId }) {
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId }) {
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
}
|
||||
|
||||
@ -282,7 +282,7 @@ final class LiveAlbumListViewModel {
|
||||
private let pageSize = 10
|
||||
|
||||
/// 初始化直播相册列表 ViewModel。
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId }) {
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId }) {
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
}
|
||||
|
||||
@ -419,7 +419,7 @@ final class LiveAlbumAddViewModel {
|
||||
private let currentScenicIdProvider: () -> Int
|
||||
|
||||
/// 初始化直播相册上传 ViewModel。
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId }) {
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId }) {
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ enum AMapBootstrap {
|
||||
/// 在用户已同意 App 隐私协议后配置高德 SDK,幂等。
|
||||
@discardableResult
|
||||
static func configureIfNeeded(appStore: AppStore = .shared) -> Bool {
|
||||
guard appStore.privacyAgreementAccepted else { return false }
|
||||
guard appStore.session.privacyAgreementAccepted else { return false }
|
||||
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
@ -44,7 +44,7 @@ final class LocationReportService {
|
||||
let remaining = Int((Self.operationIntervalMillis - (now - lastOnlineStatusSwitchTime)) / 1000)
|
||||
throw LocationReportServiceError.tooFrequent(remainingSeconds: remaining)
|
||||
}
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
throw LocationReportServiceError.missingScenic
|
||||
}
|
||||
|
||||
@ -112,10 +112,10 @@ final class LocationReportService {
|
||||
}
|
||||
}
|
||||
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
throw LocationReportServiceError.missingScenic
|
||||
}
|
||||
let staffId = appStore.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let staffId = appStore.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !staffId.isEmpty else {
|
||||
throw LocationReportServiceError.missingStaffId
|
||||
}
|
||||
@ -153,7 +153,7 @@ final class LocationReportService {
|
||||
longitude: snapshot.longitude,
|
||||
address: snapshot.address,
|
||||
type: type,
|
||||
scenicId: String(appStore.currentScenicId)
|
||||
scenicId: String(appStore.session.currentScenicId)
|
||||
)
|
||||
|
||||
lastLocationReportTime = currentTimestampMillis()
|
||||
|
||||
@ -34,7 +34,7 @@ final class LocationReportHistoryViewModel {
|
||||
/// 加载历史上报列表。
|
||||
func loadReportList(api: HomeAPI, refresh: Bool = false) async {
|
||||
guard !isLoading || refresh else { return }
|
||||
let staffId = appStore.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let staffId = appStore.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !staffId.isEmpty else {
|
||||
onShowMessage?("获取用户ID失败")
|
||||
return
|
||||
|
||||
@ -62,13 +62,6 @@ enum MaterialMediaType: Int, CaseIterable, Sendable, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 云盘文件类型,视频为 1、图片为 2。
|
||||
var cloudFileType: Int {
|
||||
switch self {
|
||||
case .image: 2
|
||||
case .video: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 素材列表项,对齐 Android `MaterialItemEntity`。
|
||||
|
||||
@ -141,7 +141,12 @@ final class MaterialListViewModel {
|
||||
)
|
||||
totalCount = response.total
|
||||
orderInfo = response.order ?? MaterialOrderInfo()
|
||||
items = reset ? response.list : items + response.list
|
||||
if reset {
|
||||
items = response.list
|
||||
} else {
|
||||
let existingIDs = Set(items.map(\.id))
|
||||
items.append(contentsOf: response.list.filter { !existingIDs.contains($0.id) })
|
||||
}
|
||||
canLoadMore = items.count < totalCount
|
||||
} catch is CancellationError {
|
||||
return
|
||||
@ -244,6 +249,7 @@ final class MaterialFormViewModel {
|
||||
private(set) var coverImage: MaterialUploadedMediaItem?
|
||||
private(set) var tagList: [MaterialTagItem] = []
|
||||
private(set) var tagInput = ""
|
||||
private(set) var pendingDeleteTag: MaterialTagItem?
|
||||
private(set) var uploadDialogState: MaterialUploadDialogState?
|
||||
private(set) var isSubmitting = false
|
||||
private(set) var isLoadingDetail = false
|
||||
@ -259,7 +265,7 @@ final class MaterialFormViewModel {
|
||||
/// 初始化素材表单 ViewModel。
|
||||
init(
|
||||
mode: Mode = .create,
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId }
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId }
|
||||
) {
|
||||
self.mode = mode
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
@ -295,7 +301,7 @@ final class MaterialFormViewModel {
|
||||
|
||||
/// 更新标签输入。
|
||||
func updateTagInput(_ text: String) {
|
||||
tagInput = text
|
||||
tagInput = String(text.prefix(15))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
@ -327,9 +333,23 @@ final class MaterialFormViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除本地标签。
|
||||
func deleteTag(_ tag: MaterialTagItem) {
|
||||
/// 请求删除标签,等待用户确认。
|
||||
func requestDeleteTag(_ tag: MaterialTagItem) {
|
||||
pendingDeleteTag = tag
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 取消删除标签。
|
||||
func cancelDeleteTag() {
|
||||
pendingDeleteTag = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 确认删除当前待删除标签。
|
||||
func confirmDeleteTag() {
|
||||
guard let tag = pendingDeleteTag else { return }
|
||||
tagList.removeAll { $0.id == tag.id }
|
||||
pendingDeleteTag = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
@ -354,48 +374,6 @@ final class MaterialFormViewModel {
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 增加云盘素材文件,文件已在云盘中时直接使用其 URL。
|
||||
func addCloudMediaFiles(_ files: [CloudFile]) {
|
||||
guard !files.isEmpty else { return }
|
||||
let remaining = mediaType.maxCount - uploadedMediaList.count
|
||||
guard remaining > 0 else {
|
||||
onShowMessage?(mediaType == .video ? "视频只能上传1个" : "图片最多上传9张")
|
||||
return
|
||||
}
|
||||
|
||||
var seenCloudIDs = importedCloudFileIDs
|
||||
var accepted: [MaterialUploadedMediaItem] = []
|
||||
for file in files where accepted.count < remaining {
|
||||
let fileURL = file.fileUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard file.type == mediaType.cloudFileType, !fileURL.isEmpty, !seenCloudIDs.contains(file.id) else {
|
||||
continue
|
||||
}
|
||||
seenCloudIDs.insert(file.id)
|
||||
accepted.append(
|
||||
MaterialUploadedMediaItem(
|
||||
id: "cloud_\(file.id)",
|
||||
data: Data(),
|
||||
thumbnailData: nil,
|
||||
previewURL: file.coverUrl.isEmpty ? fileURL : file.coverUrl,
|
||||
fileName: file.name.isEmpty ? "cloud_file_\(file.id)" : file.name,
|
||||
size: file.fileSize,
|
||||
width: 0,
|
||||
height: 0,
|
||||
ossUrl: fileURL,
|
||||
isUploading: false,
|
||||
uploadProgress: 100
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
guard !accepted.isEmpty else {
|
||||
onShowMessage?(mediaType == .video ? "请选择视频素材" : "请选择图片素材")
|
||||
return
|
||||
}
|
||||
uploadedMediaList.append(contentsOf: accepted)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 设置封面并立即上传。
|
||||
func setCoverImage(_ item: MaterialUploadedMediaItem, uploader: any MaterialOSSUploading) async {
|
||||
coverImage = item
|
||||
@ -418,16 +396,6 @@ final class MaterialFormViewModel {
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 已从云盘导入的文件 ID。
|
||||
var importedCloudFileIDs: Set<Int> {
|
||||
Set(
|
||||
uploadedMediaList.compactMap { item in
|
||||
guard item.id.hasPrefix("cloud_") else { return nil }
|
||||
return Int(item.id.dropFirst("cloud_".count))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// 上传或编辑素材。
|
||||
func submit(api: any MaterialManagementServing) async {
|
||||
guard !materialName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
|
||||
@ -40,7 +40,7 @@ final class MainTabScanHandler {
|
||||
case "check_in":
|
||||
await performCheckIn(scanResult: scanResult)
|
||||
case "order_verify":
|
||||
if AppStore.shared.currentAppRole == .storeAdmin {
|
||||
if AppStore.shared.session.currentAppRole == .storeAdmin {
|
||||
await performStoreVerify(parsed: parsed)
|
||||
} else {
|
||||
await performPhotographerVerify(jsonBody: parsed.rawJSON)
|
||||
@ -92,7 +92,7 @@ final class MainTabScanHandler {
|
||||
onShowMessage?("请扫描正确的二维码")
|
||||
return
|
||||
}
|
||||
json["scenic_id"] = String(AppStore.shared.currentScenicId)
|
||||
json["scenic_id"] = String(AppStore.shared.session.currentScenicId)
|
||||
guard let body = serializeJSON(json) else { return }
|
||||
do {
|
||||
let response = try await api.orderCheckIn(jsonBody: body)
|
||||
@ -116,7 +116,7 @@ final class MainTabScanHandler {
|
||||
onShowMessage?("请扫描正确的核销码")
|
||||
return
|
||||
}
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeId = AppStore.shared.session.currentStoreId
|
||||
guard storeId > 0 else {
|
||||
onShowMessage?("店铺信息异常,请先选择店铺")
|
||||
return
|
||||
|
||||
@ -102,7 +102,7 @@ final class DepositOrderListViewModel {
|
||||
onShowMessage?("核销码内容不完整")
|
||||
return
|
||||
}
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeId = AppStore.shared.session.currentStoreId
|
||||
guard storeId > 0 else {
|
||||
onShowMessage?("店铺信息异常,请先选择店铺")
|
||||
return
|
||||
@ -125,7 +125,7 @@ final class DepositOrderListViewModel {
|
||||
refundReason: String,
|
||||
api: OrderAPI
|
||||
) async {
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeId = AppStore.shared.session.currentStoreId
|
||||
guard storeId > 0 else {
|
||||
onShowMessage?("店铺信息异常")
|
||||
return
|
||||
@ -166,7 +166,7 @@ final class DepositOrderListViewModel {
|
||||
let phone = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let isPhoneSearch = !phone.isEmpty && Self.isValidPhone(phone)
|
||||
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeId = AppStore.shared.session.currentStoreId
|
||||
if storeId <= 0 {
|
||||
finishLoading()
|
||||
orderList = []
|
||||
|
||||
@ -122,7 +122,7 @@ final class OrderListViewModel {
|
||||
}()
|
||||
|
||||
var isScenicAdminReadOnly: Bool {
|
||||
AppStore.shared.currentAppRole == .scenicAdmin
|
||||
AppStore.shared.session.currentAppRole == .scenicAdmin
|
||||
}
|
||||
|
||||
var listAPIPath: String {
|
||||
@ -359,7 +359,7 @@ final class OrderListViewModel {
|
||||
isLoadingOrderSourcePeople = false
|
||||
notifyStateChange()
|
||||
}
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeId = AppStore.shared.session.currentStoreId
|
||||
let storeIdParam = storeId > 0 ? String(storeId) : nil
|
||||
do {
|
||||
let response = try await api.cooperativeSalers(storeId: storeIdParam)
|
||||
@ -402,7 +402,7 @@ final class OrderListViewModel {
|
||||
}
|
||||
|
||||
private func loadOrderList(api: OrderAPI, append: Bool) async {
|
||||
let scenicId = AppStore.shared.currentScenicId
|
||||
let scenicId = AppStore.shared.session.currentScenicId
|
||||
guard scenicId > 0 else {
|
||||
finishLoading()
|
||||
onShowMessage?("当前景区信息缺失")
|
||||
@ -479,7 +479,7 @@ final class OrderListViewModel {
|
||||
var json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
json["scenic_id"] = String(AppStore.shared.currentScenicId)
|
||||
json["scenic_id"] = String(AppStore.shared.session.currentScenicId)
|
||||
guard let updated = try? JSONSerialization.data(withJSONObject: json),
|
||||
let string = String(data: updated, encoding: .utf8) else {
|
||||
return nil
|
||||
|
||||
@ -40,9 +40,9 @@ final class PaymentCollectionDetailsViewModel {
|
||||
}
|
||||
|
||||
func refreshLocalState() {
|
||||
scenicName = appStore.currentScenicName
|
||||
staffId = appStore.userId
|
||||
isVoiceBroadcastOpen = appStore.isOpenReceiveVoice
|
||||
scenicName = appStore.session.currentScenicName
|
||||
staffId = appStore.session.userId
|
||||
isVoiceBroadcastOpen = appStore.payment.isOpenReceiveVoice
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ final class PaymentCollectionDetailsViewModel {
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
let scenicId = appStore.currentScenicId
|
||||
let scenicId = appStore.session.currentScenicId
|
||||
guard scenicId > 0 else {
|
||||
clearPayCode()
|
||||
return
|
||||
@ -134,7 +134,7 @@ final class PaymentCollectionDetailsViewModel {
|
||||
|
||||
func toggleReceiveVoice() {
|
||||
isVoiceBroadcastOpen.toggle()
|
||||
appStore.isOpenReceiveVoice = isVoiceBroadcastOpen
|
||||
appStore.payment.isOpenReceiveVoice = isVoiceBroadcastOpen
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ final class PaymentCollectionRecordViewModel {
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
let scenicId = appStore.currentScenicId
|
||||
let scenicId = appStore.session.currentScenicId
|
||||
guard scenicId > 0 else {
|
||||
groups = []
|
||||
return
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -36,7 +36,7 @@ final class PunchPointListViewModel {
|
||||
private let currentScenicIdProvider: () -> Int
|
||||
|
||||
/// 初始化打卡点列表 ViewModel。
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId }) {
|
||||
init(currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId }) {
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ final class PunchPointFormViewModel {
|
||||
/// 初始化打卡点表单 ViewModel。
|
||||
init(
|
||||
mode: Mode = .create,
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId },
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId },
|
||||
locationProvider: any LocationProviding = LocationProvider.shared
|
||||
) {
|
||||
self.mode = mode
|
||||
|
||||
@ -222,8 +222,8 @@ final class UploadSampleViewModel {
|
||||
|
||||
/// 初始化样片上传 ViewModel。
|
||||
init(
|
||||
scenicListProvider: @escaping () -> [ScenicInfo] = { AppStore.shared.roleScenicList() },
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId }
|
||||
scenicListProvider: @escaping () -> [ScenicInfo] = { AppStore.shared.permissions.roleScenicList() },
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId }
|
||||
) {
|
||||
self.scenicListProvider = scenicListProvider
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
|
||||
@ -84,7 +84,7 @@ final class AliyunNLSTTSManager: NSObject, ScenicQueueTTSManaging, AVSpeechSynth
|
||||
|
||||
func prepareQueueTTSEngine(api: ScenicQueueAPIProtocol?) async {
|
||||
guard let api else { return }
|
||||
guard !appStore.scenicQueueUseOfflineTTS || supportsOfflineTTS else {
|
||||
guard !appStore.scenicQueue.useOfflineTTS || supportsOfflineTTS else {
|
||||
nativeReady = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ final class ScenicQueueSettingChangeLogViewModel {
|
||||
}
|
||||
|
||||
private func load(api: ScenicQueueAPIProtocol, page: Int, append: Bool) async {
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
@ -53,10 +53,10 @@ final class ScenicQueueSettingChangeLogViewModel {
|
||||
if append { isLoadingMore = false } else { isRefreshing = false }
|
||||
notifyStateChange()
|
||||
}
|
||||
let spotId = Int64(appStore.scenicQueuePunchSpotId)
|
||||
let spotId = Int64(appStore.scenicQueue.punchSpotId)
|
||||
do {
|
||||
let data = try await api.settingChangeLog(
|
||||
scenicId: Int64(appStore.currentScenicId),
|
||||
scenicId: Int64(appStore.session.currentScenicId),
|
||||
scenicSpotId: spotId,
|
||||
page: page,
|
||||
pageSize: Constants.pageSize
|
||||
|
||||
@ -61,9 +61,9 @@ final class ScenicQueueSettingsViewModel {
|
||||
init(appStore: AppStore = .shared, ttsManager: ScenicQueueTTSManaging = AliyunNLSTTSManager.shared) {
|
||||
self.appStore = appStore
|
||||
self.ttsManager = ttsManager
|
||||
let snapshot = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
selectedPunchSpotId = appStore.scenicQueuePunchSpotId.nonEmptyTrimmed
|
||||
selectedPunchSpotLabel = appStore.scenicQueuePunchSpotName
|
||||
let snapshot = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
selectedPunchSpotId = appStore.scenicQueue.punchSpotId.nonEmptyTrimmed
|
||||
selectedPunchSpotLabel = appStore.scenicQueue.punchSpotName
|
||||
shootMinute = snapshot.shootMinute.clamped(to: Limits.shootMinute)
|
||||
shootSecond = snapshot.shootSecond.clamped(to: Limits.shootSecond)
|
||||
firstAheadCount = snapshot.firstAheadCount.clamped(to: Limits.ahead)
|
||||
@ -84,8 +84,8 @@ final class ScenicQueueSettingsViewModel {
|
||||
businessOpen = snapshot.businessOpen
|
||||
businessStartTime = Self.normalizedTime(snapshot.businessStartTime, fallback: "10:00")
|
||||
businessEndTime = Self.normalizedTime(snapshot.businessEndTime, fallback: "20:00")
|
||||
customTTSText = appStore.scenicQueueRemark
|
||||
presetVoices = appStore.scenicQueuePresetVoices()
|
||||
customTTSText = appStore.scenicQueue.remark
|
||||
presetVoices = appStore.scenicQueue.presetVoices()
|
||||
}
|
||||
|
||||
var selectedPunchSpotDisplayText: String {
|
||||
@ -105,7 +105,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
|
||||
/// 是否优先使用离线语音合成。
|
||||
var useOfflineTTS: Bool {
|
||||
appStore.scenicQueueUseOfflineTTS
|
||||
appStore.scenicQueue.useOfflineTTS
|
||||
}
|
||||
|
||||
func openSettingChangeLog() {
|
||||
@ -114,12 +114,12 @@ final class ScenicQueueSettingsViewModel {
|
||||
|
||||
/// 打开打卡点选择器前加载全部打卡点。
|
||||
func loadPunchSpotOptions(api: ScenicQueueAPIProtocol) async {
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
do {
|
||||
let response = try await api.scenicSpotListAll(scenicId: String(appStore.currentScenicId))
|
||||
let response = try await api.scenicSpotListAll(scenicId: String(appStore.session.currentScenicId))
|
||||
punchSpotOptions = response.list.map {
|
||||
ScenicQueuePunchSpotOption(id: String($0.id), label: $0.name)
|
||||
}
|
||||
@ -178,17 +178,17 @@ final class ScenicQueueSettingsViewModel {
|
||||
/// 设置离线语音合成偏好。
|
||||
func setUseOfflineTTS(_ enabled: Bool) {
|
||||
guard enabled else {
|
||||
appStore.scenicQueueUseOfflineTTS = false
|
||||
appStore.scenicQueue.useOfflineTTS = false
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
guard ttsManager.supportsOfflineTTS else {
|
||||
appStore.scenicQueueUseOfflineTTS = false
|
||||
appStore.scenicQueue.useOfflineTTS = false
|
||||
onShowMessage?("离线语音资源未安装")
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
appStore.scenicQueueUseOfflineTTS = true
|
||||
appStore.scenicQueue.useOfflineTTS = true
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
@ -234,7 +234,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
|
||||
/// 保存当前文案到本地预设。
|
||||
func saveCurrentTextAsPresetVoiceLocal() {
|
||||
guard appStore.currentScenicId > 0, selectedPunchSpotId?.nonEmptyTrimmed != nil else {
|
||||
guard appStore.session.currentScenicId > 0, selectedPunchSpotId?.nonEmptyTrimmed != nil else {
|
||||
onShowMessage?("请先选择打卡点")
|
||||
return
|
||||
}
|
||||
@ -252,7 +252,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
return
|
||||
}
|
||||
presetVoices.append(text)
|
||||
appStore.saveScenicQueuePresetVoices(presetVoices)
|
||||
appStore.scenicQueue.savePresetVoices(presetVoices)
|
||||
onShowMessage?("已保存到本地")
|
||||
notifyStateChange()
|
||||
}
|
||||
@ -260,7 +260,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
func deletePresetVoice(at index: Int) {
|
||||
guard presetVoices.indices.contains(index) else { return }
|
||||
let removed = presetVoices.remove(at: index)
|
||||
appStore.saveScenicQueuePresetVoices(presetVoices)
|
||||
appStore.scenicQueue.savePresetVoices(presetVoices)
|
||||
if ttsLoopAnchorText == removed {
|
||||
ttsManager.stopCustomTextLoop()
|
||||
ttsLoopAnchorText = nil
|
||||
@ -332,11 +332,11 @@ final class ScenicQueueSettingsViewModel {
|
||||
)
|
||||
do {
|
||||
try await api.saveSetting(request)
|
||||
appStore.scenicQueuePunchSpotId = String(ids.scenicSpotId)
|
||||
appStore.scenicQueuePunchSpotName = selectedPunchSpotLabel
|
||||
appStore.scenicQueueRemark = remark
|
||||
appStore.saveScenicQueuePresetVoices(presetVoices)
|
||||
appStore.saveScenicQueueSettingsSnapshot(currentSnapshot())
|
||||
appStore.scenicQueue.punchSpotId = String(ids.scenicSpotId)
|
||||
appStore.scenicQueue.punchSpotName = selectedPunchSpotLabel
|
||||
appStore.scenicQueue.remark = remark
|
||||
appStore.scenicQueue.savePresetVoices(presetVoices)
|
||||
appStore.scenicQueue.saveSettingsSnapshot(currentSnapshot())
|
||||
_ = try? await api.stats(scenicId: ids.scenicId, scenicSpotId: ids.scenicSpotId)
|
||||
onShowMessage?("保存成功")
|
||||
onNavigateBack?()
|
||||
@ -346,7 +346,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
}
|
||||
|
||||
private func fetchQueueSettingAfterPunchSpotSelected(_ spotId: String, api: ScenicQueueAPIProtocol) async {
|
||||
guard let scenicId = Int64("\(appStore.currentScenicId)"),
|
||||
guard let scenicId = Int64("\(appStore.session.currentScenicId)"),
|
||||
let scenicSpotId = Int64(spotId),
|
||||
scenicId > 0,
|
||||
scenicSpotId > 0 else { return }
|
||||
@ -480,7 +480,7 @@ final class ScenicQueueSettingsViewModel {
|
||||
}
|
||||
|
||||
private func currentScenicAndSpotIdsForSelected() -> (scenicId: Int64, scenicSpotId: Int64)? {
|
||||
guard let scenicId = Int64("\(appStore.currentScenicId)"), scenicId > 0,
|
||||
guard let scenicId = Int64("\(appStore.session.currentScenicId)"), scenicId > 0,
|
||||
let spotIdText = selectedPunchSpotId?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
let scenicSpotId = Int64(spotIdText), scenicSpotId > 0 else { return nil }
|
||||
return (scenicId, scenicSpotId)
|
||||
|
||||
@ -97,8 +97,8 @@ final class ScenicQueueViewModel {
|
||||
/// 开始 WebSocket 订阅。
|
||||
func startQueueStatsPolling(api: ScenicQueueAPIProtocol) {
|
||||
guard queueGatePassed,
|
||||
let scenicId = Int64("\(appStore.currentScenicId)"),
|
||||
let scenicSpotId = Int64(appStore.scenicQueuePunchSpotId),
|
||||
let scenicId = Int64("\(appStore.session.currentScenicId)"),
|
||||
let scenicSpotId = Int64(appStore.scenicQueue.punchSpotId),
|
||||
scenicId > 0,
|
||||
scenicSpotId > 0 else { return }
|
||||
Task {
|
||||
@ -186,16 +186,16 @@ final class ScenicQueueViewModel {
|
||||
/// 确认用户标记。
|
||||
func confirmUserMark(api: ScenicQueueAPIProtocol, markAsFreelancePhotog: Bool, queueBanDays: Int) async {
|
||||
guard let pendingMark else { return }
|
||||
guard appStore.currentScenicId > 0 else {
|
||||
guard appStore.session.currentScenicId > 0 else {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
let request = ScenicQueueUserMarkRequest(
|
||||
uid: pendingMark.uid,
|
||||
scenicId: Int64(appStore.currentScenicId),
|
||||
scenicId: Int64(appStore.session.currentScenicId),
|
||||
markAsFreelancePhotog: markAsFreelancePhotog ? 1 : 0,
|
||||
queueBanDays: markAsFreelancePhotog ? min(max(queueBanDays, 0), 999) : nil,
|
||||
operatorId: Int(appStore.userId)
|
||||
operatorId: Int(appStore.session.userId)
|
||||
)
|
||||
do {
|
||||
try await api.userMark(request)
|
||||
@ -270,7 +270,7 @@ final class ScenicQueueViewModel {
|
||||
func confirmRequeueTicket(api: ScenicQueueAPIProtocol) async {
|
||||
guard let pending = pendingRequeue else { return }
|
||||
dismissRequeueDialog()
|
||||
guard let operatorId = Int(appStore.userId), operatorId > 0 else {
|
||||
guard let operatorId = Int(appStore.session.userId), operatorId > 0 else {
|
||||
onShowMessage?("请先登录")
|
||||
return
|
||||
}
|
||||
@ -395,18 +395,18 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func refreshSelectedPunchSpotLine() {
|
||||
selectedPunchSpotLine = appStore.hasScenicQueuePunchSpotSaved()
|
||||
? appStore.scenicQueuePunchSpotName.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "--"
|
||||
selectedPunchSpotLine = appStore.scenicQueue.hasPunchSpotSaved()
|
||||
? appStore.scenicQueue.punchSpotName.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "--"
|
||||
: "--"
|
||||
refreshShootingCallConfig()
|
||||
}
|
||||
|
||||
private func refreshQueueGateLocally() {
|
||||
queueGatePassed = appStore.hasScenicQueuePunchSpotSaved()
|
||||
queueGatePassed = appStore.scenicQueue.hasPunchSpotSaved()
|
||||
}
|
||||
|
||||
private func refreshShootingCallConfig() {
|
||||
let snapshot = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
let snapshot = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
showStartShootingButton = snapshot.showStartShootingButton
|
||||
quickCallButtonEnabled = snapshot.quickCallButtonEnabled
|
||||
prepareCallButtonEnabled = snapshot.prepareCallButtonEnabled
|
||||
@ -419,18 +419,18 @@ final class ScenicQueueViewModel {
|
||||
guard data.exists, let setting = data.setting,
|
||||
setting.scenicId == nil || setting.scenicId == ids.scenicId,
|
||||
setting.scenicSpotId == nil || setting.scenicSpotId == ids.scenicSpotId else { return }
|
||||
let local = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
appStore.saveScenicQueueSettingsSnapshot(setting.toSnapshot(fallback: local))
|
||||
appStore.scenicQueueRemark = setting.remark ?? ""
|
||||
let local = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
appStore.scenicQueue.saveSettingsSnapshot(setting.toSnapshot(fallback: local))
|
||||
appStore.scenicQueue.remark = setting.remark ?? ""
|
||||
if let voices = setting.voiceBroadcasts {
|
||||
appStore.saveScenicQueuePresetVoices(
|
||||
appStore.scenicQueue.savePresetVoices(
|
||||
voices
|
||||
.sorted { ($0.sortOrder ?? Int.max) < ($1.sortOrder ?? Int.max) }
|
||||
.compactMap { $0.content?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty }
|
||||
)
|
||||
}
|
||||
if let name = setting.scenicSpotName?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty {
|
||||
appStore.scenicQueuePunchSpotName = name
|
||||
appStore.scenicQueue.punchSpotName = name
|
||||
selectedPunchSpotLine = name
|
||||
}
|
||||
refreshShootingCallConfig()
|
||||
@ -501,7 +501,7 @@ final class ScenicQueueViewModel {
|
||||
private func handleSocketMessage(_ message: ScenicQueueSocketMessage) {
|
||||
guard let params = message.data?.params,
|
||||
let changedSpotId = params.scenicSpotId,
|
||||
let currentSpotId = Int64(appStore.scenicQueuePunchSpotId),
|
||||
let currentSpotId = Int64(appStore.scenicQueue.punchSpotId),
|
||||
changedSpotId == currentSpotId else { return }
|
||||
switch message.data?.action {
|
||||
case ScenicQueueSocketAction.queueUpdated.rawValue:
|
||||
@ -516,7 +516,7 @@ final class ScenicQueueViewModel {
|
||||
|
||||
private func handleRemoteTicketCalled(_ params: ScenicQueueSocketParams) {
|
||||
guard let recordId = params.recordId, recordId > 0 else { return }
|
||||
if let myUid = Int64(appStore.userId), let operatorUid = params.operatorUid, myUid == operatorUid {
|
||||
if let myUid = Int64(appStore.session.userId), let operatorUid = params.operatorUid, myUid == operatorUid {
|
||||
return
|
||||
}
|
||||
let eventId = params.eventId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
@ -614,7 +614,7 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func resolveShootingTiming() -> (totalSeconds: Int, intervalSeconds: Int, readableThresholdSeconds: Int) {
|
||||
let snapshot = appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
let snapshot = appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()
|
||||
let total = max(0, snapshot.shootMinute * 60 + snapshot.shootSecond)
|
||||
let interval = (40...60).contains(snapshot.broadcastIntervalSec) ? snapshot.broadcastIntervalSec : 50
|
||||
let readable = (10...30).contains(snapshot.countdownThresholdSec) ? snapshot.countdownThresholdSec : 15
|
||||
@ -627,7 +627,7 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func speakFollowingTickets(after queueNo: String) async {
|
||||
let count = (appStore.scenicQueueSettingsSnapshot() ?? ScenicQueueSettingsSnapshot()).autoCallAheadCount.clamped(to: 0...5)
|
||||
let count = (appStore.scenicQueue.settingsSnapshot() ?? ScenicQueueSettingsSnapshot()).autoCallAheadCount.clamped(to: 0...5)
|
||||
guard count > 0, let index = currentQueue.firstIndex(where: { $0.queueNo == queueNo }) else { return }
|
||||
let nextNos = currentQueue.dropFirst(index + 1).prefix(count).map(\.queueNo).filter { !$0.isEmpty }
|
||||
guard !nextNos.isEmpty else { return }
|
||||
@ -639,9 +639,9 @@ final class ScenicQueueViewModel {
|
||||
}
|
||||
|
||||
private func currentScenicAndSpotIds() -> (scenicId: Int64, scenicSpotId: Int64)? {
|
||||
let scenicId = Int64(appStore.currentScenicId)
|
||||
let scenicId = Int64(appStore.session.currentScenicId)
|
||||
guard scenicId > 0,
|
||||
let scenicSpotId = Int64(appStore.scenicQueuePunchSpotId),
|
||||
let scenicSpotId = Int64(appStore.scenicQueue.punchSpotId),
|
||||
scenicSpotId > 0 else { return nil }
|
||||
return (scenicId, scenicSpotId)
|
||||
}
|
||||
|
||||
@ -213,9 +213,9 @@ final class StatisticsViewModel {
|
||||
let storeId = try resolveStoreId()
|
||||
return try await api.storeSummary(storeId: storeId, range: range)
|
||||
case .scenicAdmin:
|
||||
return try await api.scenicAdminSummary(scenicId: AppStore.shared.currentScenicId, range: range)
|
||||
return try await api.scenicAdminSummary(scenicId: AppStore.shared.session.currentScenicId, range: range)
|
||||
default:
|
||||
return try await api.photographerSummary(scenicId: AppStore.shared.currentScenicId, range: range)
|
||||
return try await api.photographerSummary(scenicId: AppStore.shared.session.currentScenicId, range: range)
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,14 +231,14 @@ final class StatisticsViewModel {
|
||||
)
|
||||
case .scenicAdmin:
|
||||
return try await api.scenicAdminDaily(
|
||||
scenicId: AppStore.shared.currentScenicId,
|
||||
scenicId: AppStore.shared.session.currentScenicId,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
page: currentPage
|
||||
)
|
||||
default:
|
||||
return try await api.photographerDaily(
|
||||
scenicId: AppStore.shared.currentScenicId,
|
||||
scenicId: AppStore.shared.session.currentScenicId,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
page: currentPage
|
||||
@ -247,11 +247,11 @@ final class StatisticsViewModel {
|
||||
}
|
||||
|
||||
private var currentRole: AppRoleCode {
|
||||
AppStore.shared.currentAppRole ?? .photographer
|
||||
AppStore.shared.session.currentAppRole ?? .photographer
|
||||
}
|
||||
|
||||
private func resolveStoreId() throws -> Int {
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeId = AppStore.shared.session.currentStoreId
|
||||
guard storeId > 0 else {
|
||||
throw StatisticsFlowError.missingStore
|
||||
}
|
||||
|
||||
@ -262,7 +262,7 @@ final class TaskAddViewModel {
|
||||
)
|
||||
}
|
||||
return AddTaskRequest(
|
||||
scenicId: appStore.currentScenicId,
|
||||
scenicId: appStore.session.currentScenicId,
|
||||
name: name ?? taskName,
|
||||
orderNumber: selectedOrderNumber,
|
||||
remark: taskDetails,
|
||||
|
||||
@ -32,7 +32,7 @@ final class TaskOrderSelectViewModel {
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
let scenicId = appStore.currentScenicId
|
||||
let scenicId = appStore.session.currentScenicId
|
||||
guard scenicId > 0 else {
|
||||
orders = []
|
||||
onShowMessage?("请先选择景区")
|
||||
|
||||
@ -151,10 +151,10 @@ struct TravelAlbumOTGStorageContext: Equatable, Sendable {
|
||||
/// 使用当前登录账号创建存储上下文。
|
||||
static var current: TravelAlbumOTGStorageContext {
|
||||
TravelAlbumOTGStorageContext(
|
||||
accountCachePrefix: AppStore.shared.accountCachePrefix,
|
||||
userId: AppStore.shared.userId,
|
||||
scenicId: AppStore.shared.currentScenicId,
|
||||
storeId: AppStore.shared.currentStoreId
|
||||
accountCachePrefix: AppStore.shared.session.accountCachePrefix,
|
||||
userId: AppStore.shared.session.userId,
|
||||
scenicId: AppStore.shared.session.currentScenicId,
|
||||
storeId: AppStore.shared.session.currentStoreId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ final class TravelAlbumCameraImportViewModel {
|
||||
status: .pending,
|
||||
progress: 0,
|
||||
albumId: albumId,
|
||||
userId: appStore.userId
|
||||
userId: appStore.session.userId
|
||||
)
|
||||
storage.upsert(record, albumId: albumId)
|
||||
existingPhotoIds.insert(object.id)
|
||||
|
||||
@ -44,7 +44,7 @@ final class TravelAlbumEntryViewModel {
|
||||
private let dateProvider: () -> Date
|
||||
|
||||
init(
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.currentScenicId },
|
||||
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId },
|
||||
dateProvider: @escaping () -> Date = Date.init
|
||||
) {
|
||||
self.currentScenicIdProvider = currentScenicIdProvider
|
||||
|
||||
@ -383,7 +383,7 @@ final class WiredCameraTransferViewModel {
|
||||
fileSizeBytes: Int64(item.data.count),
|
||||
status: .pending,
|
||||
albumId: albumId,
|
||||
userId: appStore.userId
|
||||
userId: appStore.session.userId
|
||||
)
|
||||
storage.upsert(record, albumId: albumId)
|
||||
importedPhotoIds.append(record.id)
|
||||
@ -553,7 +553,7 @@ final class WiredCameraTransferViewModel {
|
||||
updateRecord(id: id, status: .uploading, progress: max(record.progress, 1), error: nil)
|
||||
let material = try await uploader.upload(
|
||||
record: record,
|
||||
scenicId: appStore.currentScenicId
|
||||
scenicId: appStore.session.currentScenicId
|
||||
) { [weak self] progress in
|
||||
self?.updateRecord(id: id, status: .uploading, progress: progress, error: nil)
|
||||
}
|
||||
@ -596,7 +596,7 @@ final class WiredCameraTransferViewModel {
|
||||
progress: progress,
|
||||
errorMessage: error,
|
||||
albumId: albumId,
|
||||
userId: appStore.userId,
|
||||
userId: appStore.session.userId,
|
||||
remoteUrl: remoteUrl ?? item.remoteUrl
|
||||
)
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ final class WalletViewModel {
|
||||
/// 初始化钱包首页 ViewModel。
|
||||
init(
|
||||
staffIdProvider: @escaping () -> Int? = {
|
||||
Int(AppStore.shared.userId.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
Int(AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
},
|
||||
calendar: Calendar = Calendar(identifier: .gregorian)
|
||||
) {
|
||||
@ -480,7 +480,7 @@ final class PointsRedemptionViewModel {
|
||||
|
||||
/// 初始化积分兑现 ViewModel。
|
||||
init(staffIdProvider: @escaping () -> Int? = {
|
||||
Int(AppStore.shared.userId.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
Int(AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}) {
|
||||
self.staffIdProvider = staffIdProvider
|
||||
}
|
||||
|
||||
@ -1057,7 +1057,7 @@ final class WildReportRiskMapViewModel {
|
||||
init() {
|
||||
self.markers = []
|
||||
self.region = Self.region(for: [])
|
||||
let scenicName = AppStore.shared.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let scenicName = AppStore.shared.session.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.scenicAreaName = scenicName.isEmpty ? "当前景区" : scenicName
|
||||
self.selectedMarkerID = nil
|
||||
}
|
||||
@ -1203,7 +1203,7 @@ final class WildReportRiskMapViewModel {
|
||||
scenicName: String?,
|
||||
selectCurrentLocation: Bool
|
||||
) async {
|
||||
let resolvedScenicId = scenicId ?? AppStore.shared.currentScenicId
|
||||
let resolvedScenicId = scenicId ?? AppStore.shared.session.currentScenicId
|
||||
guard resolvedScenicId >= 1 else {
|
||||
errorMessage = "请先选择景区后再查看风险地图"
|
||||
notifyStateChange()
|
||||
@ -1244,7 +1244,7 @@ final class WildReportRiskMapViewModel {
|
||||
))
|
||||
scenicAreaName = response.scenicName.nonEmpty
|
||||
?? scenicName?.nonEmpty
|
||||
?? AppStore.shared.currentScenicName.nonEmpty
|
||||
?? AppStore.shared.session.currentScenicName.nonEmpty
|
||||
?? scenicAreaName
|
||||
greenMarkerTip = response.greenMarkerTip
|
||||
legendItems = response.legend
|
||||
@ -1376,7 +1376,7 @@ final class WildReportRiskMapViewModel {
|
||||
for marker: WildReportMapMarker,
|
||||
scenicId: Int?
|
||||
) -> WildReportMarkerDetailRequest? {
|
||||
let resolvedScenicId = scenicId ?? AppStore.shared.currentScenicId
|
||||
let resolvedScenicId = scenicId ?? AppStore.shared.session.currentScenicId
|
||||
guard resolvedScenicId >= 1 else { return nil }
|
||||
let parts = marker.id.split(separator: "-", maxSplits: 1).map(String.init)
|
||||
guard parts.count == 2, let id = Int(parts[1]), id >= 1 else { return nil }
|
||||
|
||||
Reference in New Issue
Block a user