import SwiftUI import CoreData import Combine struct HistoryView: View { @EnvironmentObject var coreDataManager: CoreDataManager @EnvironmentObject var languageManager: LanguageManager @State private var searchText = "" @State private var selectedFilter: HistoryFilter = .all @State private var itemToDelete: HistoryItem? @State private var showingDeleteAlert = false @State private var showingClearConfirmSheet = false @State private var allHistoryItems: [HistoryItem] = [] @State private var currentPage = 0 @State private var isLoadingMore = false @State private var hasMoreData = true @State private var isLoading = false @State private var refreshTrigger = false @State private var isBatchDeleteMode = false @State private var selectedItemsForDelete: Set = [] @State private var itemToEdit: HistoryItem? @State private var showingEditView = false enum HistoryFilter: String, CaseIterable { case all = "all" case barcode = "barcode" case qrcode = "qrcode" case scanned = "scanned" case created = "created" case favorites = "favorites" var displayName: String { switch self { case .all: return "all".localized case .barcode: return "barcode".localized case .qrcode: return "qrcode".localized case .scanned: return "scanned".localized case .created: return "created".localized case .favorites: return "favorites".localized } } var icon: String { switch self { case .all: return "list.bullet" case .barcode: return "barcode" case .qrcode: return "qrcode" case .scanned: return "camera.viewfinder" case .created: return "plus.circle" case .favorites: return "heart.fill" } } } var filteredItems: [HistoryItem] { // 使用 refreshTrigger 强制触发计算属性更新 let _ = refreshTrigger let searchResults = allHistoryItems.filter { item in if !searchText.isEmpty { let content = item.content ?? "" let barcodeType = item.barcodeType ?? "" let qrCodeType = item.qrCodeType ?? "" return content.localizedCaseInsensitiveContains(searchText) || barcodeType.localizedCaseInsensitiveContains(searchText) || qrCodeType.localizedCaseInsensitiveContains(searchText) } return true } switch selectedFilter { case .all: return searchResults case .barcode: return searchResults.filter { $0.dataType == DataType.barcode.rawValue } case .qrcode: return searchResults.filter { $0.dataType == DataType.qrcode.rawValue } case .scanned: return searchResults.filter { $0.dataSource == DataSource.scanned.rawValue } case .created: return searchResults.filter { $0.dataSource == DataSource.created.rawValue } case .favorites: return searchResults.filter { $0.isFavorite } } } var body: some View { VStack(spacing: 0) { // 搜索栏 searchBar // 过滤器 filterBar // 内容列表 if filteredItems.isEmpty { emptyStateView } else { historyList } } .navigationTitle("history_records".localized) .id(languageManager.refreshTrigger) .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { HStack(spacing: 16) { if isBatchDeleteMode { // 批量删除模式下的按钮 Button(action: { // 全选/取消全选 if selectedItemsForDelete.count == filteredItems.count { selectedItemsForDelete.removeAll() } else { selectedItemsForDelete = Set(filteredItems.compactMap { $0.id }) } }) { Image(systemName: selectedItemsForDelete.count == filteredItems.count ? "checkmark.rectangle.fill" : "rectangle.on.rectangle") .foregroundColor(.blue) } Button(action: { if !selectedItemsForDelete.isEmpty { deleteSelectedItems() } }) { Image(systemName: "trash.fill") .foregroundColor(.red) } .disabled(selectedItemsForDelete.isEmpty) // 返回正常状态按钮 Button(action: { exitBatchDeleteMode() }) { Image(systemName: "xmark.circle") .foregroundColor(.gray) } } else { // 正常模式下的按钮 // 只有当有记录时才显示删除按钮 if !allHistoryItems.isEmpty { Button(action: { enterBatchDeleteMode() }) { Image(systemName: "trash") .foregroundColor(.red) } } NavigationLink(destination: CodeTypeSelectionView()) { Image(systemName: "plus") } } } } } .sheet(isPresented: $showingClearConfirmSheet) { ClearHistoryConfirmView( isPresented: $showingClearConfirmSheet, onConfirm: clearHistory ) } .alert("delete_confirmation".localized, isPresented: $showingDeleteAlert) { Button("cancel".localized, role: .cancel) { } Button("delete".localized, role: .destructive) { if let item = itemToDelete { deleteHistoryItem(item) itemToDelete = nil } } } message: { if let item = itemToDelete { Text(String(format: "confirm_delete_record".localized, item.content ?? "")) .id(languageManager.refreshTrigger) } } .onAppear { loadHistoryItems() } .onReceive(coreDataManager.objectWillChange) { _ in // 当Core Data数据发生变化时,重新加载历史记录 loadHistoryItems() } .background(editNavigationLink) } // MARK: - 加载历史记录 private func loadHistoryItems() { isLoading = true currentPage = 0 hasMoreData = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { let items = coreDataManager.fetchHistoryItems(page: currentPage) allHistoryItems = items // 检查是否还有更多数据 let nextPageItems = coreDataManager.fetchHistoryItems(page: currentPage + 1) hasMoreData = !nextPageItems.isEmpty isLoading = false } } private func loadMoreHistoryItems() { guard !isLoadingMore && hasMoreData else { return } isLoadingMore = true currentPage += 1 DispatchQueue.global(qos: .userInitiated).async { let newItems = coreDataManager.fetchHistoryItems(page: currentPage) DispatchQueue.main.async { if newItems.isEmpty { self.hasMoreData = false } else { self.allHistoryItems.append(contentsOf: newItems) // 检查是否还有更多数据 let nextPageItems = coreDataManager.fetchHistoryItems(page: self.currentPage + 1) self.hasMoreData = !nextPageItems.isEmpty } self.isLoadingMore = false } } } // MARK: - 过滤器操作 private func filterAction(for filter: HistoryFilter) { // 直接切换过滤器,无任何延迟 selectedFilter = filter } // MARK: - 清空历史记录 private func clearHistory() { coreDataManager.clearAllHistory() allHistoryItems.removeAll() refreshTrigger.toggle() } // MARK: - 切换收藏状态 private func toggleFavorite(_ item: HistoryItem) { // 先保存到 Core Data item.isFavorite.toggle() coreDataManager.save() // 更新本地缓存,避免重新加载数据 if let index = allHistoryItems.firstIndex(where: { $0.id == item.id }) { allHistoryItems[index].isFavorite = item.isFavorite } // 强制触发视图刷新 refreshTrigger.toggle() } // MARK: - 删除历史记录 private func deleteHistoryItem(_ item: HistoryItem) { coreDataManager.deleteHistoryItem(item) // 从本地缓存中移除 allHistoryItems.removeAll { $0.id == item.id } refreshTrigger.toggle() } // MARK: - 显示删除确认 private func showDeleteConfirmation(for item: HistoryItem) { itemToDelete = item showingDeleteAlert = true } // MARK: - 显示编辑界面 private func showEditView(for item: HistoryItem) { itemToEdit = item showingEditView = true } // MARK: - 获取二维码类型 private func getQRCodeType(from item: HistoryItem) -> QRCodeType { if let qrCodeTypeString = item.qrCodeType, let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) { return qrCodeType } return .text // 默认类型 } // MARK: - 获取样式数据 private func getStyleData(from item: HistoryItem) -> QRCodeStyleData? { // 从历史记录项中提取样式数据 guard let jsonString = item.qrCodeStyleData, let jsonData = jsonString.data(using: .utf8) else { return nil } do { let styleData = try JSONDecoder().decode(QRCodeStyleData.self, from: jsonData) return styleData } catch { print("❌ 样式数据JSON解码失败:\(error)") return nil } } // MARK: - 编辑导航链接 private var editNavigationLink: some View { NavigationLink( destination: Group { if let itemToEdit = itemToEdit { QRCodeStyleView( qrCodeContent: itemToEdit.content ?? "", qrCodeType: getQRCodeType(from: itemToEdit), existingStyleData: getStyleData(from: itemToEdit), historyItemId: itemToEdit.id?.uuidString ) } }, isActive: $showingEditView ) { EmptyView() } } // MARK: - 批量删除相关方法 private func enterBatchDeleteMode() { isBatchDeleteMode = true // 默认全选当前过滤后的项目 selectedItemsForDelete = Set(filteredItems.compactMap { $0.id }) } private func exitBatchDeleteMode() { isBatchDeleteMode = false selectedItemsForDelete.removeAll() } private func deleteSelectedItems() { // 获取选中的项目 let itemsToDelete = allHistoryItems.filter { item in guard let id = item.id else { return false } return selectedItemsForDelete.contains(id) } // 批量删除 for item in itemsToDelete { coreDataManager.deleteHistoryItem(item) } // 从本地缓存中移除 allHistoryItems.removeAll { item in guard let id = item.id else { return false } return selectedItemsForDelete.contains(id) } // 清空选择状态并退出批量删除模式 selectedItemsForDelete.removeAll() isBatchDeleteMode = false // 强制刷新数据 DispatchQueue.main.async { allHistoryItems = coreDataManager.fetchHistoryItems() refreshTrigger.toggle() } } // MARK: - 搜索栏 private var searchBar: some View { HStack { Image(systemName: "magnifyingglass") .foregroundColor(.gray) TextField("search_history_records".localized, text: $searchText) .textFieldStyle(RoundedBorderTextFieldStyle()) } .padding(.horizontal) .padding(.vertical, 8) .background(Color(.systemBackground)) } // MARK: - 过滤器栏 private var filterBar: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 12) { ForEach(HistoryFilter.allCases, id: \.self) { filter in FilterChip( filter: filter, isSelected: selectedFilter == filter, isLoading: false, action: { filterAction(for: filter) } ) } } .padding(.horizontal) } .padding(.vertical, 8) .background(Color(.systemBackground)) } // MARK: - 历史记录列表 private var historyList: some View { List { if isLoading { HStack { Spacer() VStack(spacing: 16) { ProgressView() .scaleEffect(1.2) Text("loading".localized) .font(.caption) .foregroundColor(.secondary) .id(languageManager.refreshTrigger) } .padding(.vertical, 40) Spacer() } } else { ForEach(filteredItems) { item in HistoryItemRow( item: item, onToggleFavorite: { toggleFavorite(item) }, onDelete: { showDeleteConfirmation(for: item) }, onEdit: { showEditView(for: item) }, isBatchDeleteMode: isBatchDeleteMode, isSelected: selectedItemsForDelete.contains(item.id ?? UUID()), onToggleSelection: { if let id = item.id { if selectedItemsForDelete.contains(id) { selectedItemsForDelete.remove(id) } else { selectedItemsForDelete.insert(id) } } } ) } // 加载更多按钮 if hasMoreData && !isLoadingMore { Button(action: loadMoreHistoryItems) { HStack { Image(systemName: "arrow.down.circle") Text("load_more".localized) } .foregroundColor(.blue) .padding() } } // 加载更多指示器 if isLoadingMore { HStack { Spacer() ProgressView() .scaleEffect(0.8) Text("loading_more".localized) .font(.caption) .foregroundColor(.secondary) Spacer() } .padding() } } } .listStyle(PlainListStyle()) } // MARK: - 空状态视图 private var emptyStateView: some View { VStack(spacing: 20) { Image(systemName: "clock.arrow.circlepath") .font(.system(size: 60)) .foregroundColor(.gray) Text("no_history_records".localized) .font(.title2) .fontWeight(.medium) .foregroundColor(.gray) .id(languageManager.refreshTrigger) Text("scan_or_create_to_start".localized) .font(.body) .foregroundColor(.gray) .multilineTextAlignment(.center) .id(languageManager.refreshTrigger) NavigationLink(destination: CodeTypeSelectionView()) { HStack { Image(systemName: "plus.circle.fill") Text("create_first_record".localized) .id(languageManager.refreshTrigger) } .font(.headline) .foregroundColor(.white) .padding() .background(Color.blue) .cornerRadius(10) } } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity) } } #Preview { NavigationView { HistoryView() .environmentObject(LanguageManager.shared) } } // MARK: - 过滤器芯片 struct FilterChip: View { let filter: HistoryView.HistoryFilter let isSelected: Bool let isLoading: Bool let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: 6) { Image(systemName: filter.icon) .font(.system(size: 14)) Text(filter.displayName) .font(.system(size: 14, weight: .medium)) } .padding(.horizontal, 12) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 20) .fill(isSelected ? Color.blue : Color(.systemGray5)) ) .foregroundColor(isSelected ? .white : .primary) .scaleEffect(isSelected ? 1.05 : 1.0) } .buttonStyle(OptimizedFilterChipButtonStyle(isSelected: isSelected)) .animation(.easeInOut(duration: 0.2), value: isSelected) } } // MARK: - 优化的过滤器芯片按钮样式 struct OptimizedFilterChipButtonStyle: ButtonStyle { let isSelected: Bool func makeBody(configuration: Configuration) -> some View { configuration.label .scaleEffect(configuration.isPressed ? 0.95 : 1.0) .opacity(configuration.isPressed ? 0.9 : 1.0) .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) } } // MARK: - 过滤器芯片按钮样式(保留以防向后兼容) struct FilterChipButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .scaleEffect(configuration.isPressed ? 0.95 : 1.0) .opacity(configuration.isPressed ? 0.8 : 1.0) .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) } } // MARK: - 历史记录项行 struct HistoryItemRow: View { let item: HistoryItem let onToggleFavorite: () -> Void let onDelete: () -> Void let onEdit: (() -> Void)? let isBatchDeleteMode: Bool let isSelected: Bool let onToggleSelection: () -> Void var body: some View { HStack(spacing: 12) { // 批量删除模式下的选择框 if isBatchDeleteMode { Button(action: onToggleSelection) { Image(systemName: isSelected ? "checkmark.square.fill" : "square") .font(.system(size: 20)) .foregroundColor(isSelected ? .blue : .gray) } .buttonStyle(PlainButtonStyle()) } // 类型图标 VStack { if let dataTypeString = item.dataType, let dataType = DataType(rawValue: dataTypeString) { Image(systemName: dataType.icon) .font(.system(size: 24)) .foregroundColor(.blue) } if let dataSourceString = item.dataSource, let dataSource = DataSource(rawValue: dataSourceString) { Image(systemName: dataSource.icon) .font(.system(size: 12)) .foregroundColor(.gray) } } .frame(width: 40) // 内容信息 VStack(alignment: .leading, spacing: 4) { HStack { Text(item.content ?? "") .font(.headline) .lineLimit(1) Spacer() Button(action: onToggleFavorite) { Image(systemName: item.isFavorite ? "heart.fill" : "heart") .foregroundColor(item.isFavorite ? .red : .gray) } .buttonStyle(PlainButtonStyle()) } HStack { // 类型标签 if let dataTypeString = item.dataType, let dataType = DataType(rawValue: dataTypeString) { HStack(spacing: 4) { Image(systemName: dataType.icon) .font(.system(size: 12)) Text(dataType.displayName) .font(.caption) } .padding(.horizontal, 8) .padding(.vertical, 2) .background(Color.blue.opacity(0.1)) .foregroundColor(.blue) .cornerRadius(8) } // 具体类型标签 if let barcodeTypeString = item.barcodeType, let barcodeType = BarcodeType(rawValue: barcodeTypeString) { HStack(spacing: 4) { Image(systemName: barcodeType.icon) .font(.system(size: 12)) Text(barcodeType.displayName) .font(.caption) } .padding(.horizontal, 8) .padding(.vertical, 2) .background(Color.green.opacity(0.1)) .foregroundColor(.green) .cornerRadius(8) } if let qrCodeTypeString = item.qrCodeType, let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) { HStack(spacing: 4) { Image(systemName: qrCodeType.icon) .font(.system(size: 12)) Text(qrCodeType.displayName) .font(.caption) } .padding(.horizontal, 8) .padding(.vertical, 2) .background(Color.orange.opacity(0.1)) .foregroundColor(.orange) .cornerRadius(8) } Spacer() // 时间 if let createdAt = item.createdAt { Text(formatDate(createdAt)) .font(.caption) .foregroundColor(.gray) } } } Spacer() } .padding(.vertical, 8) .swipeActions(edge: .trailing, allowsFullSwipe: false) { // 分享按钮 Button(action: { shareItem(item) }) { Image(systemName: "square.and.arrow.up") } .tint(.gray) // 为创建类型的二维码条目添加编辑按钮 if item.dataType == DataType.qrcode.rawValue && item.dataSource == DataSource.created.rawValue, let onEdit = onEdit { Button(action: onEdit) { Image(systemName: "pencil") } .tint(.blue) } Button(action: onDelete) { Image(systemName: "trash") } .tint(.red) } .background( // 根据数据类型和数据源添加导航链接 Group { if item.dataType == DataType.qrcode.rawValue { if item.dataSource == DataSource.created.rawValue { // 创建类型的二维码条目,导航到保存界面 NavigationLink( destination: QRCodeSavedView( qrCodeImage: generateQRCodeImage(from: item), qrCodeContent: item.content ?? "", qrCodeType: getQRCodeType(from: item), styleData: getStyleData(from: item), historyItem: item ), label: { EmptyView() } ) } else { // 扫描类型的二维码条目,导航到详情界面 NavigationLink( destination: QRCodeDetailView(historyItem: item), label: { EmptyView() } ) } } else if item.dataType == DataType.barcode.rawValue { NavigationLink( destination: BarcodeDetailView(historyItem: item), label: { EmptyView() } ) } } ) } private func formatDate(_ date: Date) -> String { let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .short return formatter.string(from: date) } // MARK: - QRCodeSavedView 辅助方法 private func generateQRCodeImage(from item: HistoryItem) -> UIImage { // 从历史记录项生成二维码图片 let content = item.content ?? "" // 获取样式数据 let styleData = getStyleData(from: item) // 使用高质量二维码生成器 return QRCodeGenerator.generateHighQualityQRCode( content: content, styleData: styleData ) } // MARK: - 分享功能 private func shareItem(_ item: HistoryItem) { let content = item.content ?? "" let activityVC = UIActivityViewController( activityItems: [content], applicationActivities: nil ) // 在iPad上需要设置popoverPresentationController if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first { if let popover = activityVC.popoverPresentationController { popover.sourceView = window popover.sourceRect = CGRect(x: window.bounds.midX, y: window.bounds.midY, width: 0, height: 0) popover.permittedArrowDirections = [] } } // 记录Facebook事件 - 分享功能 let contentType = item.dataType == DataType.qrcode.rawValue ? "qr_code" : "barcode" FacebookEventManager.shared.logShare(contentType: contentType) // 获取当前视图控制器并显示分享界面 if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let window = windowScene.windows.first, let rootViewController = window.rootViewController { rootViewController.present(activityVC, animated: true) } } private func getQRCodeType(from item: HistoryItem) -> QRCodeType { if let qrCodeTypeString = item.qrCodeType, let qrCodeType = QRCodeType(rawValue: qrCodeTypeString) { return qrCodeType } return .text // 默认类型 } private func getStyleData(from item: HistoryItem) -> QRCodeStyleData? { // 从历史记录项中提取样式数据 guard let jsonString = item.qrCodeStyleData, let jsonData = jsonString.data(using: .utf8) else { return nil } do { let styleData = try JSONDecoder().decode(QRCodeStyleData.self, from: jsonData) return styleData } catch { print("❌ 样式数据JSON解码失败:\(error)") return nil } } } // MARK: - 清空历史记录确认视图 struct ClearHistoryConfirmView: View { @EnvironmentObject var languageManager: LanguageManager @Binding var isPresented: Bool let onConfirm: () -> Void var body: some View { NavigationView { VStack(spacing: 20) { // 警告图标 Image(systemName: "exclamationmark.triangle.fill") .font(.system(size: 50)) .foregroundColor(.red) // 标题 Text("clear_history".localized) .font(.title2) .fontWeight(.bold) .id(languageManager.refreshTrigger) // 简单说明 Text("clear_history_warning".localized) .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.center) .id(languageManager.refreshTrigger) Spacer() // 按钮区域 VStack(spacing: 12) { // 确认删除按钮 Button(action: { onConfirm() isPresented = false }) { HStack { Image(systemName: "trash.fill") Text("confirm_delete".localized) .id(languageManager.refreshTrigger) } .frame(maxWidth: .infinity) .padding() .background(Color.red) .foregroundColor(.white) .cornerRadius(10) } // 取消按钮 Button(action: { isPresented = false }) { Text("cancel".localized) .frame(maxWidth: .infinity) .padding() .background(Color(.systemGray5)) .foregroundColor(.primary) .cornerRadius(10) } } } .padding(20) .navigationTitle("confirm_delete".localized) .id(languageManager.refreshTrigger) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("close".localized) { isPresented = false } .id(languageManager.refreshTrigger) } } } } }