接入友盟统计与 U-APM,并优化宣传页打卡点标签交互。

友盟 SDK 改为隐私同意后再初始化;宣传页标签新增滑块位移动画,超过 5 个时选中项自动滚动居中。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-03 09:23:15 +08:00
parent c7f557f361
commit 00c3cd0a93
134 changed files with 6555 additions and 590 deletions

View File

@ -154,6 +154,8 @@ struct LoginView: View {
return
}
authSessionCoordinator.savePrivacyAgreementAccepted(viewModel.privacyChecked)
Task {
do {
try await globalLoading.withLoading(message: "登录中...") {

View File

@ -7,12 +7,28 @@
import SwiftUI
/// 5 5
/// 5 5
struct ScenicPromotionSpotTabsView: View {
let spots: [ScenicPromotionSpot]
let selectedSpotID: String?
let onSelectSpot: (ScenicPromotionSpot) -> Void
/// 线
private static let selectionAnimation = Animation.spring(response: 0.35, dampingFraction: 0.82)
/// 线
private static let scrollAnimation = Animation.easeInOut(duration: 0.25)
/// 退 0
private var selectedIndex: Int {
guard let selectedSpotID else { return 0 }
return spots.firstIndex(where: { $0.id == selectedSpotID }) ?? 0
}
/// 5
private var shouldEnableScroll: Bool {
spots.count > Int(ScenicPromotionLayout.visibleSpotTabCount)
}
var body: some View {
if spots.isEmpty {
EmptyView()
@ -22,22 +38,38 @@ struct ScenicPromotionSpotTabsView: View {
.frame(height: 42)
.overlay {
GeometryReader { proxy in
let visibleCount = min(CGFloat(max(spots.count, 1)), ScenicPromotionLayout.visibleSpotTabCount)
let visibleCount = min(
CGFloat(max(spots.count, 1)),
ScenicPromotionLayout.visibleSpotTabCount
)
let tabWidth = proxy.size.width / visibleCount
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(spots) { spot in
ScenicPromotionSpotTabButton(
spot: spot,
isSelected: selectedSpotID == spot.id,
onTap: { onSelectSpot(spot) }
)
.frame(width: tabWidth)
ScrollViewReader { scrollProxy in
ScrollView(.horizontal, showsIndicators: false) {
ZStack(alignment: .leading) {
selectionBackground(width: tabWidth)
HStack(spacing: 0) {
ForEach(spots) { spot in
ScenicPromotionSpotTabButton(
spot: spot,
isSelected: selectedSpotID == spot.id,
onTap: { onSelectSpot(spot) }
)
.frame(width: tabWidth)
.id(spot.id)
}
}
}
}
.scrollDisabled(!shouldEnableScroll)
.onAppear {
scrollSelectedTabToCenter(using: scrollProxy, animated: false)
}
.onChange(of: selectedSpotID) { _ in
scrollSelectedTabToCenter(using: scrollProxy, animated: true)
}
}
.scrollDisabled(spots.count <= Int(ScenicPromotionLayout.visibleSpotTabCount))
}
}
.background(.white.opacity(0.84), in: RoundedRectangle(cornerRadius: 14))
@ -49,6 +81,35 @@ struct ScenicPromotionSpotTabsView: View {
.shadow(color: Color(hex: 0x0D47A1, alpha: 0.12), radius: 12, y: 5)
}
}
///
@ViewBuilder
private func selectionBackground(width: CGFloat) -> some View {
RoundedRectangle(cornerRadius: 14)
.fill(
LinearGradient(
colors: [Color(hex: 0x1286FF), Color(hex: 0x006BFF)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: width, height: 42)
.offset(x: CGFloat(selectedIndex) * width)
.animation(Self.selectionAnimation, value: selectedIndex)
}
///
private func scrollSelectedTabToCenter(using scrollProxy: ScrollViewProxy, animated: Bool) {
guard shouldEnableScroll, let selectedSpotID else { return }
if animated {
withAnimation(Self.scrollAnimation) {
scrollProxy.scrollTo(selectedSpotID, anchor: .center)
}
} else {
scrollProxy.scrollTo(selectedSpotID, anchor: .center)
}
}
}
///
@ -68,22 +129,9 @@ struct ScenicPromotionSpotTabButton: View {
.minimumScaleFactor(0.66)
}
.foregroundStyle(isSelected ? .white : Color(hex: 0x14213D))
.frame(maxWidth: .infinity)
.frame(height: 42)
.padding(.horizontal, 4)
.background(
Group {
if isSelected {
LinearGradient(
colors: [Color(hex: 0x1286FF), Color(hex: 0x006BFF)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
} else {
Color.clear
}
}
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}