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>
200 lines
6.1 KiB
Swift
200 lines
6.1 KiB
Swift
//
|
||
// 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?()
|
||
}
|
||
}
|