You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
13 KiB

import SwiftUI
import CoreData
struct HistoryView: View {
@StateObject private var coreDataManager = CoreDataManager.shared
@State private var searchText = ""
@State private var selectedFilter: HistoryFilter = .all
@State private var showingCreateSheet = false
@State private var showingClearAlert = 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 "全部"
case .barcode:
return "条形码"
case .qrcode:
return "二维码"
case .scanned:
return "扫描获得"
case .created:
return "手动创建"
case .favorites:
return "收藏"
}
}
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] {
let allItems = coreDataManager.fetchHistoryItems()
let searchResults = allItems.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 {
NavigationView {
VStack(spacing: 0) {
//
searchBar
//
filterBar
//
if filteredItems.isEmpty {
emptyStateView
} else {
historyList
}
}
.navigationTitle("历史记录")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
showingClearAlert = true
}) {
Image(systemName: "trash")
.foregroundColor(.red)
}
.disabled(coreDataManager.fetchHistoryItems().isEmpty)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showingCreateSheet = true
}) {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showingCreateSheet) {
CreateCodeView()
}
.alert("清空历史记录", isPresented: $showingClearAlert) {
Button("取消", role: .cancel) { }
Button("清空", role: .destructive) {
clearHistory()
}
} message: {
Text("确定要清空所有历史记录吗?此操作不可撤销。")
}
}
}
// MARK: -
private func clearHistory() {
coreDataManager.clearAllHistory()
}
// MARK: -
private func toggleFavorite(_ item: HistoryItem) {
item.isFavorite.toggle()
coreDataManager.save()
}
// MARK: -
private func deleteHistoryItem(_ item: HistoryItem) {
coreDataManager.deleteHistoryItem(item)
}
// MARK: -
private var searchBar: some View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("搜索历史记录...", 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,
action: {
selectedFilter = filter
}
)
}
}
.padding(.horizontal)
}
.padding(.vertical, 8)
.background(Color(.systemBackground))
}
// MARK: -
private var historyList: some View {
List {
ForEach(filteredItems) { item in
HistoryItemRow(
item: item,
onToggleFavorite: {
toggleFavorite(item)
},
onDelete: {
deleteHistoryItem(item)
}
)
}
}
.listStyle(PlainListStyle())
}
// MARK: -
private var emptyStateView: some View {
VStack(spacing: 20) {
Image(systemName: "clock.arrow.circlepath")
.font(.system(size: 60))
.foregroundColor(.gray)
Text("暂无历史记录")
.font(.title2)
.fontWeight(.medium)
.foregroundColor(.gray)
Text("扫描二维码或手动创建来开始记录")
.font(.body)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
Button(action: {
showingCreateSheet = true
}) {
HStack {
Image(systemName: "plus.circle.fill")
Text("创建第一个记录")
}
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
#Preview {
HistoryView()
}
// MARK: -
struct FilterChip: View {
let filter: HistoryView.HistoryFilter
let isSelected: 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(isSelected ? Color.blue : Color(.systemGray5))
.foregroundColor(isSelected ? .white : .primary)
.cornerRadius(20)
}
.buttonStyle(PlainButtonStyle())
}
}
// MARK: -
struct HistoryItemRow: View {
let item: HistoryItem
let onToggleFavorite: () -> Void
let onDelete: () -> Void
var body: some View {
HStack(spacing: 12) {
//
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(2)
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("删除", role: .destructive) {
onDelete()
}
}
}
private func formatDate(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter.string(from: date)
}
}