新增自定义主 TabBar 与全局扫码核销流程

引入可配置的自定义 TabBar(含订单角标)、中央扫码入口跳转核销订单页,并更新迁移清单中最近完成的模块。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-23 10:52:45 +08:00
parent b766ad8270
commit 5541a0b106
25 changed files with 740 additions and 34 deletions

View File

@ -0,0 +1,155 @@
//
// 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: {}
)
}