|
|
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 = ""
|
|
|
|
|
|
// 条形码验证相关状态
|
|
|
@State private var validationResult: BarcodeValidator.ValidationResult?
|
|
|
@State private var showValidationInfo = false
|
|
|
|
|
|
// 输入焦点,确保进入页面自动弹出键盘
|
|
|
@FocusState private var isContentFieldFocused: Bool
|
|
|
|
|
|
var body: some View {
|
|
|
Form {
|
|
|
// 数据类型选择
|
|
|
Section("数据类型") {
|
|
|
HStack(spacing: 0) {
|
|
|
ForEach(DataType.allCases, id: \.self) { type in
|
|
|
Button(action: {
|
|
|
selectedDataType = type
|
|
|
isContentFieldFocused = true
|
|
|
// 添加触觉反馈
|
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .light)
|
|
|
impactFeedback.impactOccurred()
|
|
|
}) {
|
|
|
HStack(spacing: 6) {
|
|
|
Image(systemName: type.icon)
|
|
|
.font(.system(size: 16, weight: .medium))
|
|
|
.foregroundColor(selectedDataType == type ? .white : .primary)
|
|
|
|
|
|
Text(type.displayName)
|
|
|
.font(.system(size: 15, weight: .medium))
|
|
|
.foregroundColor(selectedDataType == type ? .white : .primary)
|
|
|
}
|
|
|
.frame(maxWidth: .infinity)
|
|
|
.padding(.vertical, 12)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(selectedDataType == type ? Color.blue : Color(.systemGray6))
|
|
|
)
|
|
|
}
|
|
|
.buttonStyle(PlainButtonStyle())
|
|
|
}
|
|
|
}
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(Color(.systemGray6))
|
|
|
)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.stroke(Color(.systemGray4), lineWidth: 0.5)
|
|
|
)
|
|
|
}
|
|
|
|
|
|
// 具体类型选择
|
|
|
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("内容") {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
// 输入框和计数布局
|
|
|
VStack(alignment: .trailing, spacing: 4) {
|
|
|
// 条形码格式提示
|
|
|
if selectedDataType == .barcode {
|
|
|
HStack {
|
|
|
Image(systemName: "info.circle")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.blue)
|
|
|
|
|
|
Text(getBarcodeFormatHint())
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.secondary)
|
|
|
.lineLimit(2)
|
|
|
|
|
|
Spacer()
|
|
|
}
|
|
|
.padding(.horizontal, 4)
|
|
|
.padding(.vertical, 6)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 6)
|
|
|
.fill(Color.blue.opacity(0.1))
|
|
|
)
|
|
|
}
|
|
|
|
|
|
TextField(getPlaceholderText(), text: $content)
|
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
|
.keyboardType(getKeyboardType())
|
|
|
.textInputAutocapitalization(getAutocapitalization())
|
|
|
.disableAutocorrection(true)
|
|
|
.focused($isContentFieldFocused)
|
|
|
.toolbar {
|
|
|
ToolbarItemGroup(placement: .keyboard) {
|
|
|
Spacer()
|
|
|
Button("完成") {
|
|
|
isContentFieldFocused = false
|
|
|
}
|
|
|
.foregroundColor(.blue)
|
|
|
.font(.system(size: 16, weight: .medium))
|
|
|
}
|
|
|
}
|
|
|
.foregroundColor(.black)
|
|
|
.onChange(of: content) { newValue in
|
|
|
// 针对有固定长度与数字要求的类型,进行输入规范化(仅保留数字并按最大长度截断)
|
|
|
if selectedDataType == .barcode, let maxLen = getMaxLength(), requiresDigitsOnly() {
|
|
|
let digits = newValue.filter { $0.isNumber }
|
|
|
let truncated = String(digits.prefix(maxLen))
|
|
|
if truncated != newValue {
|
|
|
content = truncated
|
|
|
// 播放输入提示音
|
|
|
playInputSound()
|
|
|
}
|
|
|
} else if selectedDataType == .barcode {
|
|
|
// 播放输入提示音
|
|
|
playInputSound()
|
|
|
}
|
|
|
validateBarcodeContent(content)
|
|
|
}
|
|
|
|
|
|
// 计数显示在输入框下方,右对齐
|
|
|
if let maxLen = getMaxLength(), selectedDataType == .barcode {
|
|
|
Text("\(normalizedInputCount())/\(maxLen)")
|
|
|
.font(.caption2)
|
|
|
.foregroundColor(.blue)
|
|
|
.fontWeight(.medium)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 输入统计与验证状态
|
|
|
HStack {
|
|
|
if let result = validationResult, result.isValid {
|
|
|
Text("✓ 格式正确")
|
|
|
.font(.caption2)
|
|
|
.foregroundColor(.green)
|
|
|
} else if !content.isEmpty {
|
|
|
Text("⚠ 格式检查中...")
|
|
|
.font(.caption2)
|
|
|
.foregroundColor(.orange)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 条形码验证信息(仅在格式不正确时显示)
|
|
|
if selectedDataType == .barcode && !content.isEmpty && (validationResult == nil || !validationResult!.isValid) {
|
|
|
BarcodeValidationInfoView(
|
|
|
validationResult: validationResult,
|
|
|
barcodeType: selectedBarcodeType
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 预览
|
|
|
if !content.isEmpty {
|
|
|
Section("预览") {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
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)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if selectedDataType == .barcode && !content.isEmpty && isInputComplete() {
|
|
|
// 条形码图片预览(仅在输入完成时显示)
|
|
|
BarcodePreviewView(
|
|
|
content: content,
|
|
|
barcodeType: selectedBarcodeType
|
|
|
)
|
|
|
.frame(height: 120)
|
|
|
.frame(maxWidth: .infinity)
|
|
|
} else {
|
|
|
// 二维码或文本预览
|
|
|
Text(content)
|
|
|
.font(.body)
|
|
|
.foregroundColor(.secondary)
|
|
|
.padding()
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
|
}
|
|
|
}
|
|
|
.padding()
|
|
|
.background(Color(.systemGray6))
|
|
|
.cornerRadius(8)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.navigationTitle("创建\(selectedDataType.displayName)")
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
.toolbar {
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
|
Button("创建") { createCode() }
|
|
|
.disabled(content.isEmpty)
|
|
|
}
|
|
|
}
|
|
|
.alert("提示", isPresented: $showingAlert) {
|
|
|
Button("确定") { }
|
|
|
} message: { Text(alertMessage) }
|
|
|
.onAppear {
|
|
|
// 稍延迟以确保进入页面时自动聚焦
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
|
isContentFieldFocused = true
|
|
|
}
|
|
|
}
|
|
|
.onChange(of: selectedDataType) { _ in
|
|
|
isContentFieldFocused = true
|
|
|
}
|
|
|
.onChange(of: selectedBarcodeType) { _ in
|
|
|
isContentFieldFocused = true
|
|
|
}
|
|
|
.onTapGesture {
|
|
|
// 点击外部关闭键盘
|
|
|
isContentFieldFocused = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 条形码验证方法
|
|
|
private func validateBarcodeContent(_ newContent: String) {
|
|
|
guard selectedDataType == .barcode else { return }
|
|
|
validationResult = BarcodeValidator.validateBarcode(newContent, type: selectedBarcodeType)
|
|
|
// 如果验证通过,自动格式化内容
|
|
|
if let result = validationResult, result.isValid {
|
|
|
content = result.formattedContent
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 键盘配置方法
|
|
|
private func getKeyboardType() -> UIKeyboardType {
|
|
|
guard selectedDataType == .barcode else { return .default }
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13, .ean8, .upce, .itf14:
|
|
|
return .numberPad
|
|
|
case .code39, .code128, .pdf417:
|
|
|
return .asciiCapable
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func getAutocapitalization() -> TextInputAutocapitalization {
|
|
|
guard selectedDataType == .barcode else { return .sentences }
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13, .ean8, .upce, .itf14:
|
|
|
return .never
|
|
|
case .code39, .code128, .pdf417:
|
|
|
return .characters
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 计数/规则
|
|
|
private func getMaxLength() -> Int? {
|
|
|
guard selectedDataType == .barcode else { return nil }
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13: return 13
|
|
|
case .ean8: return 8
|
|
|
case .upce: return 8
|
|
|
case .itf14: return 14
|
|
|
default: return nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func requiresDigitsOnly() -> Bool {
|
|
|
guard selectedDataType == .barcode else { return false }
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13, .ean8, .upce, .itf14:
|
|
|
return true
|
|
|
default:
|
|
|
return false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func normalizedInputCount() -> Int {
|
|
|
if requiresDigitsOnly() {
|
|
|
return content.filter { $0.isNumber }.count
|
|
|
}
|
|
|
return content.count
|
|
|
}
|
|
|
|
|
|
// MARK: - 条形码格式提示
|
|
|
private func getBarcodeFormatHint() -> String {
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13:
|
|
|
return "请输入13位数字,如:1234567890123"
|
|
|
case .ean8:
|
|
|
return "请输入8位数字,如:12345678"
|
|
|
case .upce:
|
|
|
return "请输入8位数字,如:12345678"
|
|
|
case .code39:
|
|
|
return "请输入字母、数字、空格和特殊字符"
|
|
|
case .code128:
|
|
|
return "请输入任意ASCII字符"
|
|
|
case .itf14:
|
|
|
return "请输入14位数字,如:12345678901234"
|
|
|
case .pdf417:
|
|
|
return "请输入任意ASCII字符"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 输入框占位符文本
|
|
|
private func getPlaceholderText() -> String {
|
|
|
if selectedDataType == .barcode {
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13:
|
|
|
return "输入13位数字"
|
|
|
case .ean8:
|
|
|
return "输入8位数字"
|
|
|
case .upce:
|
|
|
return "输入8位数字"
|
|
|
case .code39:
|
|
|
return "输入字母、数字等"
|
|
|
case .code128:
|
|
|
return "输入任意字符"
|
|
|
case .itf14:
|
|
|
return "输入14位数字"
|
|
|
case .pdf417:
|
|
|
return "输入任意字符"
|
|
|
}
|
|
|
} else {
|
|
|
return "请输入内容"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 播放输入提示音
|
|
|
private func playInputSound() {
|
|
|
// 使用系统触觉反馈作为输入提示音
|
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .light)
|
|
|
impactFeedback.impactOccurred()
|
|
|
}
|
|
|
|
|
|
// MARK: - 判断输入是否完成
|
|
|
private func isInputComplete() -> Bool {
|
|
|
if selectedDataType == .barcode {
|
|
|
// 条形码需要验证格式正确
|
|
|
return validationResult?.isValid == true
|
|
|
} else {
|
|
|
// 二维码只要有内容就算完成
|
|
|
return !content.isEmpty
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func createCode() {
|
|
|
guard !content.isEmpty else { return }
|
|
|
// 如果是条形码,验证格式
|
|
|
if selectedDataType == .barcode {
|
|
|
let validation = BarcodeValidator.validateBarcode(content, type: selectedBarcodeType)
|
|
|
if !validation.isValid {
|
|
|
alertMessage = validation.errorMessage ?? "条形码格式不正确"
|
|
|
showingAlert = true
|
|
|
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()
|
|
|
}
|