// // GroupMemberListView.swift // QuickLocation // // Created by 八条 on 2026/6/29. // import UIKit import RxSwift import RxCocoa class GroupMemberListView: UIView { var disposeBag = DisposeBag() func updateArrowVisibility() { let offsetX = collectionView.contentOffset.x let contentW = collectionView.contentSize.width let viewW = collectionView.bounds.width memberArrowLeft.isHidden = offsetX <= 0 memberArrowRight.isHidden = offsetX >= contentW - viewW } private func setupRx() { backBtn.rx.tap.subscribe(onNext: { _ in AppRouter.shared.popOrDismiss() }).disposed(by: disposeBag) collectionView.rx.contentOffset .subscribe(onNext: { [weak self] _ in self?.updateArrowVisibility() }) .disposed(by: disposeBag) // 选中日期变化时更新月份 selectedDate.subscribe(onNext: { [weak self] date in guard let self = self else { return } let fmt = DateFormatter() fmt.dateFormat = "MM月" self.monthLab.text = fmt.string(from: date) }).disposed(by: disposeBag) // 往前/往后翻 7 天 datePreviousBtn.rx.tap.subscribe(onNext: { [weak self] _ in guard let self = self else { return } let target = self.dateCollectionView.contentOffset.x - self.dateItemWidth * CGFloat(self.daysPerPage) self.dateCollectionView.setContentOffset(CGPoint(x: max(0, target), y: 0), animated: true) }).disposed(by: disposeBag) dateNextBtn.rx.tap.subscribe(onNext: { [weak self] _ in guard let self = self else { return } var target = self.dateCollectionView.contentOffset.x + self.dateItemWidth * CGFloat(self.daysPerPage) let maxOffset = CGFloat(self.dateItems.count) * self.dateItemWidth - self.dateCollectionView.bounds.width target = min(max(0, maxOffset), target) self.dateCollectionView.setContentOffset(CGPoint(x: target, y: 0), animated: true) }).disposed(by: disposeBag) } private func setupUI() { addSubview(navBgView) addSubview(navBarView) navBarView.addSubview(navTitleLabel) navBarView.addSubview(backBtn) addSubview(memberArrowLeft) addSubview(collectionView) addSubview(memberArrowRight) addSubview(reportView) navBgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(160/375) navBarView.layoutChain .edges(excludingEdge: .bottom) .height(kNaviHeight) navTitleLabel.layoutChain .top(kStatusBarHeight + 12) .centerY(backBtn) .centerX() backBtn.layoutChain .centerY(navTitleLabel) .left(15) .width(24) .height(24) collectionView.layoutChain .topToBottomOfView(navBarView, offset: 0) .height(90) .edgesHorzontal(28) memberArrowLeft.layoutChain .rightToLeftOfView(collectionView, offset: -8) .height(14) .width(5) .centerY(collectionView) memberArrowRight.layoutChain .leftToRightOfView(collectionView, offset: 8) .height(14) .width(5) .centerY(collectionView) reportView.layoutChain .topToBottomOfView(collectionView, offset: 10) .edges(excludingEdge: .top) } lazy var navBgView: UIImageView = { let iv = UIImageView() iv.image = UIImage(named: "Common/navBar_bg_2") iv.contentMode = .scaleAspectFill return iv }() lazy var navBarView: UIView = { let view = UIView() view.backgroundColor = .clear return view }() lazy var navTitleLabel: UILabel = { let label = UILabel() label.text = "圈子成员" label.font = .systemFont(ofSize: 18, weight: .medium) label.textColor = ThemeManager.shared.color.titleAuxColor label.textAlignment = .center return label }() lazy var backBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Common/back"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100) return btn }() // MARK: - 成员列表 lazy var memberArrowLeft: UIImageView = { let view = UIImageView() view.image = UIImage(named: "GroupMemberList/member_left") view.isHidden = true return view }() lazy var memberArrowRight: UIImageView = { let view = UIImageView() view.image = UIImage(named: "GroupMemberList/member_right") view.isHidden = true return view }() lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() let cvWidth = kScreenWidth - 56 layout.itemSize = CGSize(width: 65, height: 90) layout.minimumLineSpacing = 4 layout.scrollDirection = .horizontal let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.register(GroupMemberListCell.self) return cv }() // MARK: - 未解锁会员遮罩 lazy var unlockVipMaskView: UIView = { let view = UIView() view.backgroundColor = .clear view.isHidden = true let maskBg = UIImageView(image: UIImage(named: "GroupMemberList/mask_bg")) view.addSubview(maskBg) maskBg.layoutChain.edges() let tipsImg = UIImageView(image: UIImage(named: "GroupMemberList/mask_tips")) view.addSubview(tipsImg) tipsImg.layoutChain .top(83) .centerX() .width(283) .height(194) let btn = UIButton() btn.setTitle("立即开通", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) btn.setBackgroundColor(UIColor(hexStr: "#FF7B24"), for: .normal) btn.cornerRadius = 25 btn.rx.tap.subscribe(onNext: { _ in AppRouter.push(Route.vipRecharge) }).disposed(by: disposeBag) view.addSubview(btn) btn.layoutChain .topToBottomOfView(tipsImg, offset: 23) .centerX() .width(224) .height(50) return view }() // MARK: - 报告 lazy var reportView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#F5FBFF") view.layer.cornerRadius = 20 view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] let monthView = UIView() monthView.backgroundColor = UIColor(hexStr: "#57C7FF") monthView.cornerRadius = 6 monthView.addSubview(monthLab) monthLab.layoutChain .edgesVertical(2) .edgesHorzontal(12) view.addSubview(monthView) monthView.layoutChain .top(15) .left(15) let totalLab = UILabel() totalLab.text = "本周总计里程" totalLab.font = .systemFont(ofSize: 10, weight: .medium) totalLab.textColor = UIColor(hexStr: "#333333") view.addSubview(totalLab) totalLab.layoutChain .leftToRightOfView(monthView, offset: 21) .centerY(monthView) view.addSubview(mileageLab) mileageLab.layoutChain .leftToRightOfView(totalLab, offset: 5) .centerY(monthView) let highLab = UILabel() highLab.text = "本周最高时速" highLab.font = .systemFont(ofSize: 10, weight: .medium) highLab.textColor = UIColor(hexStr: "#333333") view.addSubview(highLab) highLab.layoutChain .leftToRightOfView(mileageLab, offset: 23) .centerY(monthView) view.addSubview(speedLab) speedLab.layoutChain .leftToRightOfView(highLab, offset: 5) .centerY(monthView) view.addSubview(dateView) dateView.layoutChain .topToBottomOfView(monthView, offset: 10) .edgesHorzontal() view.addSubview(scrollView) scrollView.layoutChain .topToBottomOfView(dateView) .edges(excludingEdge: .top) view.addSubview(unlockVipMaskView) unlockVipMaskView.layoutChain .topToBottomOfView(dateView) .edges(excludingEdge: .top) return view }() lazy var monthLab: UILabel = { let label = UILabel() label.text = " " label.font = .systemFont(ofSize: 14, weight: .medium) label.textColor = UIColor(hexStr: "#0F2846") label.textAlignment = .center return label }() /// 里程 lazy var mileageLab: UILabel = { let label = UILabel() label.text = "0km" label.font = .systemFont(ofSize: 16, weight: .medium) label.textColor = UIColor(hexStr: "#16B3FF") return label }() /// 速度 lazy var speedLab: UILabel = { let label = UILabel() label.text = "0km/h" label.font = .systemFont(ofSize: 16, weight: .medium) label.textColor = UIColor(hexStr: "#16B3FF") return label }() // MARK: - 日期 private var dateItems: [DateItem] = [] let selectedDate = BehaviorRelay(value: Date()) private let daysPerPage = 7 /// 当前选中的成员是否是自己 var selectedMemberIsSelf = true { didSet { updateDateSelectability() } } struct DateItem { let date: Date let day: Int let isToday: Bool let isFuture: Bool let isSelectable: Bool } /// 根据 VIP 和成员关系计算最大可查询天数 private var maxSelectableDays: Int { switch AppContextManager.shared.vip { case 1: return 0 // 非会员不可点 case 2: return selectedMemberIsSelf ? 7 : 1 case 3: return selectedMemberIsSelf ? 30 : 14 default: return 0 } } /// 重新设置 dateItems 的 isSelectable private func updateDateSelectability() { let calendar = Calendar.current let today = Date() let maxDays = maxSelectableDays dateItems = dateItems.map { item in let daysAgo = calendar.dateComponents([.day], from: item.date, to: today).day ?? 0 return DateItem(date: item.date, day: item.day, isToday: item.isToday, isFuture: item.isFuture, isSelectable: maxDays > 0 && !item.isFuture && daysAgo <= maxDays - 1) } dateCollectionView.reloadData() } private func generateDateItems() { let calendar = Calendar.current let today = Date() // 31 天前 → 今天 → 之后 3 天 = 35 天,正好 5 页 × 7 天 let pastDays = 31 let futureDays = 3 let totalDays = pastDays + 1 + futureDays // 35 dateItems = (0.. today let daysAgo = calendar.dateComponents([.day], from: date, to: today).day ?? 0 return DateItem(date: date, day: day, isToday: calendar.isDateInToday(date), isFuture: isFuture, isSelectable: maxSelectableDays > 0 && !isFuture && daysAgo <= maxSelectableDays - 1) } } /// 日期 lazy var dateView: UIView = { let view = UIView() view.backgroundColor = .clear generateDateItems() view.addSubview(datePreviousBtn) view.addSubview(dateNextBtn) view.addSubview(dateCollectionView) datePreviousBtn.layoutChain .left(15) .edgesVertical(10) .width(20) .height(20) dateNextBtn.layoutChain .right(15) .centerY() .width(20) .height(20) dateCollectionView.layoutChain .leftToRightOfView(datePreviousBtn, offset: 5) .rightToLeftOfView(dateNextBtn, offset: -5) .edgesVertical() DispatchQueue.main.async { self.scrollToToday() } return view }() private var dateItemWidth: CGFloat { let available = kScreenWidth - 15 - 20 - 5 - 5 - 20 - 15 return floor(available / CGFloat(daysPerPage)) } lazy var dateCollectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: dateItemWidth, height: 40) layout.minimumLineSpacing = 0 layout.scrollDirection = .horizontal layout.sectionInset = .zero let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.isPagingEnabled = true cv.register(DateCell.self) cv.dataSource = self cv.delegate = self return cv }() lazy var datePreviousBtn: UIButton = { let btn = UIButton() btn.setImage(UIImage(named: "GroupMemberList/date_left"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 0) return btn }() lazy var dateNextBtn: UIButton = { let btn = UIButton() btn.setImage(UIImage(named: "GroupMemberList/date_right"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 0, bottom: 15, right: 15) return btn }() // MARK: - 驾驶分析 lazy var scrollView: UIScrollView = { let view = UIScrollView() view.backgroundColor = .clear view.showsVerticalScrollIndicator = false view.bounces = false view.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSafeBottomMargin, right: 0) let contentView = UIView() contentView.backgroundColor = .clear view.addSubview(contentView) contentView.layoutChain.edges().widthToView(view) contentView.addSubview(drivingAnalysisView) drivingAnalysisView.layoutChain .top(5) .edgesHorzontal(15) .height(249) contentView.addSubview(recordView) recordView.layoutChain .topToBottomOfView(drivingAnalysisView, offset: 15) .edgesHorzontal(15) .bottom(20) return view }() lazy var drivingAnalysisView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 10 let titleBg = UIImageView(image: UIImage(named: "GroupMemberList/title_bg")) view.addSubview(titleBg) let titleLab = UILabel() titleLab.text = "驾驶分析" titleLab.font = .systemFont(ofSize: 16, weight: .medium) view.addSubview(titleLab) titleLab.layoutChain .top(15) .centerX() titleBg.layoutChain .centerX() .bottomToView(titleLab, offset: 5) view.addSubview(drivingEventCV) drivingEventCV.layoutChain .topToBottomOfView(titleBg, offset: 20) .edgesHorzontal() .bottom(20) return view }() lazy var drivingEventCV: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: 66, height: 75) layout.minimumLineSpacing = 15 layout.scrollDirection = .vertical layout.sectionInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12) let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.isScrollEnabled = false cv.register(DrivingEventCell.self) return cv }() // MARK: - 行程记录 lazy var recordView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 10 let titleBg = UIImageView(image: UIImage(named: "GroupMemberList/title_bg")) view.addSubview(titleBg) let titleLab = UILabel() titleLab.text = "行程记录" titleLab.font = .systemFont(ofSize: 16, weight: .medium) view.addSubview(titleLab) titleLab.layoutChain .top(15) .centerX() titleBg.layoutChain .centerX() .bottomToView(titleLab, offset: 5) view.addSubview(tableView) tableView.layoutChain .topToBottomOfView(titleBg, offset: 10) .edgesHorzontal() .bottom() view.addSubview(nodataLab) nodataLab.layoutChain.centerX().centerY() return view }() lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .plain) tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.estimatedRowHeight = 177 tableView.showsVerticalScrollIndicator = false tableView.isScrollEnabled = false tableView.register(ScheduleRecordCell.self) return tableView }() lazy var nodataLab: UILabel = { let l = UILabel() l.text = "暂无行程记录" l.font = .systemFont(ofSize: 12, weight: .regular) l.textColor = UIColor(hexStr: "#999999") return l }() /// 滚动报告区域到顶部 func scrollReportToTop() { scrollView.setContentOffset(.zero, animated: false) } /// 滚到今天所在页(第 5 页,items 28-34,今天在第 3 位) func scrollToToday() { let page: CGFloat = 4 let offset = page * CGFloat(daysPerPage) * dateItemWidth dateCollectionView.contentOffset.x = offset } func updateTableViewHeight(count: Int) { let h = CGFloat(count) * 179 tableView.layoutChain.height(h > 0 ? h : 179) nodataLab.isHidden = h > 0 } override func layoutSubviews() { super.layoutSubviews() updateArrowVisibility() } override init(frame: CGRect) { super.init(frame: .zero) backgroundColor = .white setupUI() setupRx() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - UICollectionViewDataSource & Delegate (日期) extension GroupMemberListView: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { dateItems.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: DateCell = collectionView.dequeueReusableCell(for: indexPath) let item = dateItems[indexPath.row] let isSel = Calendar.current.isDate(item.date, inSameDayAs: selectedDate.value) cell.configure(item: item, isSelected: isSel) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard collectionView == dateCollectionView else { return } let item = dateItems[indexPath.row] guard item.isSelectable else { return } let oldDate = selectedDate.value selectedDate.accept(item.date) if let oldRow = dateItems.firstIndex(where: { Calendar.current.isDate($0.date, inSameDayAs: oldDate) }), let oldCell = dateCollectionView.cellForItem(at: IndexPath(row: oldRow, section: 0)) as? DateCell { oldCell.configure(item: dateItems[oldRow], isSelected: false) } if let newCell = dateCollectionView.cellForItem(at: indexPath) as? DateCell { newCell.configure(item: item, isSelected: true) } } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { guard scrollView == dateCollectionView else { return } let targetX = targetContentOffset.pointee.x let idx = round(targetX / dateItemWidth) targetContentOffset.pointee.x = idx * dateItemWidth } } // MARK: - DateCell class DateCell: UICollectionViewCell { private let bgView: UIView = { let v = UIView() v.backgroundColor = UIColor(hexStr: "#16B3FF") v.cornerRadius = 11 v.isHidden = true return v }() private let dayLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 14, weight: .medium) l.textAlignment = .center return l }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(bgView) contentView.addSubview(dayLab) bgView.layoutChain.centerX().centerY().width(36).height(22) dayLab.layoutChain.centerX().centerY() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(item: GroupMemberListView.DateItem, isSelected: Bool) { dayLab.text = "\(item.day)" let disabled = item.isFuture || !item.isSelectable bgView.isHidden = !isSelected || disabled dayLab.textColor = disabled ? UIColor(hexStr: "#D1D1D6") : isSelected ? .white : UIColor(hexStr: "#333333") } } // MARK: - GroupMemberListCell 成员 class GroupMemberListCell: UICollectionViewCell { func configure(model: GroupMemberModel, isCurrentUser: Bool, isSelected: Bool) { avaterImgView.image = model.userIcon vipIcon.image = model.vipIcon nameLab.text = model.nick_name nameLab.textColor = UIColor(hexStr: isCurrentUser ? "#16B3FF" : "#0F2846") selectedBgView.isHidden = !isSelected // 会员权益 if AppContextManager.shared.vip > 1, model.is_online { batteryInfoView.isHidden = model.battery.int == 0 // 电量 16是电池图标宽度,右边有电池造型需要减去 let batteryPercent = min(CGFloat(model.battery.int), 100) batteryView.layoutChain.width(CGFloat(16 - 1) * batteryPercent / 100.0) batteryLab.text = "\(model.battery)%" } } private func setupSubviews() { contentView.addSubview(selectedBgView) contentView.addSubview(avaterImgView) contentView.addSubview(vipIcon) contentView.addSubview(batteryInfoView) batteryInfoView.addSubview(cornerView) cornerView.addSubview(batteryView) cornerView.addSubview(batteryIcon) cornerView.addSubview(batteryLab) contentView.addSubview(nameLab) setupLayout() } private func setupLayout() { selectedBgView.layoutChain.edges() avaterImgView.layoutChain .top(11) .edgesHorzontal(10) .heightToWidth(1) batteryInfoView.layoutChain .leftToView(avaterImgView) .rightToView(avaterImgView) .bottomToView(avaterImgView) .height(12) cornerView.layoutChain.edges() batteryIcon.layoutChain .left(7) .centerY() .width(16) .height(8) batteryView.layoutChain .topToView(batteryIcon) .leftToView(batteryIcon, offset: -1) .bottomToView(batteryIcon) batteryLab.layoutChain .leftToRightOfView(batteryIcon, offset: 4) .right(5) .centerY() vipIcon.layoutChain .topToView(avaterImgView, offset: -8) .leftToView(avaterImgView, offset: -6) .width(25) .height(21) nameLab.layoutChain .topToBottomOfView(batteryInfoView, offset: 4) .edgesHorzontal() } lazy var selectedBgView: UIImageView = { let view = UIImageView(image: UIImage(named: "GroupMemberList/selected_bg")) view.contentMode = .scaleAspectFill view.isHidden = true return view }() lazy var avaterImgView: UIImageView = { let view = UIImageView() view.backgroundColor = .lightGray view.contentMode = .scaleAspectFill view.cornerRadius = 25 return view }() lazy var batteryInfoView: UIView = { let view = UIView() view.backgroundColor = .clear view.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1).cgColor view.layer.shadowOffset = CGSize(width: 0, height: 2) view.layer.shadowOpacity = 1 view.layer.shadowRadius = 6 view.isHidden = true return view }() lazy var cornerView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 6 return view }() lazy var batteryView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#75E582") return view }() lazy var batteryIcon: UIImageView = { let view = UIImageView() view.backgroundColor = .clear view.image = UIImage(named: "Home/battery") return view }() lazy var batteryLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#D4D4D4") label.font = .systemFont(ofSize: 6, weight: .medium) return label }() lazy var vipIcon: UIImageView = { let view = UIImageView() return view }() lazy var nameLab: UILabel = { let label = UILabel() label.textColor = UIColor(hexStr: "#0F2846") label.font = .systemFont(ofSize: 12, weight: .medium) label.textAlignment = .center return label }() override func awakeFromNib() { super.awakeFromNib() // Initialization code } override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - DrivingEventCell 驾驶事件 class DrivingEventCell: UICollectionViewCell { func configure(_ item: DrivingEventItem) { iconView.image = UIImage(named: item.iconName) nameLab.text = "\(item.title)\n(\(item.count))" } private let bgView: UIView = { let v = UIView() v.backgroundColor = UIColor(hexStr: "#F5FBFF") v.cornerRadius = 8 return v }() lazy var iconView: UIImageView = { let view = UIImageView() view.contentMode = .scaleAspectFill return view }() lazy var nameLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .regular) l.textAlignment = .center l.numberOfLines = 0 return l }() override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(bgView) contentView.addSubview(iconView) contentView.addSubview(nameLab) bgView.layoutChain .edges(excludingEdge: .top) .height(60) iconView.layoutChain .top() .centerX() .width(32) .heightToWidth(1) nameLab.layoutChain .topToBottomOfView(iconView, offset: 8) .edgesHorzontal() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // MARK: - ScheduleRecordCells class ScheduleRecordCell: UITableViewCell { func configure(_ model: ScheduleRecordModel) { distanceLab.text = String(format: "%.1fkm", model.distance_km) speedLab.text = String(format: "最高时速 %.0fkm/h", model.max_speed) scoreLab.text = model.score.string dayLab.text = model.start_time.isoStringToCustom(model.start_time, format: "dd") ?? "" yearMonthLab.text = model.start_time.isoStringToCustom(model.start_time, format: "yyyy.MM") ?? "" let startTime = model.start_time.isoStringToCustom(model.start_time, format: "HH:mm") ?? "" let endTime = model.end_time.isoStringToCustom(model.end_time, format: "HH:mm") ?? "" durationLab.text = "\(startTime)-\(endTime)(\(model.duration_minutes)分钟)" guard let start_address = model.start_address, let end_address = model.end_address else { return } startLab.text = start_address.street endLab.text = end_address.street } private func setupViews() { contentView.addSubview(dotView) contentView.addSubview(dayLab) contentView.addSubview(yearMonthLab) contentView.addSubview(distanceLab) contentView.addSubview(durationLab) contentView.addSubview(detailView) detailView.addSubview(headerBgView) detailView.addSubview(speedLab) detailView.addSubview(scroreTitleLab) detailView.addSubview(scoreLab) detailView.addSubview(unitLab) detailView.addSubview(startTitleLab) detailView.addSubview(startLab) detailView.addSubview(endTitleLab) detailView.addSubview(endLab) contentView.addSubview(lineView) dotView.layoutChain .left(15) .width(8) .heightToWidth(1) dayLab.layoutChain .top(15) .leftToRightOfView(dotView, offset: 8) dotView.layoutChain.centerY(dayLab) yearMonthLab.layoutChain .leftToRightOfView(dayLab, offset: 4) .bottomToView(dayLab, offset: -2) distanceLab.layoutChain .leftToRightOfView(yearMonthLab, offset: 8) .bottomToView(yearMonthLab) durationLab.layoutChain .centerY(distanceLab) .leftToRightOfView(distanceLab, offset: 6) detailView.layoutChain .topToBottomOfView(dayLab, offset: 15) .left(28) .right(15) .height(110) .bottom(15) headerBgView.layoutChain .edges(excludingEdge: .bottom) .height(38) speedLab.layoutChain .top(11) .left(15) unitLab.layoutChain .right(10) .centerY(speedLab) scoreLab.layoutChain .rightToLeftOfView(unitLab) .centerY(speedLab) scroreTitleLab.layoutChain .rightToLeftOfView(scoreLab, offset: -3) .centerY(speedLab) startTitleLab.layoutChain .topToBottomOfView(headerBgView, offset: 12) .left(15) startLab.layoutChain .leftToRightOfView(startTitleLab, offset: 2) .centerY(startTitleLab) endTitleLab.layoutChain .topToBottomOfView(startTitleLab, offset: 11) .leftToView(startTitleLab) endLab.layoutChain .leftToRightOfView(endTitleLab, offset: 2) .centerY(endTitleLab) lineView.layoutChain .height(0.5) .edgesHorzontal(15) .bottom() } lazy var dotView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#16B3FF") view.cornerRadius = 4 return view }() lazy var dayLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 20, weight: .bold) l.textColor = UIColor(hexStr: "#16B3FF") return l }() lazy var yearMonthLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .bold) l.textColor = UIColor(hexStr: "#66CDFF") return l }() lazy var distanceLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#333333") return l }() lazy var durationLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .regular) l.textColor = UIColor(hexStr: "#333333") return l }() lazy var detailView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#F5FBFF") view.cornerRadius = 10 view.clipsToBounds = true return view }() lazy var headerBgView: UIView = { let view = UIView() view.backgroundColor = UIColor(hexStr: "#C4E8FF") return view }() lazy var speedLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#333333") return l }() lazy var scroreTitleLab: UILabel = { let l = UILabel() l.text = "安全评分" l.font = .systemFont(ofSize: 10, weight: .medium) l.textColor = UIColor(hexStr: "#333333") return l }() lazy var scoreLab: UILabel = { let l = UILabel() l.text = "0" l.font = .systemFont(ofSize: 24, weight: .medium) l.textColor = UIColor(hexStr: "#16B3FF") return l }() lazy var unitLab: UILabel = { let l = UILabel() l.text = "分" l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#16B3FF") return l }() lazy var startTitleLab: UILabel = { let l = UILabel() l.text = "起点:" l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#666666") return l }() lazy var startLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#333333") return l }() lazy var endTitleLab: UILabel = { let l = UILabel() l.text = "终点:" l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#666666") return l }() lazy var endLab: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#333333") return l }() lazy var lineView: UIView = { let view = UIView() view.backgroundColor = ThemeManager.shared.color.lineColor return view }() override init(style: CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear setupViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } }