// // InviteViews.swift // suixinkan // // Created by Codex on 2026/6/24. // import SwiftUI /// 摄影师邀请页面,展示邀请二维码、邀请码、规则和邀请记录入口。 struct PhotographerInviteView: View { @Environment(InviteAPI.self) private var inviteAPI @Environment(ToastCenter.self) private var toastCenter @Environment(\.globalLoading) private var globalLoading @State private var viewModel = PhotographerInviteViewModel() var body: some View { ScrollView { VStack(spacing: AppMetrics.Spacing.medium) { inviteCard ruleCard benefitCard NavigationLink(value: HomeRoute.inviteRecord) { Label("邀请记录", systemImage: "calendar.badge.clock") .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .frame(height: 48) .background(AppDesign.primary, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.button)) } .buttonStyle(.plain) } .padding(AppMetrics.Spacing.medium) } .background(Color(hex: 0xF5F7FA)) .navigationTitle("摄影师邀请") .navigationBarTitleDisplayMode(.inline) .task { await reload(showLoading: true) } .refreshable { await reload(showLoading: false) } } private var inviteCard: some View { VStack(spacing: AppMetrics.Spacing.medium) { if let qrImage = viewModel.qrImage { Image(uiImage: qrImage) .resizable() .interpolation(.none) .scaledToFit() .frame(width: 240, height: 240) } else { Image(systemName: "qrcode") .font(.system(size: 72)) .foregroundStyle(AppDesign.textSecondary) .frame(width: 240, height: 240) .background(Color(hex: 0xF3F4F6), in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } HStack { Text("邀请码") .foregroundStyle(AppDesign.textSecondary) Text(viewModel.inviteCode.isEmpty ? "--" : viewModel.inviteCode) .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(AppDesign.primary) Spacer() Button("复制") { viewModel.copyInviteCode() toastCenter.show("邀请码已复制") } .font(.system(size: AppMetrics.FontSize.subheadline, weight: .semibold)) } HStack(spacing: AppMetrics.Spacing.small) { Button { viewModel.copyInviteUrl() toastCenter.show("邀请链接已复制") } label: { Label("复制链接", systemImage: "link") .frame(maxWidth: .infinity) .frame(height: 44) } Button { viewModel.copyInviteUrl() toastCenter.show("链接已复制,可粘贴分享") } label: { Label("分享", systemImage: "square.and.arrow.up") .frame(maxWidth: .infinity) .frame(height: 44) } } .font(.system(size: AppMetrics.FontSize.subheadline, weight: .semibold)) .foregroundStyle(.white) .background(AppDesign.primary, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.button)) } .padding(AppMetrics.Spacing.medium) .background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } private var ruleCard: some View { VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) { Text("收益规则") .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) let rules = viewModel.rules.isEmpty ? ["直接邀请将获得订单奖励", "间接邀请将获得额外奖励", "具体规则以后端配置为准"] : viewModel.rules ForEach(Array(rules.enumerated()), id: \.offset) { index, rule in HStack(alignment: .top, spacing: AppMetrics.Spacing.small) { Text("\(index + 1)") .font(.system(size: AppMetrics.FontSize.caption, weight: .bold)) .foregroundStyle(.white) .frame(width: 22, height: 22) .background(AppDesign.primary, in: Circle()) Text(rule) .font(.system(size: AppMetrics.FontSize.subheadline)) .foregroundStyle(AppDesign.textPrimary) } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(AppMetrics.Spacing.medium) .background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } private var benefitCard: some View { LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: AppMetrics.Spacing.small) { ForEach(["免费剪辑修图", "免费云盘相册", "免费线上流量", "景区运营身份"], id: \.self) { title in Text(title) .font(.system(size: AppMetrics.FontSize.subheadline, weight: .medium)) .foregroundStyle(AppDesign.primary) .frame(maxWidth: .infinity) .frame(height: 54) .background(AppDesign.primarySoft, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.input)) } } .padding(AppMetrics.Spacing.medium) .background(.white, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)) } private func reload(showLoading: Bool) async { await globalLoading.withOptionalLoading(showLoading) { await viewModel.reload(api: inviteAPI) } } } /// 邀请记录页面,展示邀请用户和奖励记录。 struct InviteRecordView: View { @Environment(InviteAPI.self) private var inviteAPI @Environment(WalletAPI.self) private var walletAPI @Environment(\.globalLoading) private var globalLoading @State private var viewModel = InviteRecordViewModel() var body: some View { VStack(spacing: 0) { header Picker("记录类型", selection: $viewModel.tab) { ForEach(InviteRecordTab.allCases, id: \.self) { tab in Text(tab.title).tag(tab) } } .pickerStyle(.segmented) .padding(AppMetrics.Spacing.medium) .onChange(of: viewModel.tab) { _, newValue in Task { await viewModel.selectTab(newValue, inviteAPI: inviteAPI, walletAPI: walletAPI) } } recordList NavigationLink(value: HomeRoute.wallet) { Text("去钱包提现") .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .frame(height: 50) .background(AppDesign.primary, in: RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.button)) } .buttonStyle(.plain) .padding(AppMetrics.Spacing.medium) .background(.white) } .background(Color(hex: 0xF5F7FA)) .navigationTitle("邀请记录") .navigationBarTitleDisplayMode(.inline) .task { await reload(refresh: true, showLoading: true) } } private var header: some View { HStack(spacing: 0) { metric("累计奖励金额", viewModel.totalRewardText) Rectangle() .fill(.white.opacity(0.6)) .frame(width: 1, height: 54) metric("可提现金额", viewModel.withdrawableText) } .padding(.vertical, AppMetrics.Spacing.large) .background(AppDesign.primary) } private func metric(_ title: String, _ value: String) -> some View { VStack(spacing: 6) { Text(title) .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(.white.opacity(0.78)) Text(value) .font(.system(size: AppMetrics.FontSize.title2, weight: .semibold)) .foregroundStyle(.white) } .frame(maxWidth: .infinity) } private var recordList: some View { ScrollView { LazyVStack(spacing: 0) { if viewModel.displayRows.isEmpty && !viewModel.loading { ContentUnavailableView("暂无记录", systemImage: "tray") .frame(minHeight: 280) } ForEach(viewModel.displayRows) { row in InviteRecordRow(row: row, tab: viewModel.tab) } if viewModel.hasMore { ProgressView() .padding(AppMetrics.Spacing.medium) .onAppear { Task { await reload(refresh: false, showLoading: false) } } } } .padding(.horizontal, AppMetrics.Spacing.medium) } .refreshable { await reload(refresh: true, showLoading: false) } } private func reload(refresh: Bool, showLoading: Bool) async { await globalLoading.withOptionalLoading(showLoading && refresh) { await viewModel.reload(inviteAPI: inviteAPI, walletAPI: walletAPI, refresh: refresh) } } } /// 邀请记录展示行。 private struct InviteRecordRow: View { let row: InviteDisplayRow let tab: InviteRecordTab var body: some View { HStack(spacing: AppMetrics.Spacing.medium) { if tab == .invite { RemoteAvatarImage(urlString: row.avatar, iconSize: 26) .frame(width: 48, height: 48) .clipShape(Circle()) } else { Image(systemName: "yensign.circle.fill") .font(.system(size: 34)) .foregroundStyle(AppDesign.success) .frame(width: 48, height: 48) } VStack(alignment: .leading, spacing: 6) { HStack { Text(row.title) .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(AppDesign.textPrimary) if tab == .invite { Text(row.levelText) .font(.system(size: AppMetrics.FontSize.caption, weight: .semibold)) .foregroundStyle(AppDesign.primary) } } Text(tab == .invite ? row.phone : row.subtitle) .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(AppDesign.textSecondary) Text(row.time) .font(.system(size: AppMetrics.FontSize.caption)) .foregroundStyle(AppDesign.textSecondary) } Spacer() if tab == .reward { Text(row.amount) .font(.system(size: AppMetrics.FontSize.body, weight: .semibold)) .foregroundStyle(AppDesign.success) } } .padding(AppMetrics.Spacing.medium) .background(.white) .overlay(alignment: .bottom) { Rectangle() .fill(Color(hex: 0xE5E7EB)) .frame(height: 1) } } }