diff --git a/MyQrCode/Models/QRCodeParser.swift b/MyQrCode/Models/QRCodeParser.swift index 68b2949..698f007 100644 --- a/MyQrCode/Models/QRCodeParser.swift +++ b/MyQrCode/Models/QRCodeParser.swift @@ -377,28 +377,75 @@ class QRCodeParser { let components = mecardInfo.components(separatedBy: ";") var name = "" + var nickname = "" var phone = "" var email = "" + var company = "" var address = "" + var website = "" + var birthday = "" + var note = "" for component in components { - if component.hasPrefix("N:") { - name = String(component.dropFirst(2)) - } else if component.hasPrefix("TEL:") { - phone = String(component.dropFirst(4)) - } else if component.hasPrefix("EMAIL:") { - email = String(component.dropFirst(6)) - } else if component.hasPrefix("ADR:") { - address = String(component.dropFirst(4)) + let trimmedComponent = component.trimmingCharacters(in: .whitespaces) + if trimmedComponent.isEmpty { continue } + + if trimmedComponent.hasPrefix("N:") { + let nameValue = String(trimmedComponent.dropFirst(2)) + let nameParts = nameValue.components(separatedBy: ",") + if nameParts.count >= 2 { + let lastName = nameParts[0] + let firstName = nameParts[1] + name = "\(firstName) \(lastName)" + } else if nameParts.count == 1 { + name = nameParts[0] + } + } else if trimmedComponent.hasPrefix("NICKNAME:") { + nickname = String(trimmedComponent.dropFirst(9)) + } else if trimmedComponent.hasPrefix("TEL:") { + phone = String(trimmedComponent.dropFirst(4)) + } else if trimmedComponent.hasPrefix("EMAIL:") { + email = String(trimmedComponent.dropFirst(6)) + } else if trimmedComponent.hasPrefix("ORG:") { + company = String(trimmedComponent.dropFirst(4)) + } else if trimmedComponent.hasPrefix("ADR:") { + address = String(trimmedComponent.dropFirst(4)) + } else if trimmedComponent.hasPrefix("URL:") { + website = String(trimmedComponent.dropFirst(4)) + } else if trimmedComponent.hasPrefix("BDAY:") { + let birthdayValue = String(trimmedComponent.dropFirst(5)) + if birthdayValue.count == 8 { + let year = String(birthdayValue.prefix(4)) + let month = String(birthdayValue.dropFirst(4).prefix(2)) + let day = String(birthdayValue.dropFirst(6)) + birthday = "\(year)年\(month)月\(day)日" + } else { + birthday = birthdayValue + } + } else if trimmedComponent.hasPrefix("NOTE:") { + note = String(trimmedComponent.dropFirst(5)) } } - let title = "联系人信息" - let subtitle = "姓名: \(name)\n电话: \(phone)\n邮箱: \(email)\n地址: \(address)" + var subtitle = "" + if !name.isEmpty { subtitle += "姓名: \(name)\n" } + if !nickname.isEmpty { subtitle += "昵称: \(nickname)\n" } + if !phone.isEmpty { subtitle += "电话: \(phone)\n" } + if !email.isEmpty { subtitle += "邮箱: \(email)\n" } + if !company.isEmpty { subtitle += "公司: \(company)\n" } + if !address.isEmpty { subtitle += "地址: \(address)\n" } + if !website.isEmpty { subtitle += "网站: \(website)\n" } + if !birthday.isEmpty { subtitle += "生日: \(birthday)\n" } + if !note.isEmpty { subtitle += "备注: \(note)\n" } + + // 移除最后一个换行符 + if subtitle.hasSuffix("\n") { + subtitle = String(subtitle.dropLast()) + } return ParsedQRData( type: .mecard, - title: title, + title: "联系人信息", subtitle: subtitle, icon: "person.crop.rectangle" ) diff --git a/MyQrCode/Views/Components/ContactInputView.swift b/MyQrCode/Views/Components/ContactInputView.swift index 9c1c3a9..1dfd6c5 100644 --- a/MyQrCode/Views/Components/ContactInputView.swift +++ b/MyQrCode/Views/Components/ContactInputView.swift @@ -10,11 +10,14 @@ struct ContactInputView: View { @Binding var title: String @Binding var address: String @Binding var website: String + @Binding var nickname: String + @Binding var birthday: Date + @Binding var note: String @FocusState var focusedField: ContactField? // 联系人字段枚举 enum ContactField: Hashable { - case firstName, lastName, phone, email, company, title, address, website + case firstName, lastName, phone, email, company, title, address, website, nickname, birthday, note } var body: some View { @@ -52,6 +55,20 @@ struct ContactInputView: View { } } + // 昵称 + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("昵称") + .font(.subheadline) + .foregroundColor(.primary) + Spacer() + } + + TextField("昵称", text: $nickname) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .focused($focusedField, equals: .nickname) + } + // 电话 VStack(alignment: .leading, spacing: 8) { HStack { @@ -140,6 +157,34 @@ struct ContactInputView: View { .autocapitalization(.none) .focused($focusedField, equals: .website) } + + // 生日 + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("生日") + .font(.subheadline) + .foregroundColor(.primary) + Spacer() + } + + DatePicker("选择生日", selection: $birthday, displayedComponents: .date) + .datePickerStyle(CompactDatePickerStyle()) + .focused($focusedField, equals: .birthday) + } + + // 备注 + VStack(alignment: .leading, spacing: 8) { + HStack { + Text("备注") + .font(.subheadline) + .foregroundColor(.primary) + Spacer() + } + + TextField("备注信息", text: $note) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .focused($focusedField, equals: .note) + } } .toolbar { ToolbarItemGroup(placement: .keyboard) { @@ -163,6 +208,9 @@ struct ContactInputView: View { company: .constant(""), title: .constant(""), address: .constant(""), - website: .constant("") + website: .constant(""), + nickname: .constant(""), + birthday: .constant(Date()), + note: .constant("") ) } \ No newline at end of file diff --git a/MyQrCode/Views/Components/InputComponentFactory.swift b/MyQrCode/Views/Components/InputComponentFactory.swift index 4118480..9479740 100644 --- a/MyQrCode/Views/Components/InputComponentFactory.swift +++ b/MyQrCode/Views/Components/InputComponentFactory.swift @@ -26,6 +26,9 @@ struct ContactInputConfig { let title: Binding let address: Binding let website: Binding + let nickname: Binding + let birthday: Binding + let note: Binding } // MARK: - 位置输入配置 @@ -104,7 +107,10 @@ struct InputComponentFactory { company: config.company, title: config.title, address: config.address, - website: config.website + website: config.website, + nickname: config.nickname, + birthday: config.birthday, + note: config.note ) ) } diff --git a/MyQrCode/Views/CreateQRCodeView.swift b/MyQrCode/Views/CreateQRCodeView.swift index 38699e3..d41a963 100644 --- a/MyQrCode/Views/CreateQRCodeView.swift +++ b/MyQrCode/Views/CreateQRCodeView.swift @@ -37,6 +37,9 @@ struct CreateQRCodeView: View { @State private var contactTitle = "" @State private var contactAddress = "" @State private var contactWebsite = "" + @State private var contactNickname = "" + @State private var contactBirthday = Date() + @State private var contactNote = "" @FocusState private var focusedContactField: ContactInputView.ContactField? // 位置相关字段 @@ -173,7 +176,10 @@ struct CreateQRCodeView: View { company: $contactCompany, title: $contactTitle, address: $contactAddress, - website: $contactWebsite + website: $contactWebsite, + nickname: $contactNickname, + birthday: $contactBirthday, + note: $contactNote ) return InputComponentFactory.createInputComponent( for: selectedQRCodeType, @@ -433,24 +439,55 @@ struct CreateQRCodeView: View { return vcard case .mecard: var mecard = "MECARD:" + + // 姓名字段 if !contactFirstName.isEmpty || !contactLastName.isEmpty { - mecard += "N:\(contactLastName),\(contactFirstName);" + let lastName = contactLastName.isEmpty ? "" : contactLastName + let firstName = contactFirstName.isEmpty ? "" : contactFirstName + mecard += "N:\(lastName),\(firstName);" + } + + // 昵称字段 + if !contactNickname.isEmpty { + mecard += "NICKNAME:\(contactNickname);" } + + // 电话字段 if !contactPhone.isEmpty { mecard += "TEL:\(contactPhone);" } + + // 邮箱字段 if !contactEmail.isEmpty { mecard += "EMAIL:\(contactEmail);" } + + // 公司字段 if !contactCompany.isEmpty { mecard += "ORG:\(contactCompany);" } + + // 地址字段 if !contactAddress.isEmpty { mecard += "ADR:\(contactAddress);" } + + // 网站字段 if !contactWebsite.isEmpty { mecard += "URL:\(contactWebsite);" } + + // 生日字段 + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyyMMdd" + let birthdayString = dateFormatter.string(from: contactBirthday) + mecard += "BDAY:\(birthdayString);" + + // 备注字段 + if !contactNote.isEmpty { + mecard += "NOTE:\(contactNote);" + } + mecard += ";" return mecard case .location: @@ -518,7 +555,35 @@ struct CreateQRCodeView: View { case .wifi: historyItem.content = "WiFi: \(wifiSSID) (\(wifiEncryptionType.displayName))" case .vcard, .mecard: - historyItem.content = "联系人: \(contactFirstName) \(contactLastName)" + var contactContent = "联系人: " + if !contactFirstName.isEmpty || !contactLastName.isEmpty { + contactContent += "\(contactFirstName) \(contactLastName)" + } + if !contactNickname.isEmpty { + contactContent += " (\(contactNickname))" + } + if !contactPhone.isEmpty { + contactContent += "\n电话: \(contactPhone)" + } + if !contactEmail.isEmpty { + contactContent += "\n邮箱: \(contactEmail)" + } + if !contactCompany.isEmpty { + contactContent += "\n公司: \(contactCompany)" + } + if !contactTitle.isEmpty { + contactContent += "\n职位: \(contactTitle)" + } + if !contactAddress.isEmpty { + contactContent += "\n地址: \(contactAddress)" + } + if !contactWebsite.isEmpty { + contactContent += "\n网站: \(contactWebsite)" + } + if !contactNote.isEmpty { + contactContent += "\n备注: \(contactNote)" + } + historyItem.content = contactContent case .location: historyItem.content = "位置: \(locationLatitude), \(locationLongitude)" case .calendar: diff --git a/MyQrCode/Views/QRCodeDetailView.swift b/MyQrCode/Views/QRCodeDetailView.swift index 040ea45..c5ecca4 100644 --- a/MyQrCode/Views/QRCodeDetailView.swift +++ b/MyQrCode/Views/QRCodeDetailView.swift @@ -362,6 +362,12 @@ struct ShareSheet: UIViewControllerRepresentable { return NavigationView { QRCodeDetailView(historyItem: item) } } +#Preview("MeCard") { + let ctx = PreviewData.context + let item = PreviewData.mecardSample(in: ctx) + return NavigationView { QRCodeDetailView(historyItem: item) } +} + // MARK: - Preview Data private enum PreviewData { static let context: NSManagedObjectContext = { @@ -426,4 +432,9 @@ private enum PreviewData { let content = "Hello, this is a text message!" return makeBaseItem(in: context, content: content, qrType: .text) } + + static func mecardSample(in context: NSManagedObjectContext) -> HistoryItem { + let content = "MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;BDAY:19820908;NOTE:Software Engineer;;" + return makeBaseItem(in: context, content: content, qrType: .mecard) + } } diff --git a/docs/QRCODE_DETAIL_VIEW_README.md b/docs/QRCODE_DETAIL_VIEW_README.md index 869bb87..16863ec 100644 --- a/docs/QRCODE_DETAIL_VIEW_README.md +++ b/docs/QRCODE_DETAIL_VIEW_README.md @@ -70,8 +70,8 @@ END:VCARD // 支持vCard 2.1到3.0的自动转换 // 联系人信息 (MeCard) -MECARD:N:<姓名>;TEL:<电话>;EMAIL:<邮箱>;ADR:<地址>;; -示例: MECARD:N:John Doe;TEL:+1234567890;EMAIL:example@example.com;; +MECARD:N:<姓>,<名>;NICKNAME:<昵称>;TEL:<电话>;EMAIL:<邮箱>;ORG:<公司>;ADR:<地址>;URL:<网站>;BDAY:<生日>;NOTE:<备注>;; +示例: MECARD:N:Doe,John;NICKNAME:Johnny;TEL:+1234567890;EMAIL:john.doe@example.com;ORG:Example Company;ADR:123 Main St,Anytown,CA,12345,USA;URL:https://example.com;BDAY:19820908;NOTE:Software Engineer;; // 文本内容 <文本内容>