新增门店订单与核销管理列表,对齐 Android 订单能力并完善账号级缓存。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -36,8 +36,21 @@ final class OrdersViewModel: ObservableObject {
|
||||
@Published var orderSourceSelections: [String: OrderSourceSelection] = [:]
|
||||
@Published var loadingOrderSourceLeadsFor: String?
|
||||
|
||||
@Published var refundingOrder: OrderEntity?
|
||||
@Published var sendGiftContext: SendGiftContext?
|
||||
|
||||
private var loadingOrderSourcePeople = false
|
||||
|
||||
/// 赠送修图/视频弹窗上下文。
|
||||
struct SendGiftContext: Equatable, Identifiable {
|
||||
var id: String { orderNumber }
|
||||
let orderNumber: String
|
||||
let nowPhotoNum: Int
|
||||
let nowVideoNum: Int
|
||||
var sendPhotoNum: Int
|
||||
var sendVideoNum: Int
|
||||
}
|
||||
|
||||
static let orderTypePhotographerFollow = 11
|
||||
static let orderTypePhotographerFollowOfflineScan = 14
|
||||
|
||||
@ -125,7 +138,17 @@ final class OrdersViewModel: ObservableObject {
|
||||
if showLoading { loading = true }
|
||||
defer { if showLoading { loading = false } }
|
||||
|
||||
let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize)
|
||||
let result = try await api.writeOffList(
|
||||
scenicId: scenicId,
|
||||
storeId: storeId,
|
||||
page: 1,
|
||||
pageSize: pageSize,
|
||||
orderStatus: nil,
|
||||
orderVerifyStatus: nil,
|
||||
startTime: nil,
|
||||
endTime: nil,
|
||||
userPhone: nil
|
||||
)
|
||||
writeOffOrders = result.list
|
||||
writeOffTotal = result.total
|
||||
writeOffPage = 1
|
||||
@ -139,7 +162,17 @@ final class OrdersViewModel: ObservableObject {
|
||||
defer { loadingMore = false }
|
||||
|
||||
let nextPage = writeOffPage + 1
|
||||
let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: nextPage, pageSize: pageSize)
|
||||
let result = try await api.writeOffList(
|
||||
scenicId: scenicId,
|
||||
storeId: storeId,
|
||||
page: nextPage,
|
||||
pageSize: pageSize,
|
||||
orderStatus: nil,
|
||||
orderVerifyStatus: nil,
|
||||
startTime: nil,
|
||||
endTime: nil,
|
||||
userPhone: nil
|
||||
)
|
||||
writeOffOrders.append(contentsOf: result.list)
|
||||
writeOffTotal = result.total
|
||||
writeOffPage = nextPage
|
||||
@ -158,13 +191,82 @@ final class OrdersViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
try await api.writeOff(orderNumber: orderNumber)
|
||||
let refreshed = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize)
|
||||
let refreshed = try await api.writeOffList(
|
||||
scenicId: scenicId,
|
||||
storeId: storeId,
|
||||
page: 1,
|
||||
pageSize: pageSize,
|
||||
orderStatus: nil,
|
||||
orderVerifyStatus: nil,
|
||||
startTime: nil,
|
||||
endTime: nil,
|
||||
userPhone: nil
|
||||
)
|
||||
writeOffOrders = refreshed.list
|
||||
writeOffTotal = refreshed.total
|
||||
writeOffPage = 1
|
||||
writeOffHasMore = writeOffOrders.count < refreshed.total && !refreshed.list.isEmpty
|
||||
}
|
||||
|
||||
/// 判断订单卡片是否展示退款按钮。
|
||||
func shouldShowRefundButton(order: OrderEntity, appRole: AppRoleCode?) -> Bool {
|
||||
guard !Self.isScenicAdminReadOnly(appRole: appRole) else { return false }
|
||||
return (order.orderStatus == 18 || order.orderStatus == 30) && order.orderType != 19
|
||||
}
|
||||
|
||||
/// 判断订单卡片是否展示核销按钮。
|
||||
func shouldShowVerifyButton(order: OrderEntity, appRole: AppRoleCode?) -> Bool {
|
||||
guard !Self.isScenicAdminReadOnly(appRole: appRole) else { return false }
|
||||
if order.orderType == 19 { return true }
|
||||
guard let photoTravel = order.photoTravel else { return false }
|
||||
return photoTravel.needCheckIn && order.orderStatus != 30
|
||||
}
|
||||
|
||||
/// 更新订单精修选择并同步本地列表状态。
|
||||
func updateRefinedChoice(api: OrderServing, orderNumber: String, isRefined: Int) async throws {
|
||||
try await api.updateRefinedChoice(orderNumber: orderNumber, isRefined: isRefined)
|
||||
if let index = storeOrders.firstIndex(where: { $0.orderNumber == orderNumber }) {
|
||||
storeOrders[index].isRefined = isRefined
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消订单并刷新列表。
|
||||
func cancelOrder(api: OrderServing, scenicId: Int?, appRole: AppRoleCode?, orderNumber: String) async throws {
|
||||
try await api.cancelOrder(orderNumber: orderNumber)
|
||||
try await reloadStoreOrders(api: api, scenicId: scenicId, appRole: appRole, showLoading: false)
|
||||
}
|
||||
|
||||
/// 打开赠送修图/视频弹窗。
|
||||
func prepareSendGift(for order: OrderEntity) {
|
||||
let photoTravel = order.photoTravel
|
||||
sendGiftContext = SendGiftContext(
|
||||
orderNumber: order.orderNumber,
|
||||
nowPhotoNum: photoTravel?.orderPhotoNum ?? 0,
|
||||
nowVideoNum: photoTravel?.orderVideoNum ?? 0,
|
||||
sendPhotoNum: photoTravel?.retouchGiftPhotoNum ?? 0,
|
||||
sendVideoNum: photoTravel?.retouchGiftVideoNum ?? 0
|
||||
)
|
||||
}
|
||||
|
||||
/// 提交赠送修图/视频并刷新列表。
|
||||
func submitSendGift(
|
||||
api: OrderServing,
|
||||
scenicId: Int?,
|
||||
appRole: AppRoleCode?,
|
||||
photoNum: Int,
|
||||
videoNum: Int
|
||||
) async throws {
|
||||
guard let context = sendGiftContext else { return }
|
||||
try await api.sendGift(orderNumber: context.orderNumber, photoNum: photoNum, videoNum: videoNum)
|
||||
sendGiftContext = nil
|
||||
try await reloadStoreOrders(api: api, scenicId: scenicId, appRole: appRole, showLoading: false)
|
||||
}
|
||||
|
||||
/// 当前筛选标题。
|
||||
var currentFilterTitle: String {
|
||||
OrderFilters.statusFilters.first(where: { $0.id == selectedStatus })?.title ?? "全部"
|
||||
}
|
||||
|
||||
/// 判断订单卡片是否展示订单来源入口。
|
||||
func shouldShowOrderSource(order: OrderEntity, appRole: AppRoleCode?) -> Bool {
|
||||
guard !Self.isScenicAdminReadOnly(appRole: appRole) else { return false }
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
//
|
||||
// StoreAdminOrderListViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
/// 店长订单 Tab ViewModel,对齐 Android DepositOrderListViewModel。
|
||||
final class StoreAdminOrderListViewModel: ObservableObject {
|
||||
@Published var searchPhone = ""
|
||||
@Published var selectedStatus = 0
|
||||
@Published var filterStartDate: Date?
|
||||
@Published var filterEndDate: Date?
|
||||
@Published private(set) var orders: [StoreOrderListItem] = []
|
||||
@Published private(set) var loading = false
|
||||
@Published private(set) var loadingMore = false
|
||||
@Published private(set) var total = 0
|
||||
@Published private(set) var page = 1
|
||||
@Published private(set) var hasMore = false
|
||||
|
||||
private let pageSize = 20
|
||||
|
||||
/// 返回去除首尾空白后的手机号搜索值。
|
||||
var normalizedPhone: String? {
|
||||
let text = searchPhone.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return text.isEmpty ? nil : text
|
||||
}
|
||||
|
||||
/// 当前筛选标题。
|
||||
var currentFilterTitle: String {
|
||||
OrderFilters.storeAdminStatusFilters.first(where: { $0.id == selectedStatus })?.title ?? "全部"
|
||||
}
|
||||
|
||||
/// 刷新店长订单列表。
|
||||
func reload(api: OrderServing, storeId: Int?, showLoading: Bool = true) async throws {
|
||||
guard let storeId, storeId > 0 else {
|
||||
orders = []
|
||||
total = 0
|
||||
page = 1
|
||||
hasMore = false
|
||||
return
|
||||
}
|
||||
|
||||
if showLoading { loading = true }
|
||||
defer { if showLoading { loading = false } }
|
||||
|
||||
let result = try await api.storeOrderList(
|
||||
storeId: storeId,
|
||||
page: 1,
|
||||
pageSize: pageSize,
|
||||
userPhone: normalizedPhone,
|
||||
orderStatus: orderStatusQuery,
|
||||
orderType: nil,
|
||||
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
|
||||
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate)
|
||||
)
|
||||
orders = result.list
|
||||
total = result.total
|
||||
page = 1
|
||||
hasMore = orders.count < result.total && !result.list.isEmpty
|
||||
}
|
||||
|
||||
/// 加载下一页。
|
||||
func loadMore(api: OrderServing, storeId: Int?) async throws {
|
||||
guard !loadingMore, hasMore, let storeId, storeId > 0 else { return }
|
||||
loadingMore = true
|
||||
defer { loadingMore = false }
|
||||
|
||||
let nextPage = page + 1
|
||||
let result = try await api.storeOrderList(
|
||||
storeId: storeId,
|
||||
page: nextPage,
|
||||
pageSize: pageSize,
|
||||
userPhone: normalizedPhone,
|
||||
orderStatus: orderStatusQuery,
|
||||
orderType: nil,
|
||||
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
|
||||
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate)
|
||||
)
|
||||
orders.append(contentsOf: result.list)
|
||||
total = result.total
|
||||
page = nextPage
|
||||
hasMore = orders.count < result.total && !result.list.isEmpty
|
||||
}
|
||||
|
||||
/// 提交店长订单退款。
|
||||
func refund(
|
||||
api: OrderServing,
|
||||
storeId: Int?,
|
||||
orderNumber: String,
|
||||
mode: OrderRefundMode,
|
||||
amount: String,
|
||||
reason: String
|
||||
) async throws {
|
||||
guard let storeId, storeId > 0 else { return }
|
||||
try await api.storeOrderRefund(
|
||||
storeId: storeId,
|
||||
orderNumber: orderNumber,
|
||||
refundType: mode,
|
||||
refundAmount: amount,
|
||||
refundReason: reason
|
||||
)
|
||||
}
|
||||
|
||||
private var orderStatusQuery: Int? {
|
||||
selectedStatus > 0 ? selectedStatus : nil
|
||||
}
|
||||
}
|
||||
141
suixinkan/Features/Orders/ViewModels/WriteOffListViewModel.swift
Normal file
141
suixinkan/Features/Orders/ViewModels/WriteOffListViewModel.swift
Normal file
@ -0,0 +1,141 @@
|
||||
//
|
||||
// WriteOffListViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
/// 核销管理页 ViewModel,对齐 Android WriteOffListViewModel。
|
||||
final class WriteOffListViewModel: ObservableObject {
|
||||
@Published var searchPhone = ""
|
||||
@Published var selectedStatus = 0
|
||||
@Published var filterStartDate: Date?
|
||||
@Published var filterEndDate: Date?
|
||||
@Published private(set) var orders: [WriteOffOrderItem] = []
|
||||
@Published private(set) var loading = false
|
||||
@Published private(set) var loadingMore = false
|
||||
@Published private(set) var isVerifying = false
|
||||
@Published private(set) var total = 0
|
||||
@Published private(set) var page = 1
|
||||
@Published private(set) var hasMore = false
|
||||
|
||||
private let pageSize = 20
|
||||
|
||||
/// 返回去除首尾空白后的手机号搜索值。
|
||||
var normalizedPhone: String? {
|
||||
let text = searchPhone.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return text.isEmpty ? nil : text
|
||||
}
|
||||
|
||||
/// 刷新核销列表。
|
||||
func reload(api: OrderServing, scenicId: Int?, storeId: Int? = nil, showLoading: Bool = true) async throws {
|
||||
guard let scenicId else {
|
||||
orders = []
|
||||
total = 0
|
||||
page = 1
|
||||
hasMore = false
|
||||
return
|
||||
}
|
||||
|
||||
if showLoading { loading = true }
|
||||
defer { if showLoading { loading = false } }
|
||||
|
||||
let result = try await api.writeOffList(
|
||||
scenicId: scenicId,
|
||||
storeId: storeId,
|
||||
page: 1,
|
||||
pageSize: pageSize,
|
||||
orderStatus: orderStatusQuery,
|
||||
orderVerifyStatus: verifyStatusQuery,
|
||||
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
|
||||
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate),
|
||||
userPhone: normalizedPhone
|
||||
)
|
||||
orders = result.list
|
||||
total = result.total
|
||||
page = 1
|
||||
hasMore = orders.count < result.total && !result.list.isEmpty
|
||||
}
|
||||
|
||||
/// 加载下一页。
|
||||
func loadMore(api: OrderServing, scenicId: Int?, storeId: Int? = nil) async throws {
|
||||
guard !loadingMore, hasMore, let scenicId else { return }
|
||||
loadingMore = true
|
||||
defer { loadingMore = false }
|
||||
|
||||
let nextPage = page + 1
|
||||
let result = try await api.writeOffList(
|
||||
scenicId: scenicId,
|
||||
storeId: storeId,
|
||||
page: nextPage,
|
||||
pageSize: pageSize,
|
||||
orderStatus: orderStatusQuery,
|
||||
orderVerifyStatus: verifyStatusQuery,
|
||||
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
|
||||
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate),
|
||||
userPhone: normalizedPhone
|
||||
)
|
||||
orders.append(contentsOf: result.list)
|
||||
total = result.total
|
||||
page = nextPage
|
||||
hasMore = orders.count < result.total && !result.list.isEmpty
|
||||
}
|
||||
|
||||
/// 核销订单并刷新列表。
|
||||
func verify(api: OrderServing, scenicId: Int, storeId: Int? = nil, orderNumber: String) async throws {
|
||||
guard !isVerifying else { return }
|
||||
isVerifying = true
|
||||
defer { isVerifying = false }
|
||||
|
||||
try await api.writeOff(orderNumber: orderNumber)
|
||||
try await reload(api: api, scenicId: scenicId, storeId: storeId, showLoading: false)
|
||||
}
|
||||
|
||||
/// 解析扫码结果并返回当前列表中的匹配订单。
|
||||
func matchedWriteOffOrder(for rawCode: String) -> (orderNumber: String, matched: WriteOffOrderItem?)? {
|
||||
guard let orderNumber = OrderNumberParser.parse(rawCode) else { return nil }
|
||||
let matched = orders.first {
|
||||
$0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame
|
||||
}
|
||||
return (matched?.orderNumber ?? orderNumber, matched)
|
||||
}
|
||||
|
||||
/// 根据订单号查找当前列表中的订单。
|
||||
func writeOffOrder(matching orderNumber: String) -> WriteOffOrderItem? {
|
||||
orders.first { $0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame }
|
||||
}
|
||||
|
||||
/// 判断订单是否仍可核销。
|
||||
func canVerify(_ item: WriteOffOrderItem) -> Bool {
|
||||
let status = displayVerificationStatus(for: item)
|
||||
return !status.contains("已核销") && !status.contains("已退款") && !status.contains("已完成")
|
||||
}
|
||||
|
||||
/// 返回核销状态展示文案。
|
||||
func displayVerificationStatus(for item: WriteOffOrderItem) -> String {
|
||||
let text = item.orderVerificationStatus.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return text.isEmpty ? "待核销" : text
|
||||
}
|
||||
|
||||
private var orderStatusQuery: Int? {
|
||||
switch selectedStatus {
|
||||
case 18, 30:
|
||||
return selectedStatus
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var verifyStatusQuery: Int? {
|
||||
switch selectedStatus {
|
||||
case 1, 2:
|
||||
return selectedStatus
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user