前言
做业务型 App 时,一个很常见的问题是:
- 首页有一套风险卡
- 结果页又有一套风险卡
- 首页有一套指标卡
- 结果页又有一套指标卡
短期看没什么问题,长期一定会出事。
因为只要设计稿改一次,你就得:
- 首页改一遍
- 结果页再改一遍
- 最后两边还不一定完全一致
所以这次我做了一个非常明确的决定:
只要视觉一致、数据兼容,就直接跨页面复用同一套卡片组件。
什么时候应该复用,什么时候不该硬复用
我现在判断一个 UI 要不要跨页面复用,主要看三点:
- 视觉是否真的一致
- 数据结构是否兼容
- 复用后是否会让边界更清晰,而不是更混乱
比如:
- 首页和结果页的风险卡视觉一样
- 首页和结果页的健康指标卡视觉一样
- 数据都能归一成统一结构
那就应该复用。
但像结果页顶部导航、总分卡这种页面专属结构,就不该为了“复用率”强行共用。
先把数据模型统一成展示层 DTO
复用的前提不是 view 写得多漂亮,而是数据先收敛。
比如风险卡完全可以统一成一个 DTO:
enum DisplayTone {
case positive
case warning
case caution
var valueColor: UIColor {
switch self {
case .positive: return UIColor(hex: "#20D292")
case .warning: return UIColor(hex: "#FF6A4A")
case .caution: return UIColor(hex: "#FFB020")
}
}
var badgeTextColor: UIColor { valueColor }
var badgeBackgroundColor: UIColor { valueColor.withAlphaComponent(0.08) }
}
struct RiskViewData {
let title: String
let valueText: String
let statusText: String
let descriptionText: String
let tone: DisplayTone
}
健康指标卡也一样:
struct MetricViewData {
let icon: UIImage?
let title: String
let valueText: String
let unitText: String
let statusText: String
let tone: DisplayTone
}
一旦展示层数据统一了,view 的复用就会简单很多。
风险卡组件怎么抽
风险卡通常包含这些稳定结构:
- 标题
- 数值
- 状态标签
- 解释文本
这种结构非常适合抽成统一组件:
final class RiskCardView: UIView {
private let titleLabel = UILabel()
private let valueLabel = UILabel()
private let statusLabel = PaddingLabel()
private let descriptionLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 16
layer.borderWidth = 1
layer.borderColor = UIColor(hex: "#F0EFF8").cgColor
[titleLabel, valueLabel, statusLabel, descriptionLabel].forEach(addSubview)
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
valueLabel.font = .systemFont(ofSize: 28, weight: .bold)
descriptionLabel.numberOfLines = 2
descriptionLabel.textColor = UIColor(hex: "#82828C")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with viewData: RiskViewData) {
titleLabel.text = viewData.title
valueLabel.text = viewData.valueText
statusLabel.text = viewData.statusText
descriptionLabel.text = viewData.descriptionText
statusLabel.textColor = viewData.tone.badgeTextColor
statusLabel.backgroundColor = viewData.tone.badgeBackgroundColor
valueLabel.textColor = viewData.tone.valueColor
}
}
这样首页和结果页只需要负责把各自的数据转成 RiskViewData,view 本身根本不需要知道“自己来自哪个页面”。
指标卡组件也一样
健康指标卡的稳定结构通常是:
- 小图标
- 标题
- 数值
- 单位
- 状态标签
示例:
final class MetricCardView: UIView {
private let iconView = UIImageView()
private let titleLabel = UILabel()
private let valueLabel = UILabel()
private let unitLabel = UILabel()
private let statusLabel = PaddingLabel()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
layer.cornerRadius = 16
layer.borderWidth = 1
layer.borderColor = UIColor(hex: "#F0EFF8").cgColor
[iconView, titleLabel, valueLabel, unitLabel, statusLabel].forEach(addSubview)
titleLabel.numberOfLines = 2
titleLabel.font = .systemFont(ofSize: 12, weight: .bold)
valueLabel.font = .systemFont(ofSize: 24, weight: .bold)
unitLabel.textColor = UIColor(hex: "#8C8B97")
statusLabel.layer.cornerRadius = 10
statusLabel.layer.masksToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with viewData: MetricViewData) {
iconView.image = viewData.icon
titleLabel.text = viewData.title
valueLabel.text = viewData.valueText
unitLabel.text = viewData.unitText
statusLabel.text = viewData.statusText
statusLabel.textColor = viewData.tone.badgeTextColor
statusLabel.backgroundColor = viewData.tone.badgeBackgroundColor
}
}
页面容器不同,卡片组件相同
这是我这次最核心的处理方式。
也就是说:
- 首页容器负责首页结构
- 结果页容器负责结果页结构
- 但风险卡和指标卡本身,两边直接共用
这样做最大的收益有两个:
第一,视觉统一。
第二,后续改样式只改一处。
如果以后 badge 样式改了、圆角改了、文案区间距改了,你不会再面临“首页改完,结果页忘了改”的问题。
复用时一个很重要的边界:别把页面专属结构也硬塞进去
虽然风险卡和指标卡很适合复用,但不是所有结果页元素都该复用。
比如下面这些更适合单独做:
- 结果页顶部导航
- PDF 按钮
- 总分环形卡
- 首页 Hero 区
- 首页进度卡
原因很简单:
这些不是“通用卡片”,而是“页面专属结构”。
如果为了复用而把这些东西都塞进一个超级组件,最后只会让代码越来越难维护。
总结
真正有价值的组件复用,不是为了少写几个 view,而是为了让:
- 同一类 UI 在多个页面里长期保持一致
- 后续设计改动只改一处
- 页面结构和卡片结构边界更清晰
如果你也在做类似的首页/结果页双页面改造,我建议你先问自己三个问题:
- 视觉是否真的一致?
- 数据结构是否能统一?
- 复用后边界是否更清晰?
如果答案都是“是”,那就果断复用。
一句话总结:
组件复用真正的价值,不是省代码,而是长期降低样式分叉和维护成本。