// // CreateRecordSheet.swift // VenusKitto // // Created by Neoa on 2025/8/25. // import Foundation import UIKit /// 自定义创建记录弹窗(含半透明遮罩 + 白色面板) final class CreateRecordSheet: UIView, UITextViewDelegate { // 需要添加一个viewController引用 weak var viewController: UIViewController? // MARK: - UI private let overlay = UIControl() private let sheet = UIView() private let header = UIView() private let backBtn = UIButton(type: .system) private let titleLabel = UILabel() private let closeBtn = UIButton(type: .system) private let memoBtn = UIButton(type: .system) private let dash = CAShapeLayer() private let scroll = UIScrollView() private let content = UIStackView() // Editor (Quick Memo) private var editorView: UIView? private var editorBuilt = false private let editorTextView = UITextView() private let editorPlaceholder = UILabel() // Editor title & tag private let editorTitleLabel = UILabel() private let tagButton = UIButton(type: .system) private var selectedEventName: String = "随手小记" // 事件标题到 recordTypeId 的映射(按后端给定) private let recordTypeMap: [String: Int] = [ "吃饭": 1, "喝水": 2, "喂奶": 3, "尿便": 4, "铲屎": 5, "驱虫": 6, "喂药": 7, "疫苗": 8, "看病": 9, "手术": 10, "洗澡": 11, "梳毛": 12, "剪指甲": 13, "洗耳朵": 14, // 后端示例为“挤肛门踢”,也兼容“挤肛门腺” "挤肛门踢": 15, "挤肛门腺": 15, "洗笼子": 16, "洗食盆": 17, "洗水盆": 18, "洗玩具": 19, "换耗材": 20 ] // 新增:保存选择的petId private var selectedPetId: Int? // Tag picker // 添加TagPickerView属性 private var tagPickerView: TagPickerView! // 在 CreateRecordSheet 中添加日期选择的功能 private var selectedDate: Date? // 对外回调(可选) var onDismiss: (() -> Void)? // rows 属性定义 private var rows = UIStackView() private var dateLabel = UILabel() private var petAvatar = UIImageView() private var petNameLabel = UILabel() // MARK: - Life override init(frame: CGRect) { super.init(frame: frame) buildUI() isHidden = true } required init?(coder: NSCoder) { fatalError() } // MARK: - Public func show() { isHidden = false overlay.alpha = 0 sheet.transform = CGAffineTransform(translationX: 0, y: 30) sheet.alpha = 0 setNeedsLayout() layoutIfNeeded() UIView.animate(withDuration: 0.22) { self.overlay.alpha = 1 self.sheet.alpha = 1 self.sheet.transform = .identity } } @objc func hide() { UIView.animate(withDuration: 0.2, animations: { self.overlay.alpha = 0 self.sheet.alpha = 0 self.sheet.transform = CGAffineTransform(translationX: 0, y: 10) }, completion: { _ in self.isHidden = true self.onDismiss?() }) } // MARK: - Build private func buildUI() { backgroundColor = .clear // overlay overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5) overlay.alpha = 0 overlay.addTarget(self, action: #selector(hide), for: .touchUpInside) addSubview(overlay) // sheet sheet.backgroundColor = .white sheet.layer.cornerRadius = 16 sheet.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] sheet.layer.masksToBounds = true sheet.alpha = 0 addSubview(sheet) // header backBtn.setImage(UIImage(named: "AddPet385")?.withRenderingMode(.alwaysOriginal), for: .normal) backBtn.tintColor = UIColor(hex: "#2B2B2B") backBtn.addTarget(self, action: #selector(onBackTapped), for: .touchUpInside) titleLabel.text = "创建记录" titleLabel.font = .systemFont(ofSize: 18, weight: .semibold) titleLabel.textColor = UIColor(hex: "#2B2B2B") titleLabel.textAlignment = .center closeBtn.setImage(UIImage(named: "AddPet384")?.withRenderingMode(.alwaysOriginal), for: .normal) closeBtn.tintColor = UIColor(hex: "#2B2B2B") closeBtn.addTarget(self, action: #selector(hide), for: .touchUpInside) [header, backBtn, titleLabel, closeBtn].forEach { $0.translatesAutoresizingMaskIntoConstraints = false } sheet.addSubview(header) header.addSubview(backBtn) header.addSubview(titleLabel) header.addSubview(closeBtn) // dashed memo button memoBtn.setTitle("随手小记", for: .normal) memoBtn.setTitleColor(UIColor(hex: "#2B2B2B"), for: .normal) memoBtn.titleLabel?.font = .systemFont(ofSize: 15) memoBtn.backgroundColor = UIColor(hex: "#FCFCFC") memoBtn.layer.cornerRadius = 14 memoBtn.layer.masksToBounds = true memoBtn.translatesAutoresizingMaskIntoConstraints = false sheet.addSubview(memoBtn) dash.strokeColor = UIColor(hex: "#5B4227").cgColor dash.fillColor = UIColor.clear.cgColor // 使用 NSNumber,避免某些系统下的桥接问题 dash.lineDashPattern = [NSNumber(value: 6), NSNumber(value: 4)] dash.lineCap = .round dash.lineWidth = 1 dash.zPosition = 1 memoBtn.layer.addSublayer(dash) // scroll + content scroll.showsVerticalScrollIndicator = false scroll.translatesAutoresizingMaskIntoConstraints = false sheet.addSubview(scroll) content.axis = .vertical content.spacing = 16 content.translatesAutoresizingMaskIntoConstraints = false scroll.addSubview(content) overlay.translatesAutoresizingMaskIntoConstraints = false sheet.translatesAutoresizingMaskIntoConstraints = false // 创建TagPickerView tagPickerView = TagPickerView(frame: CGRect.zero) tagPickerView.translatesAutoresizingMaskIntoConstraints = false addSubview(tagPickerView) tagPickerView.onTagSelected = { [weak self] selectedTag in guard let self = self else { return } self.selectedEventName = selectedTag self.editorTitleLabel.text = selectedTag } NSLayoutConstraint.activate([ overlay.topAnchor.constraint(equalTo: topAnchor), overlay.leadingAnchor.constraint(equalTo: leadingAnchor), overlay.trailingAnchor.constraint(equalTo: trailingAnchor), overlay.bottomAnchor.constraint(equalTo: bottomAnchor), sheet.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0), sheet.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0), sheet.bottomAnchor.constraint(equalTo: bottomAnchor), sheet.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75), header.topAnchor.constraint(equalTo: sheet.topAnchor, constant: 8), header.leadingAnchor.constraint(equalTo: sheet.leadingAnchor), header.trailingAnchor.constraint(equalTo: sheet.trailingAnchor), header.heightAnchor.constraint(equalToConstant: 44), backBtn.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 12), backBtn.centerYAnchor.constraint(equalTo: header.centerYAnchor), backBtn.widthAnchor.constraint(equalToConstant: 30), backBtn.heightAnchor.constraint(equalToConstant: 30), closeBtn.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -12), closeBtn.centerYAnchor.constraint(equalTo: header.centerYAnchor), closeBtn.widthAnchor.constraint(equalToConstant: 30), closeBtn.heightAnchor.constraint(equalToConstant: 30), titleLabel.centerXAnchor.constraint(equalTo: header.centerXAnchor), titleLabel.centerYAnchor.constraint(equalTo: header.centerYAnchor), memoBtn.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 8), memoBtn.leadingAnchor.constraint(equalTo: sheet.leadingAnchor, constant: 16), memoBtn.trailingAnchor.constraint(equalTo: sheet.trailingAnchor, constant: -16), memoBtn.heightAnchor.constraint(equalToConstant: 48), scroll.topAnchor.constraint(equalTo: memoBtn.bottomAnchor, constant: 12), scroll.leadingAnchor.constraint(equalTo: sheet.leadingAnchor), scroll.trailingAnchor.constraint(equalTo: sheet.trailingAnchor), scroll.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: -8), content.topAnchor.constraint(equalTo: scroll.topAnchor, constant: 8), content.leadingAnchor.constraint(equalTo: scroll.leadingAnchor, constant: 16), content.trailingAnchor.constraint(equalTo: scroll.trailingAnchor, constant: -16), content.bottomAnchor.constraint(equalTo: scroll.bottomAnchor, constant: -16), content.widthAnchor.constraint(equalTo: scroll.widthAnchor, constant: -32), tagPickerView.topAnchor.constraint(equalTo: sheet.topAnchor), tagPickerView.leadingAnchor.constraint(equalTo: sheet.leadingAnchor), tagPickerView.trailingAnchor.constraint(equalTo: sheet.trailingAnchor), tagPickerView.bottomAnchor.constraint(equalTo: sheet.bottomAnchor) ]) // 隐藏TagPickerView tagPickerView.isHidden = true // Sections let sections: [(String, [String])] = [ ("日常事项", ["吃饭","喝水","喂奶","尿便","铲屎"]), ("健康事项", ["驱虫","喂药","疫苗","看病","手术"]), ("清洁事项", ["洗澡","梳毛","剪指甲","洗耳朵","挤肛门腺"]), ("打扫事项", ["洗笼子","洗食盆","洗水盆","洗玩具","换耗材"]) ] for (t, items) in sections { content.addArrangedSubview(makeSection(title: t, items: items)) } memoBtn.addTarget(self, action: #selector(onMemoTapped), for: .touchUpInside) // Pre-build editor container (hidden by default) buildQuickMemoEditorIfNeeded() } override func layoutSubviews() { super.layoutSubviews() // Recompute dashed border path after Auto Layout has finalized frames let bounds = memoBtn.bounds guard bounds.width > 0 && bounds.height > 0 else { return } dash.contentsScale = UIScreen.main.scale dash.frame = bounds let inset: CGFloat = dash.lineWidth / 2 let rect = bounds.insetBy(dx: inset, dy: inset) let radius = max(0, 14 - inset) dash.path = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath } // MARK: - Helpers private func makeSection(title: String, items: [String]) -> UIView { let wrap = UIStackView() wrap.axis = .vertical wrap.spacing = 8 let titleLabel = UILabel() titleLabel.text = title titleLabel.font = .systemFont(ofSize: 13, weight: .semibold) titleLabel.textColor = UIColor(hex: "#2B2B2B") wrap.addArrangedSubview(titleLabel) let grid = UIStackView() grid.axis = .vertical grid.spacing = 12 wrap.addArrangedSubview(grid) var row = UIStackView(); row.axis = .horizontal; row.spacing = 16; row.distribution = .fillEqually grid.addArrangedSubview(row) var count = 0 for name in items { if count == 5 { row = UIStackView(); row.axis = .horizontal; row.spacing = 16; row.distribution = .fillEqually grid.addArrangedSubview(row) count = 0 } let v = makeIconChip(title: name, imageName: "peihead") row.addArrangedSubview(v) count += 1 } return wrap } private func makeIconChip(title: String, imageName: String) -> UIView { let holder = UIView() let container = UIStackView() container.axis = .vertical container.alignment = .center container.spacing = 6 holder.addSubview(container) container.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ container.topAnchor.constraint(equalTo: holder.topAnchor), container.bottomAnchor.constraint(equalTo: holder.bottomAnchor), container.leadingAnchor.constraint(equalTo: holder.leadingAnchor), container.trailingAnchor.constraint(equalTo: holder.trailingAnchor) ]) let bg = UIView() bg.backgroundColor = .white bg.layer.cornerRadius = 12 bg.layer.borderWidth = 1 bg.layer.borderColor = UIColor(hex: "#E9E5E1").cgColor bg.translatesAutoresizingMaskIntoConstraints = false let iv = UIImageView(image: UIImage(named: imageName)) iv.contentMode = .scaleAspectFit iv.translatesAutoresizingMaskIntoConstraints = false bg.addSubview(iv) NSLayoutConstraint.activate([ bg.widthAnchor.constraint(equalToConstant: 56), bg.heightAnchor.constraint(equalToConstant: 56), iv.centerXAnchor.constraint(equalTo: bg.centerXAnchor), iv.centerYAnchor.constraint(equalTo: bg.centerYAnchor), iv.widthAnchor.constraint(equalToConstant: 40), iv.heightAnchor.constraint(equalToConstant: 40) ]) let label = UILabel(); label.text = title; label.font = .systemFont(ofSize: 12); label.textColor = UIColor(hex: "#6B6B6B") container.addArrangedSubview(bg) container.addArrangedSubview(label) // Full-size invisible button to capture taps let tap = UIButton(type: .system) tap.backgroundColor = .clear tap.accessibilityLabel = title // 把事件名称带出来 tap.addTarget(self, action: #selector(onIconTapped(_:)), for: .touchUpInside) holder.addSubview(tap) tap.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ tap.topAnchor.constraint(equalTo: holder.topAnchor), tap.bottomAnchor.constraint(equalTo: holder.bottomAnchor), tap.leadingAnchor.constraint(equalTo: holder.leadingAnchor), tap.trailingAnchor.constraint(equalTo: holder.trailingAnchor) ]) return holder } // MARK: - Back/Editor @objc private func onBackTapped() { if let ed = editorView, !ed.isHidden { // Back from editor to grid toggleEditor(false, animated: true) } else { hide() } } @objc private func openQuickMemoEditor() { buildQuickMemoEditorIfNeeded() toggleEditor(true, animated: true) } private func buildQuickMemoEditorIfNeeded() { guard !editorBuilt else { return } editorBuilt = true //键盘回收功能 wire() let ed = UIView() ed.backgroundColor = .clear ed.isHidden = true sheet.addSubview(ed) ed.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ ed.leadingAnchor.constraint(equalTo: sheet.leadingAnchor, constant: 0), ed.trailingAnchor.constraint(equalTo: sheet.trailingAnchor, constant: 0), ed.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 0), ed.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: 0) ]) self.editorView = ed // Card let card = UIView() card.backgroundColor = .white card.layer.cornerRadius = 14 card.layer.masksToBounds = false ed.addSubview(card) card.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ card.leadingAnchor.constraint(equalTo: ed.leadingAnchor, constant: 0), card.trailingAnchor.constraint(equalTo: ed.trailingAnchor, constant: -0), card.topAnchor.constraint(equalTo: ed.topAnchor, constant: 0) ]) // Shadow to mimic design // card.layer.shadowColor = UIColor.black.withAlphaComponent(0.06).cgColor // card.layer.shadowOpacity = 1 // card.layer.shadowRadius = 10 // card.layer.shadowOffset = CGSize(width: 0, height: 6) let vstack = UIStackView(); vstack.axis = .vertical; vstack.alignment = .fill; vstack.spacing = 16 card.addSubview(vstack) vstack.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ vstack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16), vstack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16), vstack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16), ]) // Icon + title let icon = UIImageView(image: UIImage(named: "peihead")) icon.contentMode = .scaleAspectFit icon.translatesAutoresizingMaskIntoConstraints = false icon.widthAnchor.constraint(equalToConstant: 88).isActive = true icon.heightAnchor.constraint(equalToConstant: 88).isActive = true let iconWrap = UIView(); iconWrap.translatesAutoresizingMaskIntoConstraints = false iconWrap.addSubview(icon) NSLayoutConstraint.activate([ icon.centerXAnchor.constraint(equalTo: iconWrap.centerXAnchor), icon.topAnchor.constraint(equalTo: iconWrap.topAnchor), icon.bottomAnchor.constraint(equalTo: iconWrap.bottomAnchor) ]) vstack.addArrangedSubview(iconWrap) // 修改标题行的创建代码 let titleRow = UIStackView() titleRow.axis = .horizontal titleRow.alignment = .center titleRow.distribution = .fill editorTitleLabel.text = selectedEventName editorTitleLabel.font = .systemFont(ofSize: 16, weight: .semibold) editorTitleLabel.textColor = UIColor(hex: "#2B2B2B") editorTitleLabel.textAlignment = .center // 添加居中文本对齐 // 创建左右占位视图以确保标题居中 let leftSpacer = UIView() let rightSpacer = UIView() titleRow.addArrangedSubview(leftSpacer) titleRow.addArrangedSubview(editorTitleLabel) titleRow.addArrangedSubview(rightSpacer) // 现在左右占位视图已经添加到同一个父视图(titleRow)中,可以设置宽度相等约束 leftSpacer.widthAnchor.constraint(equalTo: rightSpacer.widthAnchor).isActive = true // 确保占位视图有最小高度 leftSpacer.heightAnchor.constraint(equalToConstant: 44).isActive = true rightSpacer.heightAnchor.constraint(equalToConstant: 44).isActive = true // 标签按钮放在右侧占位视图中 rightSpacer.addSubview(tagButton) rightSpacer.isUserInteractionEnabled = true tagButton.translatesAutoresizingMaskIntoConstraints = false tagButton.setImage(UIImage(named: "AddRecord382")?.withRenderingMode(.alwaysOriginal), for: .normal) tagButton.contentEdgeInsets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4) tagButton.addTarget(self, action: #selector(openTagPicker), for: .touchUpInside) // 修改约束,确保按钮在右侧并居中 NSLayoutConstraint.activate([ tagButton.leadingAnchor.constraint(equalTo: rightSpacer.leadingAnchor, constant: 8), // 添加一些右边距 tagButton.centerYAnchor.constraint(equalTo: rightSpacer.centerYAnchor), tagButton.widthAnchor.constraint(equalToConstant: 30), // 明确设置宽度 tagButton.heightAnchor.constraint(equalToConstant: 30) // 明确设置高度 ]) vstack.addArrangedSubview(titleRow) // Form rows container rows = UIStackView(); rows.axis = .vertical; rows.spacing = 12 vstack.addArrangedSubview(rows) // Row: 选择宠物(右侧小头像)Home372 let petRow = makeFormRow(left: "选择宠物") petAvatar = UIImageView(image: UIImage(named: "Home372")) petAvatar.layer.cornerRadius = 16 petAvatar.clipsToBounds = true petAvatar.translatesAutoresizingMaskIntoConstraints = false petAvatar.widthAnchor.constraint(equalToConstant: 32).isActive = true petAvatar.heightAnchor.constraint(equalToConstant: 32).isActive = true // Configure petNameLabel petNameLabel.font = .systemFont(ofSize: 14) petNameLabel.textColor = UIColor(hex: "#2B2B2B") petNameLabel.textAlignment = .right petNameLabel.numberOfLines = 1 petNameLabel.text = "" petNameLabel.translatesAutoresizingMaskIntoConstraints = false // Set content hugging and compression priorities petNameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) petNameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) petRow.rightContainer.addSubview(petNameLabel) petRow.rightContainer.addSubview(petAvatar) NSLayoutConstraint.activate([ petAvatar.trailingAnchor.constraint(equalTo: petRow.rightContainer.trailingAnchor), petAvatar.centerYAnchor.constraint(equalTo: petRow.rightContainer.centerYAnchor), petNameLabel.trailingAnchor.constraint(equalTo: petAvatar.leadingAnchor, constant: -8), petNameLabel.centerYAnchor.constraint(equalTo: petRow.rightContainer.centerYAnchor), petNameLabel.leadingAnchor.constraint(greaterThanOrEqualTo: petRow.rightContainer.leadingAnchor) ]) petRow.container.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openPetPicker))) rows.addArrangedSubview(petRow.container) // Row: 记录时间(右侧日期 + chevron) let timeRow = makeFormRow(left: "记录时间") dateLabel = UILabel(); dateLabel.text = DateFormatter.memoDateString(from: Date()); dateLabel.font = .systemFont(ofSize: 14); dateLabel.textColor = UIColor(hex: "#2B2B2B") let chevron = UIImageView(image: UIImage(systemName: "chevron.right")); chevron.tintColor = UIColor(hex: "#2B2B2B"); chevron.translatesAutoresizingMaskIntoConstraints = false; chevron.widthAnchor.constraint(equalToConstant: 10).isActive = true let rightStack = UIStackView(); rightStack.axis = .horizontal; rightStack.alignment = .center; rightStack.spacing = 6 rightStack.addArrangedSubview(dateLabel) rightStack.addArrangedSubview(chevron) timeRow.rightContainer.addSubview(rightStack) rightStack.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ rightStack.trailingAnchor.constraint(equalTo: timeRow.rightContainer.trailingAnchor), rightStack.centerYAnchor.constraint(equalTo: timeRow.rightContainer.centerYAnchor) ]) timeRow.container.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openDatePicker))) rows.addArrangedSubview(timeRow.container) // Note box let noteBox = UIView(); noteBox.backgroundColor = UIColor(hex: "#F8F6F4"); noteBox.layer.cornerRadius = 12 noteBox.translatesAutoresizingMaskIntoConstraints = false noteBox.heightAnchor.constraint(equalToConstant: 136).isActive = true vstack.addArrangedSubview(noteBox) editorTextView.backgroundColor = .clear editorTextView.font = .systemFont(ofSize: 14) editorTextView.textColor = UIColor(hex: "#2B2B2B") editorTextView.delegate = self noteBox.addSubview(editorTextView) editorTextView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ editorTextView.leadingAnchor.constraint(equalTo: noteBox.leadingAnchor, constant: 12), editorTextView.trailingAnchor.constraint(equalTo: noteBox.trailingAnchor, constant: -12), editorTextView.topAnchor.constraint(equalTo: noteBox.topAnchor, constant: 12), editorTextView.bottomAnchor.constraint(equalTo: noteBox.bottomAnchor, constant: -12) ]) editorPlaceholder.text = "字里行间,都是我们的故事" editorPlaceholder.textColor = UIColor(hex: "#B0B0B0") editorPlaceholder.font = .systemFont(ofSize: 14) noteBox.addSubview(editorPlaceholder) editorPlaceholder.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ editorPlaceholder.leadingAnchor.constraint(equalTo: editorTextView.leadingAnchor), editorPlaceholder.topAnchor.constraint(equalTo: editorTextView.topAnchor) ]) // Save button let saveBtn = UIButton(type: .system) saveBtn.setTitle("保存", for: .normal) saveBtn.setTitleColor(.black, for: .normal) saveBtn.backgroundColor = UIColor(hex: "#FFE059") saveBtn.layer.cornerRadius = 24 vstack.addArrangedSubview(saveBtn) saveBtn.heightAnchor.constraint(equalToConstant: 48).isActive = true saveBtn.addTarget(self, action: #selector(onSaveMemo), for: .touchUpInside) // Bottom spacing to allow scrolling content not clipped let bottom = UIView(); bottom.heightAnchor.constraint(equalToConstant: 16).isActive = true vstack.addArrangedSubview(bottom) // Lay out mini dash square in next layout pass // mini.layoutIfNeeded() // let miniInset: CGFloat = 0.5 // let miniPath = UIBezierPath(roundedRect: mini.bounds.insetBy(dx: miniInset, dy: miniInset), cornerRadius: 3) // miniDash.path = miniPath.cgPath // Ensure card bottom anchors to its subviews let bottomConstraint = vstack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16) bottomConstraint.isActive = true } @objc private func openPetPicker() { let petListVC = PetListViewController() petListVC.onPetSelected = { [weak self] selectedPet in self?.updatePetInfo(pet: selectedPet) } viewController?.present(petListVC, animated: true) } func updatePetInfo(pet: Pet) { petNameLabel.text = pet.name // 在加载图片前设置selectedPetId self.selectedPetId = Int(pet.id) ?? self.selectedPetId print("dddd \(pet.name)") if let url = URL(string: pet.avatar) { loadImage(from: url) } // selectedPet = pet } private func loadImage(from url: URL) { // Load the image without using third-party libraries URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let data = data, error == nil else { return } DispatchQueue.main.async { self?.petAvatar.image = UIImage(data: data) } }.resume() } @objc private func openDatePicker() { let datePickerController = DatePickerSheetController(initial: selectedDate ?? Date()) datePickerController.onDone = { [weak self] selectedDate in self?.selectedDate = selectedDate let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" print("sss \(dateFormatter.string(from: selectedDate))") // 更新记录时间显示 self?.dateLabel.text = dateFormatter.string(from: selectedDate) } viewController?.present(datePickerController, animated: false) } private func wire() { // 关闭键盘的手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) tapGesture.cancelsTouchesInView = false addGestureRecognizer(tapGesture) } @objc private func dismissKeyboard() { // 结束编辑并收回键盘 editorTextView.resignFirstResponder() } private func toggleEditor(_ show: Bool, animated: Bool) { guard let ed = editorView else { return } let work = { ed.isHidden = !show self.scroll.isHidden = show } if animated { UIView.transition(with: sheet, duration: 0.2, options: .transitionCrossDissolve, animations: work) } else { work() } } private func makeFormRow(left: String) -> (container: UIView, rightContainer: UIView) { let box = UIView(); box.backgroundColor = UIColor.white; box.layer.cornerRadius = 12 box.layer.shadowColor = UIColor.black.withAlphaComponent(0.05).cgColor box.layer.shadowOpacity = 1; box.layer.shadowRadius = 6; box.layer.shadowOffset = CGSize(width: 0, height: 3) let leftLabel = UILabel(); leftLabel.text = left; leftLabel.font = .systemFont(ofSize: 14); leftLabel.textColor = UIColor(hex: "#6B6B6B") let right = UIView() let line = UIView(); line.backgroundColor = UIColor(hex: "#F0EEEC") [leftLabel, right, line].forEach { box.addSubview($0); $0.translatesAutoresizingMaskIntoConstraints = false } NSLayoutConstraint.activate([ leftLabel.leadingAnchor.constraint(equalTo: box.leadingAnchor, constant: 12), leftLabel.centerYAnchor.constraint(equalTo: box.centerYAnchor), right.trailingAnchor.constraint(equalTo: box.trailingAnchor, constant: -12), right.centerYAnchor.constraint(equalTo: box.centerYAnchor), right.leadingAnchor.constraint(greaterThanOrEqualTo: leftLabel.trailingAnchor, constant: 8), line.heightAnchor.constraint(equalToConstant: 1), line.leadingAnchor.constraint(equalTo: box.leadingAnchor), line.trailingAnchor.constraint(equalTo: box.trailingAnchor), line.bottomAnchor.constraint(equalTo: box.bottomAnchor) ]) box.heightAnchor.constraint(equalToConstant: 54).isActive = true // 为 container 添加手势识别器 let container = box let rightContainer = right container.isUserInteractionEnabled = true // 确保视图可以接受手势 return (container, rightContainer) } @objc private func onSaveMemo() { // 1. 构建请求body let content = editorTextView.text ?? "" let title = (editorTitleLabel.text?.isEmpty == false ? editorTitleLabel.text! : selectedEventName) let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" let recordTypeIdVal = recordTypeMap[title] ?? 0 let recordDate = dateFormatter.string(from: selectedDate ?? Date()) let petId = selectedPetId ?? 0 let params: [String: Any] = [ "content": content, "title": title, "recordDate": recordDate, "petId": petId, // 其他字段可按接口需要补充,暂用默认 "module": "event", "recordTypeId": recordTypeIdVal, "recordUrl": "", "id": 0, "createTime": "", "updateTime": "" ] print("🧩 recordTypeId for title(\(title)) = \(recordTypeIdVal)") guard let url = URL(string: "\(baseURL)/petRecordInfo/createRecord") else { showToast("无效的请求地址") return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") if let token = UserDefaults.standard.string(forKey: "userToken") { request.setValue(token, forHTTPHeaderField: "Authorization") } do { let body = try JSONSerialization.data(withJSONObject: params, options: []) request.httpBody = body print("请求参数: \(String(data: body, encoding: .utf8) ?? "")") } catch { showToast("参数序列化失败") return } let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in DispatchQueue.main.async { if let error = error { print("网络错误: \(error)") self?.showToast("网络错误") return } guard let httpResp = response as? HTTPURLResponse else { self?.showToast("无响应") return } let status = httpResp.statusCode let respBody = data.flatMap { String(data: $0, encoding: .utf8) } ?? "" print("响应状态: \(status)") print("响应内容: \(respBody)") // 尝试解析json var codeValue: String = "" var msgValue: String = "" if let d = data { if let json = try? JSONSerialization.jsonObject(with: d, options: []) as? [String: Any] { codeValue = (json["code"] as? String) ?? (json["code"] as? Int).map { String($0) } ?? "" msgValue = (json["msg"] as? String) ?? "" } } if status == 200 && (codeValue == "200" || codeValue == "200") { self?.showToast("保存成功") // 通知 HomeViewController 刷新数据 let petIdStr = String(petId) NotificationCenter.default.post(name: .eventRecordDidCreate, object: nil, userInfo: [ "petId": petIdStr, "recordDate": recordDate, "title": title ]) DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { self?.hide() } } else { self?.showToast(msgValue.isEmpty ? "保存失败" : msgValue) } } } task.resume() } // MARK: - Toast Helper private func showToast(_ text: String) { let lab = PaddingLabel(insets: .init(top: 8, left: 12, bottom: 8, right: 12)) lab.backgroundColor = UIColor.black.withAlphaComponent(0.85) lab.textColor = .white lab.font = .systemFont(ofSize: 14) lab.layer.cornerRadius = 8 lab.layer.masksToBounds = true lab.text = text sheet.addSubview(lab) lab.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ lab.centerXAnchor.constraint(equalTo: sheet.centerXAnchor), lab.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: -40) ]) lab.alpha = 0 UIView.animate(withDuration: 0.2, animations: { lab.alpha = 1 }) { _ in UIView.animate(withDuration: 0.2, delay: 1.2, options: [], animations: { lab.alpha = 0 }) { _ in lab.removeFromSuperview() } } } // MARK: - Memo & Icon Tap Handlers @objc private func onMemoTapped() { selectedEventName = "随手小记" editorTitleLabel.text = selectedEventName openQuickMemoEditor() } @objc private func onIconTapped(_ sender: UIButton) { let name = sender.accessibilityLabel ?? "随手小记" selectedEventName = name editorTitleLabel.text = name openQuickMemoEditor() } // MARK: - Tag Picker UI @objc private func openTagPicker() { tagPickerView.isHidden = false tagPickerView.show() } func textViewDidChange(_ textView: UITextView) { editorPlaceholder.isHidden = !textView.text.isEmpty } } extension DateFormatter { static func memoDateString(from date: Date) -> String { let df = DateFormatter(); df.dateFormat = "yyyy-MM-dd"; return df.string(from: date) } } // MARK: - Notification Keys extension Notification.Name { static let eventRecordDidCreate = Notification.Name("EventRecordDidCreate") }