添加景区选择与合作订单流程并对齐 Android
This commit is contained in:
@ -212,9 +212,17 @@ final class HomeViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// 刷新景区名与角色展示状态。
|
||||
/// 刷新景区名与角色展示状态,对齐 Android `initCurrentScenicName`。
|
||||
func refreshLocalDisplayState() {
|
||||
currentScenicName = appStore.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let scenicId = appStore.currentScenicId
|
||||
let scenicName = appStore.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if scenicId > 0, !scenicName.isEmpty {
|
||||
currentScenicName = scenicName
|
||||
} else {
|
||||
currentScenicName = ""
|
||||
appStore.currentScenicId = 0
|
||||
appStore.currentScenicName = ""
|
||||
}
|
||||
currentAppRole = appStore.currentAppRole
|
||||
isMinimalTopRole = currentAppRole?.usesHomeMinimalTopLayout ?? false
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
//
|
||||
// PermissionApplyStatusViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 权限申请审核状态 ViewModel,对齐 Android `PermissionApplyStatusViewModel`。
|
||||
final class PermissionApplyStatusViewModel {
|
||||
|
||||
private(set) var pendingData: RoleApplyPendingResponse?
|
||||
private(set) var isLoading = false
|
||||
private(set) var rolePermissionList: [RolePermissionResponse] = []
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onNavigateToEdit: ((RoleApplyPendingResponse) -> Void)?
|
||||
|
||||
private let applyCode: String
|
||||
private let appStore: AppStore
|
||||
|
||||
init(applyCode: String, appStore: AppStore = .shared) {
|
||||
self.applyCode = applyCode
|
||||
self.appStore = appStore
|
||||
rolePermissionList = appStore.rolePermissionList()
|
||||
}
|
||||
|
||||
func loadPendingData(api: HomeAPI) async {
|
||||
isLoading = true
|
||||
notifyStateChange()
|
||||
do {
|
||||
let pendingList = try await api.roleApplyPending()
|
||||
pendingData = pendingList.first { $0.code == applyCode }
|
||||
} catch {
|
||||
pendingData = nil
|
||||
}
|
||||
isLoading = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func navigateToEditIfRejected() {
|
||||
guard let pendingData, pendingData.status == PermissionApplyPendingStatus.rejected else { return }
|
||||
onNavigateToEdit?(pendingData)
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,199 @@
|
||||
//
|
||||
// PermissionApplyViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 权限申请 ViewModel,对齐 Android `PermissionApplyViewModel`。
|
||||
final class PermissionApplyViewModel {
|
||||
|
||||
private(set) var availableRoles: [RoleOption] = []
|
||||
private(set) var selectedRoleId: Int?
|
||||
private(set) var selectedRoleName = ""
|
||||
private(set) var availableScenics: [ScenicOption] = []
|
||||
private(set) var selectedScenicIds: [Int] = []
|
||||
private(set) var selectedScenicNames: [String] = []
|
||||
private(set) var isLoadingScenics = false
|
||||
private(set) var isSubmitting = false
|
||||
private(set) var showRolePicker = false
|
||||
private(set) var showScenicPicker = false
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
var onSubmitSuccess: ((String) -> Void)?
|
||||
|
||||
private let appStore: AppStore
|
||||
|
||||
init(appStore: AppStore = .shared) {
|
||||
self.appStore = appStore
|
||||
loadRolesFromLocal()
|
||||
}
|
||||
|
||||
/// 从待审核数据预填表单(status=3 去编辑)。
|
||||
func initWithPendingData(_ pending: RoleApplyPendingResponse) {
|
||||
selectedRoleId = pending.roleId
|
||||
selectedRoleName = pending.roleName
|
||||
selectedScenicIds = pending.scenicList.map(\.id)
|
||||
selectedScenicNames = pending.scenicList.map(\.name)
|
||||
availableRoles = availableRoles.map { role in
|
||||
var copy = role
|
||||
copy.isSelected = role.id == pending.roleId
|
||||
return copy
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func loadRolesFromLocal() {
|
||||
let rolePermissionList = appStore.rolePermissionList()
|
||||
var seen = Set<Int>()
|
||||
availableRoles = rolePermissionList.compactMap { item in
|
||||
guard !seen.contains(item.role.id) else { return nil }
|
||||
seen.insert(item.role.id)
|
||||
return RoleOption(
|
||||
id: item.role.id,
|
||||
name: item.role.name,
|
||||
description: item.role.notes ?? "",
|
||||
isSelected: false
|
||||
)
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func showRolePickerSheet() {
|
||||
showRolePicker = true
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func hideRolePickerSheet() {
|
||||
showRolePicker = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func selectRole(_ role: RoleOption) {
|
||||
selectedRoleId = role.id
|
||||
selectedRoleName = role.name
|
||||
availableRoles = availableRoles.map { item in
|
||||
var copy = item
|
||||
copy.isSelected = item.id == role.id
|
||||
return copy
|
||||
}
|
||||
selectedScenicIds = []
|
||||
selectedScenicNames = []
|
||||
availableScenics = []
|
||||
showRolePicker = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func showScenicPickerSheet() {
|
||||
guard selectedRoleId != nil else {
|
||||
onShowMessage?("请先选择角色")
|
||||
return
|
||||
}
|
||||
showScenicPicker = true
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func hideScenicPickerSheet() {
|
||||
showScenicPicker = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func loadAvailableScenics(api: HomeAPI?) async {
|
||||
guard let roleId = selectedRoleId else { return }
|
||||
guard let api else { return }
|
||||
isLoadingScenics = true
|
||||
notifyStateChange()
|
||||
do {
|
||||
let response = try await api.scenicListAll()
|
||||
let existingIds = existingScenicIds(for: roleId)
|
||||
availableScenics = response.list.map { scenic in
|
||||
let isExisting = existingIds.contains(scenic.id)
|
||||
let isPreselected = selectedScenicIds.contains(scenic.id)
|
||||
return ScenicOption(
|
||||
id: scenic.id,
|
||||
name: scenic.name,
|
||||
isSelected: isExisting || isPreselected,
|
||||
isDisabled: isExisting
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
onShowMessage?("获取景区列表失败")
|
||||
}
|
||||
isLoadingScenics = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func toggleScenic(_ scenic: ScenicOption) {
|
||||
guard !scenic.isDisabled else { return }
|
||||
if selectedScenicIds.contains(scenic.id) {
|
||||
selectedScenicIds.removeAll { $0 == scenic.id }
|
||||
selectedScenicNames.removeAll { $0 == scenic.name }
|
||||
} else {
|
||||
selectedScenicIds.append(scenic.id)
|
||||
selectedScenicNames.append(scenic.name)
|
||||
}
|
||||
availableScenics = availableScenics.map { item in
|
||||
var copy = item
|
||||
if !copy.isDisabled {
|
||||
copy.isSelected = selectedScenicIds.contains(copy.id)
|
||||
}
|
||||
return copy
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func removeSelectedScenic(id: Int, name: String) {
|
||||
selectedScenicIds.removeAll { $0 == id }
|
||||
selectedScenicNames.removeAll { $0 == name }
|
||||
availableScenics = availableScenics.map { item in
|
||||
var copy = item
|
||||
if !copy.isDisabled {
|
||||
copy.isSelected = selectedScenicIds.contains(copy.id)
|
||||
}
|
||||
return copy
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 已有角色下已开通的景区列表。
|
||||
func existingScenics(for roleId: Int) -> [ScenicInfo] {
|
||||
appStore.rolePermissionList()
|
||||
.filter { $0.role.id == roleId }
|
||||
.flatMap(\.scenic)
|
||||
}
|
||||
|
||||
func submit(api: HomeAPI) async {
|
||||
guard let roleId = selectedRoleId else {
|
||||
onShowMessage?("请先选择角色")
|
||||
return
|
||||
}
|
||||
let newScenicIds = selectedScenicIds.filter { id in
|
||||
!existingScenicIds(for: roleId).contains(id)
|
||||
}
|
||||
guard !newScenicIds.isEmpty else {
|
||||
onShowMessage?("请至少选择一个景区")
|
||||
return
|
||||
}
|
||||
|
||||
isSubmitting = true
|
||||
notifyStateChange()
|
||||
do {
|
||||
let result = try await api.submitRoleApply(scenicIds: newScenicIds, roleId: roleId)
|
||||
onShowMessage?("提交成功")
|
||||
onSubmitSuccess?(result.code)
|
||||
} catch {
|
||||
onShowMessage?("提交失败,请稍后重试")
|
||||
}
|
||||
isSubmitting = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
private func existingScenicIds(for roleId: Int) -> Set<Int> {
|
||||
Set(existingScenics(for: roleId).map(\.id))
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
//
|
||||
// ScenicSelectionViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 权限申请审核中状态,对齐 Android role-apply pending status。
|
||||
enum PermissionApplyPendingStatus {
|
||||
static let reviewing = 1
|
||||
static let rejected = 3
|
||||
}
|
||||
|
||||
/// 景区选择 ViewModel,对齐 Android `ScenicSelectionViewModel`。
|
||||
final class ScenicSelectionViewModel {
|
||||
|
||||
private(set) var spots: [ScenicSpotDisplayItem] = []
|
||||
private(set) var searchQuery = ""
|
||||
private(set) var currentLocationText = "最近的景区"
|
||||
private(set) var isLoading = false
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
var onSelectionComplete: (() -> Void)?
|
||||
var onNavigateApply: (() -> Void)?
|
||||
var onNavigateApplyStatus: ((String) -> Void)?
|
||||
|
||||
private let appStore: AppStore
|
||||
private let locationProvider: HomeLocationProvider
|
||||
private var currentLat: Double?
|
||||
private var currentLng: Double?
|
||||
|
||||
init(appStore: AppStore = .shared, locationProvider: HomeLocationProvider = HomeLocationProvider()) {
|
||||
self.appStore = appStore
|
||||
self.locationProvider = locationProvider
|
||||
}
|
||||
|
||||
func loadFromLocal() {
|
||||
let scenicList = resolvedScenicList()
|
||||
spots = ScenicSpotDisplayMapper.buildItems(
|
||||
scenicList: scenicList,
|
||||
searchQuery: searchQuery,
|
||||
currentLat: currentLat,
|
||||
currentLng: currentLng,
|
||||
selectedScenicId: appStore.currentScenicId
|
||||
)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func onSearchQueryChange(_ query: String) {
|
||||
searchQuery = query
|
||||
loadFromLocal()
|
||||
}
|
||||
|
||||
func startLocation() async {
|
||||
isLoading = true
|
||||
notifyStateChange()
|
||||
do {
|
||||
let snapshot = try await locationProvider.requestSnapshot()
|
||||
currentLat = snapshot.latitude
|
||||
currentLng = snapshot.longitude
|
||||
let address = snapshot.address.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
currentLocationText = address.isEmpty ? "最近的景区" : address
|
||||
loadFromLocal()
|
||||
} catch HomeLocationProviderError.permissionDenied {
|
||||
onShowMessage?("未授予定位权限,无法获取附近景区")
|
||||
} catch {
|
||||
onShowMessage?("定位失败: \(error.localizedDescription)")
|
||||
}
|
||||
isLoading = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func selectSpot(_ spot: ScenicSpotDisplayItem) {
|
||||
appStore.currentScenicId = spot.id
|
||||
appStore.currentScenicName = spot.name
|
||||
NotificationCenter.default.post(
|
||||
name: NotificationName.scenicDidChange,
|
||||
object: nil,
|
||||
userInfo: [
|
||||
NotificationUserInfoKey.scenicId: spot.id,
|
||||
NotificationUserInfoKey.scenicName: spot.name,
|
||||
]
|
||||
)
|
||||
loadFromLocal()
|
||||
onSelectionComplete?()
|
||||
}
|
||||
|
||||
func onApplyNow(api: HomeAPI) async {
|
||||
isLoading = true
|
||||
notifyStateChange()
|
||||
defer {
|
||||
isLoading = false
|
||||
notifyStateChange()
|
||||
}
|
||||
do {
|
||||
let pendingList = try await api.roleApplyPending()
|
||||
if let first = pendingList.first,
|
||||
first.status == PermissionApplyPendingStatus.reviewing
|
||||
|| first.status == PermissionApplyPendingStatus.rejected {
|
||||
onNavigateApplyStatus?(first.code)
|
||||
} else {
|
||||
onNavigateApply?()
|
||||
}
|
||||
} catch {
|
||||
onNavigateApply?()
|
||||
}
|
||||
}
|
||||
|
||||
private func resolvedScenicList() -> [ScenicInfo] {
|
||||
let cached = appStore.roleScenicList()
|
||||
if !cached.isEmpty { return cached }
|
||||
let rolePermissionList = appStore.rolePermissionList()
|
||||
guard !rolePermissionList.isEmpty else { return [] }
|
||||
return appStore.matchRolePermissionItem(in: rolePermissionList)?.scenic ?? []
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user