Add scenic selection and cooperation order flows aligned with Android.

Upgrade scenic picker with search, location sorting, permission apply/status, and blocking home dialog; add cooperation order module and order source picker improvements with unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 10:48:43 +08:00
parent 3c4ca7eefb
commit 2bea05b1d9
50 changed files with 5603 additions and 191 deletions

View File

@ -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?()
}
}