|
|
import SwiftUI
|
|
|
|
|
|
// MARK: - 内容输入和预览组件
|
|
|
struct CodeContentInputView: View {
|
|
|
let selectedDataType: DataType
|
|
|
let selectedBarcodeType: BarcodeType
|
|
|
let selectedQRCodeType: QRCodeType
|
|
|
|
|
|
@Binding var content: String
|
|
|
@Binding var validationResult: BarcodeValidator.ValidationResult?
|
|
|
@FocusState.Binding var isContentFieldFocused: Bool
|
|
|
|
|
|
var body: some View {
|
|
|
VStack(spacing: 16) {
|
|
|
// 内容输入区域
|
|
|
contentInputSection
|
|
|
|
|
|
// 预览区域
|
|
|
if !content.isEmpty {
|
|
|
previewSection
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 内容输入区域
|
|
|
private var contentInputSection: some View {
|
|
|
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
|
|
|
handleContentChange(newValue)
|
|
|
}
|
|
|
|
|
|
// 计数显示在输入框下方,右对齐
|
|
|
if let maxLen = getMaxLength(), selectedDataType == .barcode {
|
|
|
Text("\(normalizedInputCount())/\(maxLen)")
|
|
|
.font(.caption2)
|
|
|
.foregroundColor(.blue)
|
|
|
.fontWeight(.medium)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 输入统计与验证状态
|
|
|
HStack {
|
|
|
if let result = validationResult, result.isValid {
|
|
|
Text("format_correct".localized)
|
|
|
.font(.caption2)
|
|
|
.foregroundColor(.green)
|
|
|
} else if !content.isEmpty {
|
|
|
Text("format_checking".localized)
|
|
|
.font(.caption2)
|
|
|
.foregroundColor(.orange)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 条形码验证信息(仅在格式不正确时显示)
|
|
|
if selectedDataType == .barcode && !content.isEmpty && (validationResult == nil || !validationResult!.isValid) {
|
|
|
BarcodeValidationInfoView(
|
|
|
validationResult: validationResult,
|
|
|
barcodeType: selectedBarcodeType
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 预览区域
|
|
|
private var previewSection: some View {
|
|
|
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)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 辅助方法
|
|
|
|
|
|
private func handleContentChange(_ newValue: String) {
|
|
|
// 针对有固定长度与数字要求的类型,进行输入规范化(仅保留数字并按最大长度截断)
|
|
|
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)
|
|
|
}
|
|
|
|
|
|
private func validateBarcodeContent(_ newContent: String) {
|
|
|
guard selectedDataType == .barcode else { return }
|
|
|
validationResult = BarcodeValidator.validateBarcode(newContent, type: selectedBarcodeType)
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
private func getBarcodeFormatHint() -> String {
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13:
|
|
|
return "ean_13_format_hint".localized
|
|
|
case .ean8:
|
|
|
return "ean_8_format_hint".localized
|
|
|
case .upce:
|
|
|
return "upc_e_format_hint".localized
|
|
|
case .code39:
|
|
|
return "code_39_format_hint".localized
|
|
|
case .code128:
|
|
|
return "code_128_format_hint".localized
|
|
|
case .itf14:
|
|
|
return "itf_14_format_hint".localized
|
|
|
case .pdf417:
|
|
|
return "pdf417_format_hint".localized
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func getPlaceholderText() -> String {
|
|
|
if selectedDataType == .barcode {
|
|
|
switch selectedBarcodeType {
|
|
|
case .ean13:
|
|
|
return "input_13_digits".localized
|
|
|
case .ean8:
|
|
|
return "input_8_digits".localized
|
|
|
case .upce:
|
|
|
return "input_8_digits".localized
|
|
|
case .code39:
|
|
|
return "input_letters_numbers".localized
|
|
|
case .code128:
|
|
|
return "input_any_characters".localized
|
|
|
case .itf14:
|
|
|
return "input_14_digits".localized
|
|
|
case .pdf417:
|
|
|
return "input_any_characters".localized
|
|
|
}
|
|
|
} else {
|
|
|
return "please_enter_content".localized
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private func playInputSound() {
|
|
|
// 使用系统触觉反馈作为输入提示音
|
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .light)
|
|
|
impactFeedback.impactOccurred()
|
|
|
}
|
|
|
|
|
|
private func isInputComplete() -> Bool {
|
|
|
if selectedDataType == .barcode {
|
|
|
// 条形码需要验证格式正确
|
|
|
return validationResult?.isValid == true
|
|
|
} else {
|
|
|
// 二维码只要有内容就算完成
|
|
|
return !content.isEmpty
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|