Add History button to ContentView for accessing past scans; enhance UI with improved layout and styling for better user experience.
parent
89fb4a2daa
commit
dc8006d1bb
@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
import CoreData
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
class CoreDataManager: ObservableObject {
|
||||
static let shared = CoreDataManager()
|
||||
|
||||
let container: NSPersistentContainer
|
||||
|
||||
init() {
|
||||
container = NSPersistentContainer(name: "MyQrCode")
|
||||
|
||||
container.loadPersistentStores { description, error in
|
||||
if let error = error {
|
||||
print("Core Data 加载失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// 启用自动合并更改
|
||||
container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
}
|
||||
|
||||
// 保存上下文
|
||||
func save() {
|
||||
let context = container.viewContext
|
||||
|
||||
if context.hasChanges {
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
print("保存失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取历史记录
|
||||
func fetchHistoryItems() -> [HistoryItem] {
|
||||
let request: NSFetchRequest<HistoryItem> = HistoryItem.fetchRequest()
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \HistoryItem.createdAt, ascending: false)]
|
||||
|
||||
do {
|
||||
return try container.viewContext.fetch(request)
|
||||
} catch {
|
||||
print("获取历史记录失败: \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 添加历史记录
|
||||
func addHistoryItem(_ item: HistoryItem) {
|
||||
container.viewContext.insert(item)
|
||||
save()
|
||||
}
|
||||
|
||||
// 删除历史记录
|
||||
func deleteHistoryItem(_ item: HistoryItem) {
|
||||
container.viewContext.delete(item)
|
||||
save()
|
||||
}
|
||||
|
||||
// 清空所有历史记录
|
||||
func clearAllHistory() {
|
||||
let request: NSFetchRequest<NSFetchRequestResult> = HistoryItem.fetchRequest()
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)
|
||||
|
||||
do {
|
||||
try container.viewContext.execute(deleteRequest)
|
||||
save()
|
||||
} catch {
|
||||
print("清空历史记录失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索历史记录
|
||||
func searchHistoryItems(query: String) -> [HistoryItem] {
|
||||
let request: NSFetchRequest<HistoryItem> = HistoryItem.fetchRequest()
|
||||
|
||||
if !query.isEmpty {
|
||||
let contentPredicate = NSPredicate(format: "content CONTAINS[cd] %@", query)
|
||||
let barcodeTypePredicate = NSPredicate(format: "barcodeType CONTAINS[cd] %@", query)
|
||||
let qrCodeTypePredicate = NSPredicate(format: "qrCodeType CONTAINS[cd] %@", query)
|
||||
|
||||
let compoundPredicate = NSCompoundPredicate(
|
||||
orPredicateWithSubpredicates: [
|
||||
contentPredicate,
|
||||
barcodeTypePredicate,
|
||||
qrCodeTypePredicate
|
||||
]
|
||||
)
|
||||
|
||||
request.predicate = compoundPredicate
|
||||
}
|
||||
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \HistoryItem.createdAt, ascending: false)]
|
||||
|
||||
do {
|
||||
return try container.viewContext.fetch(request)
|
||||
} catch {
|
||||
print("搜索历史记录失败: \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 按类型过滤
|
||||
func filterByType(_ type: DataType) -> [HistoryItem] {
|
||||
let request: NSFetchRequest<HistoryItem> = HistoryItem.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "dataType == %@", type.rawValue)
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \HistoryItem.createdAt, ascending: false)]
|
||||
|
||||
do {
|
||||
return try container.viewContext.fetch(request)
|
||||
} catch {
|
||||
print("按类型过滤失败: \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 按来源过滤
|
||||
func filterBySource(_ source: DataSource) -> [HistoryItem] {
|
||||
let request: NSFetchRequest<HistoryItem> = HistoryItem.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "dataSource == %@", source.rawValue)
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \HistoryItem.createdAt, ascending: false)]
|
||||
|
||||
do {
|
||||
return try container.viewContext.fetch(request)
|
||||
} catch {
|
||||
print("按来源过滤失败: \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 获取收藏项目
|
||||
func getFavoriteItems() -> [HistoryItem] {
|
||||
let request: NSFetchRequest<HistoryItem> = HistoryItem.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "isFavorite == YES")
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \HistoryItem.createdAt, ascending: false)]
|
||||
|
||||
do {
|
||||
return try container.viewContext.fetch(request)
|
||||
} catch {
|
||||
print("获取收藏项目失败: \(error.localizedDescription)")
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - 条形码类型枚举
|
||||
public enum BarcodeType: String, CaseIterable {
|
||||
case ean13 = "EAN-13"
|
||||
case ean8 = "EAN-8"
|
||||
case upce = "UPC-E"
|
||||
case code39 = "Code 39"
|
||||
case code128 = "Code 128"
|
||||
case pdf417 = "PDF417"
|
||||
|
||||
var displayName: String {
|
||||
return self.rawValue
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .ean13, .ean8, .upce:
|
||||
return "barcode"
|
||||
case .code39, .code128:
|
||||
return "barcode.viewfinder"
|
||||
case .pdf417:
|
||||
return "qrcode.viewfinder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 二维码类型枚举
|
||||
public enum QRCodeType: String, CaseIterable {
|
||||
case wifi = "WiFi"
|
||||
case mail = "Email"
|
||||
case url = "URL"
|
||||
case phone = "Phone"
|
||||
case sms = "SMS"
|
||||
case vcard = "vCard"
|
||||
case mecard = "MeCard"
|
||||
case text = "Text"
|
||||
case location = "Location"
|
||||
case calendar = "Calendar"
|
||||
case instagram = "Instagram"
|
||||
case facebook = "Facebook"
|
||||
case spotify = "Spotify"
|
||||
case twitter = "Twitter"
|
||||
case whatsapp = "WhatsApp"
|
||||
case viber = "Viber"
|
||||
case snapchat = "Snapchat"
|
||||
case tiktok = "TikTok"
|
||||
|
||||
var displayName: String {
|
||||
return self.rawValue
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .wifi:
|
||||
return "wifi"
|
||||
case .mail:
|
||||
return "envelope"
|
||||
case .url:
|
||||
return "link"
|
||||
case .phone:
|
||||
return "phone"
|
||||
case .sms:
|
||||
return "message"
|
||||
case .vcard, .mecard:
|
||||
return "person.crop.rectangle"
|
||||
case .text:
|
||||
return "text.quote"
|
||||
case .location:
|
||||
return "location"
|
||||
case .calendar:
|
||||
return "calendar"
|
||||
case .instagram:
|
||||
return "camera"
|
||||
case .facebook:
|
||||
return "person.2"
|
||||
case .spotify:
|
||||
return "music.note"
|
||||
case .twitter:
|
||||
return "bird"
|
||||
case .whatsapp:
|
||||
return "message.circle"
|
||||
case .viber:
|
||||
return "bubble.left.and.bubble.right"
|
||||
case .snapchat:
|
||||
return "camera.viewfinder"
|
||||
case .tiktok:
|
||||
return "music.mic"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 数据来源枚举
|
||||
public enum DataSource: String, CaseIterable {
|
||||
case scanned = "scanned"
|
||||
case created = "created"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .scanned:
|
||||
return "扫描获得"
|
||||
case .created:
|
||||
return "手动创建"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .scanned:
|
||||
return "camera.viewfinder"
|
||||
case .created:
|
||||
return "plus.circle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 数据类型枚举
|
||||
public enum DataType: String, CaseIterable {
|
||||
case barcode = "barcode"
|
||||
case qrcode = "qrcode"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .barcode:
|
||||
return "条形码"
|
||||
case .qrcode:
|
||||
return "二维码"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .barcode:
|
||||
return "barcode"
|
||||
case .qrcode:
|
||||
return "qrcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 二维码解析数据
|
||||
@objc(ParsedQRData)
|
||||
public class ParsedQRData: NSObject, NSSecureCoding {
|
||||
public static var supportsSecureCoding: Bool = true
|
||||
|
||||
public let type: QRCodeType
|
||||
public let title: String
|
||||
public let subtitle: String?
|
||||
public let icon: String
|
||||
|
||||
public init(type: QRCodeType, title: String, subtitle: String? = nil, icon: String? = nil) {
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.icon = icon ?? type.icon
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
let typeString = coder.decodeObject(of: NSString.self, forKey: "type") as String? ?? ""
|
||||
self.type = QRCodeType(rawValue: typeString) ?? .text
|
||||
self.title = coder.decodeObject(of: NSString.self, forKey: "title") as String? ?? ""
|
||||
self.subtitle = coder.decodeObject(of: NSString.self, forKey: "subtitle") as String?
|
||||
self.icon = coder.decodeObject(of: NSString.self, forKey: "icon") as String? ?? self.type.icon
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(type.rawValue, forKey: "type")
|
||||
coder.encode(title, forKey: "title")
|
||||
coder.encode(subtitle, forKey: "subtitle")
|
||||
coder.encode(icon, forKey: "icon")
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23A5297f" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="HistoryItem" representedClassName="HistoryItem" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="barcodeType" optional="YES" attributeType="String"/>
|
||||
<attribute name="content" optional="YES" attributeType="String"/>
|
||||
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="dataSource" optional="YES" attributeType="String"/>
|
||||
<attribute name="dataType" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="isFavorite" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="parsedData" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="ParsedQRData"/>
|
||||
<attribute name="qrCodeType" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
</model>
|
@ -0,0 +1,160 @@
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
struct CreateCodeView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@StateObject private var coreDataManager = CoreDataManager.shared
|
||||
|
||||
@State private var selectedDataType: DataType = .qrcode
|
||||
@State private var selectedBarcodeType: BarcodeType = .ean13
|
||||
@State private var selectedQRCodeType: QRCodeType = .text
|
||||
@State private var content = ""
|
||||
@State private var showingAlert = false
|
||||
@State private var alertMessage = ""
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
// 数据类型选择
|
||||
Section("数据类型") {
|
||||
Picker("数据类型", selection: $selectedDataType) {
|
||||
ForEach(DataType.allCases, id: \.self) { type in
|
||||
HStack {
|
||||
Image(systemName: type.icon)
|
||||
Text(type.displayName)
|
||||
}
|
||||
.tag(type)
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
}
|
||||
|
||||
// 具体类型选择
|
||||
if selectedDataType == .barcode {
|
||||
Section("条形码类型") {
|
||||
Picker("条形码类型", selection: $selectedBarcodeType) {
|
||||
ForEach(BarcodeType.allCases, id: \.self) { type in
|
||||
HStack {
|
||||
Image(systemName: type.icon)
|
||||
Text(type.displayName)
|
||||
}
|
||||
.tag(type)
|
||||
}
|
||||
}
|
||||
.pickerStyle(WheelPickerStyle())
|
||||
}
|
||||
} else {
|
||||
Section("二维码类型") {
|
||||
Picker("二维码类型", selection: $selectedQRCodeType) {
|
||||
ForEach(QRCodeType.allCases, id: \.self) { type in
|
||||
HStack {
|
||||
Image(systemName: type.icon)
|
||||
Text(type.displayName)
|
||||
}
|
||||
.tag(type)
|
||||
}
|
||||
}
|
||||
.pickerStyle(WheelPickerStyle())
|
||||
}
|
||||
}
|
||||
|
||||
// 内容输入
|
||||
Section("内容") {
|
||||
TextField("请输入内容", text: $content)
|
||||
.frame(minHeight: 80)
|
||||
}
|
||||
|
||||
// 预览
|
||||
if !content.isEmpty {
|
||||
Section("预览") {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Image(systemName: selectedDataType.icon)
|
||||
Text(selectedDataType.displayName)
|
||||
Spacer()
|
||||
if selectedDataType == .barcode {
|
||||
Text(selectedBarcodeType.displayName)
|
||||
.font(.caption)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.green.opacity(0.1))
|
||||
.foregroundColor(.green)
|
||||
.cornerRadius(8)
|
||||
} else {
|
||||
Text(selectedQRCodeType.displayName)
|
||||
.font(.caption)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.orange.opacity(0.1))
|
||||
.foregroundColor(.orange)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
Text(content)
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("创建\(selectedDataType.displayName)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("取消") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("创建") {
|
||||
createCode()
|
||||
}
|
||||
.disabled(content.isEmpty)
|
||||
}
|
||||
}
|
||||
.alert("提示", isPresented: $showingAlert) {
|
||||
Button("确定") { }
|
||||
} message: {
|
||||
Text(alertMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createCode() {
|
||||
guard !content.isEmpty else { return }
|
||||
|
||||
let context = coreDataManager.container.viewContext
|
||||
let historyItem = HistoryItem(context: context)
|
||||
|
||||
historyItem.id = UUID()
|
||||
historyItem.content = content
|
||||
historyItem.dataType = selectedDataType.rawValue
|
||||
historyItem.dataSource = DataSource.created.rawValue
|
||||
historyItem.createdAt = Date()
|
||||
historyItem.isFavorite = false
|
||||
|
||||
if selectedDataType == .barcode {
|
||||
historyItem.barcodeType = selectedBarcodeType.rawValue
|
||||
} else {
|
||||
historyItem.qrCodeType = selectedQRCodeType.rawValue
|
||||
}
|
||||
|
||||
coreDataManager.addHistoryItem(historyItem)
|
||||
alertMessage = "\(selectedDataType.displayName)创建成功!"
|
||||
showingAlert = true
|
||||
|
||||
// 延迟关闭
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CreateCodeView()
|
||||
}
|
@ -0,0 +1,387 @@
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue