Introduce configurable custom TabBar with order badge, central scanner routing to verification orders, and update the migration checklist for recently completed modules. Co-authored-by: Cursor <cursoragent@cursor.com>
156 lines
4.7 KiB
Swift
156 lines
4.7 KiB
Swift
//
|
||
// CustomMainTabBar.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/23.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
/// 自定义主 TabBar,展示四个一级入口和中间扫码核销按钮。
|
||
struct CustomMainTabBar: View {
|
||
@Binding var selectedTab: AppTab
|
||
|
||
let orderBadgeText: String?
|
||
let onScanTap: () -> Void
|
||
|
||
private let items = CustomMainTabItem.defaultItems
|
||
|
||
var body: some View {
|
||
HStack(alignment: .center, spacing: 0) {
|
||
CustomMainTabBarItem(
|
||
item: items[0],
|
||
selectedTab: $selectedTab,
|
||
badgeText: nil
|
||
)
|
||
CustomMainTabBarItem(
|
||
item: items[1],
|
||
selectedTab: $selectedTab,
|
||
badgeText: orderBadgeText
|
||
)
|
||
CustomMainTabBarCenterAction(onScanTap: onScanTap)
|
||
.frame(width: 74)
|
||
CustomMainTabBarItem(
|
||
item: items[2],
|
||
selectedTab: $selectedTab,
|
||
badgeText: nil
|
||
)
|
||
CustomMainTabBarItem(
|
||
item: items[3],
|
||
selectedTab: $selectedTab,
|
||
badgeText: nil
|
||
)
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
.frame(height: 72)
|
||
.padding(.horizontal, 22)
|
||
.background(.white)
|
||
.overlay(alignment: .top) {
|
||
Rectangle()
|
||
.fill(Color.black.opacity(0.04))
|
||
.frame(height: 0.5)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 自定义 TabBar 单个入口的静态配置。
|
||
private struct CustomMainTabItem {
|
||
let tab: AppTab
|
||
let title: String
|
||
let selectedImage: String
|
||
let unselectedImage: String
|
||
|
||
static let defaultItems: [CustomMainTabItem] = [
|
||
.init(tab: .home, title: "首页", selectedImage: "TabHomeSelected", unselectedImage: "TabHomeUnselected"),
|
||
.init(tab: .orders, title: "订单", selectedImage: "TabOrderSelected", unselectedImage: "TabOrderUnselected"),
|
||
.init(tab: .statistics, title: "数据", selectedImage: "TabDataSelected", unselectedImage: "TabDataUnselected"),
|
||
.init(tab: .profile, title: "我的", selectedImage: "TabProfileSelected", unselectedImage: "TabProfileUnselected")
|
||
]
|
||
}
|
||
|
||
/// 自定义 TabBar 的普通入口按钮。
|
||
private struct CustomMainTabBarItem: View {
|
||
let item: CustomMainTabItem
|
||
@Binding var selectedTab: AppTab
|
||
let badgeText: String?
|
||
|
||
private var isSelected: Bool {
|
||
selectedTab == item.tab
|
||
}
|
||
|
||
var body: some View {
|
||
Button {
|
||
selectedTab = item.tab
|
||
} label: {
|
||
VStack(spacing: 3) {
|
||
ZStack(alignment: .topTrailing) {
|
||
Image(isSelected ? item.selectedImage : item.unselectedImage)
|
||
.renderingMode(.original)
|
||
.resizable()
|
||
.scaledToFit()
|
||
.frame(width: 24, height: 24)
|
||
|
||
if let badgeText {
|
||
CustomMainTabBadge(text: badgeText)
|
||
}
|
||
}
|
||
Text(item.title)
|
||
.font(.system(size: 12, weight: isSelected ? .medium : .regular))
|
||
.foregroundStyle(isSelected ? AppDesign.primary : Color(hex: 0x7D8DA3))
|
||
.lineLimit(1)
|
||
.minimumScaleFactor(0.8)
|
||
}
|
||
.frame(maxWidth: .infinity)
|
||
.frame(height: 58)
|
||
.contentShape(Rectangle())
|
||
}
|
||
.buttonStyle(.plain)
|
||
.accessibilityLabel(item.title)
|
||
}
|
||
}
|
||
|
||
/// 自定义 TabBar 中间的扫码核销按钮。
|
||
private struct CustomMainTabBarCenterAction: View {
|
||
let onScanTap: () -> Void
|
||
|
||
var body: some View {
|
||
Button(action: onScanTap) {
|
||
ZStack {
|
||
Circle()
|
||
.fill(AppDesign.primary)
|
||
.frame(width: 58, height: 58)
|
||
Image(systemName: "qrcode.viewfinder")
|
||
.font(.system(size: 30, weight: .bold))
|
||
.foregroundStyle(.white)
|
||
}
|
||
.frame(width: 70, height: 64)
|
||
.contentShape(Circle())
|
||
}
|
||
.buttonStyle(.plain)
|
||
.accessibilityLabel("扫码核销")
|
||
}
|
||
}
|
||
|
||
/// 自定义 TabBar 的订单数量角标。
|
||
private struct CustomMainTabBadge: View {
|
||
let text: String
|
||
|
||
var body: some View {
|
||
Text(text)
|
||
.font(.system(size: 9, weight: .bold))
|
||
.foregroundStyle(.white)
|
||
.frame(minWidth: 15, minHeight: 15)
|
||
.padding(.horizontal, 2)
|
||
.background(Color(hex: 0xEF4444), in: Capsule())
|
||
.offset(x: 9, y: -7)
|
||
}
|
||
}
|
||
|
||
#Preview {
|
||
CustomMainTabBar(
|
||
selectedTab: .constant(.orders),
|
||
orderBadgeText: "8",
|
||
onScanTap: {}
|
||
)
|
||
}
|