基于 Lottie 在关键流程中新增全局 Loading 遮罩

引入带引用计数的 GlobalLoadingCenter,通过 RootView 及主要登录/订单/个人中心页面接入,并添加 Loading 动画资源与单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-23 11:25:56 +08:00
parent 5541a0b106
commit 47848b88f2
17 changed files with 514 additions and 139 deletions

View File

@ -15,6 +15,7 @@ struct OrdersView: View {
@Environment(RouterPath.self) private var router
@Environment(OrdersAPI.self) private var ordersAPI
@Environment(ToastCenter.self) private var toastCenter
@Environment(\.globalLoading) private var globalLoading
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@State private var viewModel = OrdersViewModel()
@ -227,10 +228,10 @@ struct OrdersView: View {
private var storeList: some View {
LazyVStack(spacing: AppMetrics.Spacing.small) {
if viewModel.loading {
ProgressView()
if viewModel.loading && viewModel.storeOrders.isEmpty {
Color.clear
.frame(maxWidth: .infinity)
.padding(.vertical, AppMetrics.Spacing.xxLarge)
.frame(minHeight: 260)
} else if viewModel.storeOrders.isEmpty {
ContentUnavailableView("暂无订单", systemImage: "tray", description: Text("可切换筛选条件或下拉刷新。"))
.frame(minHeight: 260)
@ -300,10 +301,10 @@ struct OrdersView: View {
private var writeOffList: some View {
LazyVStack(spacing: AppMetrics.Spacing.small) {
if viewModel.loading {
ProgressView()
if viewModel.loading && viewModel.writeOffOrders.isEmpty {
Color.clear
.frame(maxWidth: .infinity)
.padding(.vertical, AppMetrics.Spacing.xxLarge)
.frame(minHeight: 260)
} else if viewModel.writeOffOrders.isEmpty {
ContentUnavailableView("暂无核销订单", systemImage: "tray", description: Text("可下拉刷新或切换景区查看。"))
.frame(minHeight: 260)
@ -516,18 +517,31 @@ struct OrdersView: View {
private func reload(showLoading: Bool = true) async {
do {
try await viewModel.reload(
api: ordersAPI,
scenicId: currentScenicId,
storeId: currentStoreId,
roleId: currentRoleId,
showLoading: showLoading
)
try await globalLoading.withOptionalLoading(shouldShowGlobalLoading(showLoading: showLoading), message: "加载中...") {
try await viewModel.reload(
api: ordersAPI,
scenicId: currentScenicId,
storeId: currentStoreId,
roleId: currentRoleId,
showLoading: showLoading
)
}
} catch {
toastCenter.show(error.localizedDescription)
}
}
/// 使 Loading
private func shouldShowGlobalLoading(showLoading: Bool) -> Bool {
guard showLoading, currentScenicId != nil else { return false }
switch viewModel.selectedEntry {
case .storeOrders:
return viewModel.storeOrders.isEmpty
case .verificationOrders:
return viewModel.writeOffOrders.isEmpty
}
}
private func loadMoreStoreOrders() async {
do {
try await viewModel.loadMoreStoreOrders(api: ordersAPI, scenicId: currentScenicId, roleId: currentRoleId)
@ -567,7 +581,9 @@ struct OrdersView: View {
private func verify(orderNumber: String) async {
guard let scenicId = currentScenicId else { return }
do {
try await viewModel.verify(api: ordersAPI, scenicId: scenicId, storeId: currentStoreId, orderNumber: orderNumber)
try await globalLoading.withLoading(message: "核销中...") {
try await viewModel.verify(api: ordersAPI, scenicId: scenicId, storeId: currentStoreId, orderNumber: orderNumber)
}
manualOrderNumber = ""
pendingVerifyOrder = nil
pendingVerifyOrderNumber = nil