From 87696a4774e750373aa1dab311bc70dd3fc2379b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=89=E7=A7=8B?= <497055328@qq.com> Date: Mon, 6 Jul 2026 17:58:18 +0800 Subject: [PATCH] Introduce unified design tokens and migrate Home, Orders, and Statistics UI. Establish AppColor/AppFont/AppSpacing/AppRadius theming, shared components, and design-system docs so pilot screens align with Android while keeping iOS-native spacing and touch targets. Co-authored-by: Cursor --- design-system/随心瞰商家版/MASTER.md | 128 ++++++++++++++++++ design-system/随心瞰商家版/pages/home.md | 24 ++++ suixinkan/Base/BaseViewController.swift | 6 +- suixinkan/SceneDelegate.swift | 2 + suixinkan/Theme/AppColor.swift | 57 ++++++-- suixinkan/Theme/AppFont.swift | 54 ++++++++ .../Theme/AppNavigationBarAppearance.swift | 28 ++++ suixinkan/Theme/AppRadius.swift | 20 +++ suixinkan/Theme/AppShadow.swift | 18 +++ suixinkan/Theme/AppSpacing.swift | 33 +++++ suixinkan/UI/Common/AppButton.swift | 6 +- suixinkan/UI/Common/AppCardView.swift | 44 ++++++ suixinkan/UI/Common/AppStatusBadge.swift | 82 +++++++++++ suixinkan/UI/Home/HomeViewController.swift | 10 +- .../Home/Views/HomeCommonAppsGridView.swift | 16 +-- .../Views/HomeLocationReportCardView.swift | 30 ++-- .../UI/Home/Views/HomeQuickActionsView.swift | 10 +- .../UI/Home/Views/HomeScenicHeaderView.swift | 14 +- .../UI/Home/Views/HomeStoreCardView.swift | 28 ++-- .../Home/Views/HomeWorkStatusCardView.swift | 26 ++-- .../UI/MainTab/MainTabBarController.swift | 10 +- suixinkan/UI/Orders/OrderCardCell.swift | 66 ++++----- .../UI/Orders/OrderStatusBadgeView.swift | 31 +---- .../UI/Orders/OrdersViewController.swift | 1 - .../UI/Profile/ProfileStatusBadgeView.swift | 32 ++--- .../Statistics/StatisticsDailyListView.swift | 20 +-- .../Statistics/StatisticsPeriodButton.swift | 8 +- .../Statistics/StatisticsStatCardView.swift | 18 +-- .../StatisticsSummaryCardView.swift | 36 ++--- .../Statistics/StatisticsViewController.swift | 12 +- suixinkanTests/AppThemeTokensTests.swift | 80 +++++++++++ 31 files changed, 737 insertions(+), 213 deletions(-) create mode 100644 design-system/随心瞰商家版/MASTER.md create mode 100644 design-system/随心瞰商家版/pages/home.md create mode 100644 suixinkan/Theme/AppFont.swift create mode 100644 suixinkan/Theme/AppNavigationBarAppearance.swift create mode 100644 suixinkan/Theme/AppRadius.swift create mode 100644 suixinkan/Theme/AppShadow.swift create mode 100644 suixinkan/Theme/AppSpacing.swift create mode 100644 suixinkan/UI/Common/AppCardView.swift create mode 100644 suixinkan/UI/Common/AppStatusBadge.swift create mode 100644 suixinkanTests/AppThemeTokensTests.swift diff --git a/design-system/随心瞰商家版/MASTER.md b/design-system/随心瞰商家版/MASTER.md new file mode 100644 index 0000000..fcbeece --- /dev/null +++ b/design-system/随心瞰商家版/MASTER.md @@ -0,0 +1,128 @@ +# 随心瞰商家版 — Design System (MASTER) + +> Global Source of Truth。页面级 override 见 `pages/[page].md`。 + +## 设计定位 + +| 维度 | 决策 | +|------|------| +| 产品 | B2B 户外作业工具(景区商户 / 摄影师 / 飞手) | +| 风格 | Flat Design + Executive Dashboard 卡片布局 | +| 字体 | SF Pro(System),不引入 Web 字体 | +| 主色 | `#0073FF`;Banner `#1677FF` | +| 触控 | 最小点击区域 44×44pt | +| 对比度 | 正文 ≥ 4.5:1 | + +**不采用:** 渐变/Aurora 背景、Newsletter 布局、替换品牌主色。 + +## 色彩 Token(`AppColor`) + +| Token | Hex | 用途 | +|-------|-----|------| +| `primary` | `#0073FF` | Tab 选中、主按钮、链接 | +| `primaryBanner` | `#1677FF` | 排队 Banner | +| `primaryLight` | `#EFF6FF` | 次要按钮底、info badge | +| `pageBackground` | `#F5F5F5` | 默认 Tab 页背景 | +| `pageBackgroundSoft` | `#F7FAFF` | 数据 / 个人中心 | +| `cardBackground` | `#FFFFFF` | 卡片、表单 | +| `inputBackground` | `#F4F4F4` | 输入框、离线 chip | +| `border` | `#EEEEEE` | 分割线 | +| `cardOutline` | `#E9ECEF` | 列表卡片描边 | +| `textPrimary` | `#333333` | 主文案 | +| `textSecondary` | `#666666` | 副文案 | +| `textTertiary` | `#999999` | 弱提示 | +| `textTabInactive` | `#7D8DA3` | Tab 未选中 | +| `success` / `successBackground` | `#10B981` / `#F0FDF4` | 成功、待叫号 | +| `warning` / `warningBackground` | `#FF7B00` / `#FFF0E2` | 待处理 | +| `danger` / `dangerBackground` | `#EF4444` / `#FFE7E7` | 错误、过号 | +| `info` / `infoBackground` | `#0073FF` / `#E8F4FF` | 进行中 | +| `online` | `#22C55E` | 在线 pill | +| `overlayScrim` | `#00000033` | Loading 遮罩 | + +**规则:** 业务 View 禁止 `UIColor(hex:)`,一律引用 `AppColor`。 + +## 字体 Token(`AppFont`) + +| Token | Size | Weight | 场景 | +|-------|------|--------|------| +| `caption` | 12 | regular | Tab、协议 | +| `captionMedium` | 12 | medium | Tab 选中 | +| `body` | 14 | regular | 按钮、列表 | +| `bodyMedium` | 14 | medium | Badge、chip | +| `subtitle` | 16 | medium | 卡片标题 | +| `title` | 16 | bold | Section 标题 | +| `metric` | 18 | bold | 统计数值 | +| `display` | 28 | semibold | 登录欢迎 | + +## 间距 Token(`AppSpacing`) + +| Token | pt | +|-------|-----| +| `xxs` | 4 | +| `xs` | 8 | +| `sm` | 12 | +| `md` | 16 | +| `lg` | 24 | +| `xl` | 32 | +| `xxl` | 48 | + +常量:`screenHorizontalInset = 16`,`minTouchTarget = 44`,`homeHeaderHeight = 52`。 + +## 圆角 Token(`AppRadius`) + +| Token | pt | 场景 | +|-------|-----|------| +| `xs` | 4 | Badge | +| `sm` | 8 | 小按钮 | +| `md` | 10 | 默认卡片、AppButton | +| `lg` | 12 | 输入框、统计卡 | +| `xl` | 16 | 登录表单、Dialog | +| `sheetTop` | 14 | 内容区顶圆角 | + +## 阴影(`AppShadow`) + +仅登录表单卡等浮层使用;普通卡片 **flat + 可选 1px `cardOutline` 描边**。 + +## 共享组件 + +| 组件 | 路径 | 说明 | +|------|------|------| +| `AppCardView` | `UI/Common/AppCardView.swift` | 白底卡片容器 | +| `AppStatusBadge` | `UI/Common/AppStatusBadge.swift` | 统一状态标签 | +| `AppButton` | `UI/Common/AppButton.swift` | 主/次按钮 | +| `AppNavigationBarAppearance` | `Theme/AppNavigationBarAppearance.swift` | 全局 NavBar | + +## 页面模板(Tab 内页) + +``` +NavigationBar(白底,16pt title)— Home 例外:自定义 Header +pageBackground + AppCardView × N(inset 16,stack gap 12) +TabBar(白底,primary tint) +``` + +## 反馈模式 + +| 场景 | 方式 | +|------|------| +| 提交 / 首次加载 | GlobalLoading | +| 列表刷新 | UIRefreshControl | +| 分页 | Footer UIActivityIndicatorView | +| 操作结果 | Toast | +| destructive | Alert | + +## Pre-Delivery Checklist + +- [ ] 无 inline hex,使用 AppColor +- [ ] 触控区域 ≥ 44pt +- [ ] 正文对比度 ≥ 4.5:1 +- [ ] 异步操作 >300ms 有 loading 反馈 +- [ ] 状态不仅靠颜色(含文字标签) +- [ ] 圆角 / 间距来自 AppRadius / AppSpacing + +## 路线图(后续) + +1. Profile / Login / Auth 迁移 +2. 排队管理(`Theme/Modules/ScenicQueueTokens.swift`) +3. 全工程 hex 清零 +4. Orders EmptyState、FilterBar 组件化 diff --git a/design-system/随心瞰商家版/pages/home.md b/design-system/随心瞰商家版/pages/home.md new file mode 100644 index 0000000..4d6f6d0 --- /dev/null +++ b/design-system/随心瞰商家版/pages/home.md @@ -0,0 +1,24 @@ +# Home 页面 Override + +覆盖 MASTER 的以下规则: + +## Navigation + +- 隐藏系统 `UINavigationBar` +- 使用 `HomeScenicHeaderView`(高度 `AppSpacing.homeHeaderHeight` = 52pt) + +## Background + +- `AppColor.pageBackground`(`#F5F5F5`) + +## Layout + +- `UIScrollView` + 垂直 `UIStackView` +- 水平 inset:`AppSpacing.screenHorizontalInset`(16pt) +- 卡片间距:`AppSpacing.sm`(12pt) +- 卡片圆角:`AppRadius.md`(10pt) + +## Cards + +- `HomeWorkStatusCardView`:在线 `AppColor.online`,离线 `AppColor.inputBackground` +- 快捷操作、应用网格等同 Home 卡片规范 diff --git a/suixinkan/Base/BaseViewController.swift b/suixinkan/Base/BaseViewController.swift index 4b20aef..66a47d7 100644 --- a/suixinkan/Base/BaseViewController.swift +++ b/suixinkan/Base/BaseViewController.swift @@ -13,7 +13,7 @@ class BaseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .systemBackground + view.backgroundColor = AppColor.pageBackground setupNavigationBar() setupUI() setupConstraints() @@ -61,14 +61,14 @@ class BaseViewController: UIViewController { let container = UIView() container.backgroundColor = UIColor.black.withAlphaComponent(0.8) - container.layer.cornerRadius = 8 + container.layer.cornerRadius = AppRadius.sm container.clipsToBounds = true container.alpha = 0 let label = UILabel() label.text = message label.textColor = .white - label.font = .systemFont(ofSize: 14) + label.font = .app(.body) label.textAlignment = .center label.numberOfLines = 0 diff --git a/suixinkan/SceneDelegate.swift b/suixinkan/SceneDelegate.swift index 8b0a23b..9c0ef58 100644 --- a/suixinkan/SceneDelegate.swift +++ b/suixinkan/SceneDelegate.swift @@ -16,6 +16,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) { guard let windowScene = scene as? UIWindowScene else { return } + AppNavigationBarAppearance.applyGlobalAppearance() + let window = UIWindow(windowScene: windowScene) window.rootViewController = AppRouter.makeRootViewController() window.makeKeyAndVisible() diff --git a/suixinkan/Theme/AppColor.swift b/suixinkan/Theme/AppColor.swift index 9e7de15..158d321 100644 --- a/suixinkan/Theme/AppColor.swift +++ b/suixinkan/Theme/AppColor.swift @@ -5,15 +5,56 @@ import UIKit -/// 应用主题色,与 Android `theme/Color.kt` 对齐。 +/// 应用语义色,与 Android `theme/Color.kt` 及 `ScenicQueueTokens` 对齐。 enum AppColor { - static let primary = UIColor(red: 0 / 255, green: 115 / 255, blue: 255 / 255, alpha: 1) - static let text333 = UIColor(red: 51 / 255, green: 51 / 255, blue: 51 / 255, alpha: 1) - static let text666 = UIColor(red: 102 / 255, green: 102 / 255, blue: 102 / 255, alpha: 1) - static let agreementLink = UIColor(red: 32 / 255, green: 139 / 255, blue: 255 / 255, alpha: 1) - static let inputBackground = UIColor(red: 244 / 255, green: 244 / 255, blue: 244 / 255, alpha: 1) - static let secondaryButtonBackground = UIColor(red: 239 / 255, green: 246 / 255, blue: 255 / 255, alpha: 1) - static let buttonDisabled = UIColor(red: 192 / 255, green: 192 / 255, blue: 192 / 255, alpha: 1) + // MARK: - Brand + + static let primary = UIColor(hex: 0x0073FF) + static let primaryBanner = UIColor(hex: 0x1677FF) + static let primaryLight = UIColor(hex: 0xEFF6FF) + + // MARK: - Background + + static let pageBackground = UIColor(hex: 0xF5F5F5) + static let pageBackgroundSoft = UIColor(hex: 0xF7FAFF) + static let cardBackground = UIColor.white + static let inputBackground = UIColor(hex: 0xF4F4F4) + + // MARK: - Border + + static let border = UIColor(hex: 0xEEEEEE) + static let cardOutline = UIColor(hex: 0xE9ECEF) + + // MARK: - Text + + static let textPrimary = UIColor(hex: 0x333333) + static let textSecondary = UIColor(hex: 0x666666) + static let textTertiary = UIColor(hex: 0x999999) + static let textTabInactive = UIColor(hex: 0x7D8DA3) + + // MARK: - Semantic + + static let success = UIColor(hex: 0x10B981) + static let successBackground = UIColor(hex: 0xF0FDF4) + static let warning = UIColor(hex: 0xFF7B00) + static let warningBackground = UIColor(hex: 0xFFF0E2) + static let danger = UIColor(hex: 0xEF4444) + static let dangerBackground = UIColor(hex: 0xFFE7E7) + static let info = UIColor(hex: 0x0073FF) + static let infoBackground = UIColor(hex: 0xE8F4FF) + static let online = UIColor(hex: 0x22C55E) + + // MARK: - Overlay + + static let overlayScrim = UIColor.black.withAlphaComponent(0.2) + + // MARK: - Legacy aliases(逐步迁移后移除) + + static let text333 = textPrimary + static let text666 = textSecondary + static let agreementLink = UIColor(hex: 0x208BFF) + static let secondaryButtonBackground = primaryLight + static let buttonDisabled = UIColor(hex: 0xC0C0C0) static let placeholder = UIColor.gray } diff --git a/suixinkan/Theme/AppFont.swift b/suixinkan/Theme/AppFont.swift new file mode 100644 index 0000000..4c2c604 --- /dev/null +++ b/suixinkan/Theme/AppFont.swift @@ -0,0 +1,54 @@ +// +// AppFont.swift +// suixinkan +// + +import UIKit + +/// 应用字体层级,基于 8pt 网格与 SF Pro(System)。 +enum AppFont { + + case caption + case captionMedium + case body + case bodyMedium + case subtitle + case title + case metric + case display + + /// 对应 UIFont。 + var uiFont: UIFont { + switch self { + case .caption: + return .systemFont(ofSize: 12, weight: .regular) + case .captionMedium: + return .systemFont(ofSize: 12, weight: .medium) + case .body: + return .systemFont(ofSize: 14, weight: .regular) + case .bodyMedium: + return .systemFont(ofSize: 14, weight: .medium) + case .subtitle: + return .systemFont(ofSize: 16, weight: .medium) + case .title: + return .systemFont(ofSize: 16, weight: .bold) + case .metric: + return .systemFont(ofSize: 18, weight: .bold) + case .display: + return .systemFont(ofSize: 28, weight: .semibold) + } + } + + /// 将字体应用到 UILabel。 + func apply(to label: UILabel) { + label.font = uiFont + } +} + +extension UIFont { + + /// 通过 `AppFont` 获取系统字体。 + static func app(_ style: AppFont) -> UIFont { + style.uiFont + } +} diff --git a/suixinkan/Theme/AppNavigationBarAppearance.swift b/suixinkan/Theme/AppNavigationBarAppearance.swift new file mode 100644 index 0000000..3d231dc --- /dev/null +++ b/suixinkan/Theme/AppNavigationBarAppearance.swift @@ -0,0 +1,28 @@ +// +// AppNavigationBarAppearance.swift +// suixinkan +// + +import UIKit + +/// 全局 UINavigationBar 外观配置。 +enum AppNavigationBarAppearance { + + /// 应用标准导航栏样式(白底、无 shadow、主色标题)。 + static func applyGlobalAppearance() { + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = AppColor.cardBackground + appearance.shadowColor = .clear + appearance.titleTextAttributes = [ + .foregroundColor: AppColor.textPrimary, + .font: UIFont.app(.title), + ] + + let navigationBar = UINavigationBar.appearance() + navigationBar.standardAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + navigationBar.compactAppearance = appearance + navigationBar.tintColor = AppColor.primary + } +} diff --git a/suixinkan/Theme/AppRadius.swift b/suixinkan/Theme/AppRadius.swift new file mode 100644 index 0000000..0021494 --- /dev/null +++ b/suixinkan/Theme/AppRadius.swift @@ -0,0 +1,20 @@ +// +// AppRadius.swift +// suixinkan +// + +import CoreGraphics + +/// 应用圆角常量,与 Android ScenicQueueTokens 近似对齐。 +enum AppRadius { + + static let xs: CGFloat = 4 + static let sm: CGFloat = 8 + static let md: CGFloat = 10 + static let lg: CGFloat = 12 + static let xl: CGFloat = 16 + static let sheetTop: CGFloat = 14 + + /// 周期筛选 pill(Statistics)。 + static let pill: CGFloat = 18 +} diff --git a/suixinkan/Theme/AppShadow.swift b/suixinkan/Theme/AppShadow.swift new file mode 100644 index 0000000..afa26bc --- /dev/null +++ b/suixinkan/Theme/AppShadow.swift @@ -0,0 +1,18 @@ +// +// AppShadow.swift +// suixinkan +// + +import UIKit + +/// 轻量阴影,仅用于登录表单等浮层卡片。 +enum AppShadow { + + /// 为 layer 应用标准卡片阴影(登录表单等)。 + static func applyCardShadow(to layer: CALayer) { + layer.shadowColor = UIColor.black.cgColor + layer.shadowOpacity = 0.08 + layer.shadowOffset = CGSize(width: 0, height: 4) + layer.shadowRadius = 12 + } +} diff --git a/suixinkan/Theme/AppSpacing.swift b/suixinkan/Theme/AppSpacing.swift new file mode 100644 index 0000000..13c315d --- /dev/null +++ b/suixinkan/Theme/AppSpacing.swift @@ -0,0 +1,33 @@ +// +// AppSpacing.swift +// suixinkan +// + +import CoreGraphics + +/// 应用间距常量,基于 8pt 网格。 +enum AppSpacing { + + static let xxs: CGFloat = 4 + static let xs: CGFloat = 8 + static let sm: CGFloat = 12 + static let md: CGFloat = 16 + static let lg: CGFloat = 24 + static let xl: CGFloat = 32 + static let xxl: CGFloat = 48 + + /// 屏幕水平内边距。 + static let screenHorizontalInset: CGFloat = md + + /// 最小可点击区域(Apple HIG)。 + static let minTouchTarget: CGFloat = 44 + + /// 首页自定义 Header 高度。 + static let homeHeaderHeight: CGFloat = 52 + + /// 主按钮默认高度。 + static let buttonHeight: CGFloat = 46 + + /// 表单行默认高度。 + static let formRowHeight: CGFloat = 52 +} diff --git a/suixinkan/UI/Common/AppButton.swift b/suixinkan/UI/Common/AppButton.swift index f70af10..65a5c67 100644 --- a/suixinkan/UI/Common/AppButton.swift +++ b/suixinkan/UI/Common/AppButton.swift @@ -32,14 +32,14 @@ final class AppButton: UIButton { } private func setupUI(title: String) { - titleLabel?.font = .systemFont(ofSize: 14, weight: .regular) - layer.cornerRadius = 10 + titleLabel?.font = .app(.body) + layer.cornerRadius = AppRadius.md clipsToBounds = true setTitle(title, for: .normal) updateAppearance() snp.makeConstraints { make in - make.height.equalTo(46) + make.height.equalTo(AppSpacing.buttonHeight) } } diff --git a/suixinkan/UI/Common/AppCardView.swift b/suixinkan/UI/Common/AppCardView.swift new file mode 100644 index 0000000..94bf9ec --- /dev/null +++ b/suixinkan/UI/Common/AppCardView.swift @@ -0,0 +1,44 @@ +// +// AppCardView.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 标准白底卡片容器,统一圆角与可选描边。 +final class AppCardView: UIView { + + /// 卡片内容区,子视图应添加到此 container。 + let contentView = UIView() + + private let showsBorder: Bool + + /// - Parameter showsBorder: 是否显示 1px 描边(默认 false,flat 风格)。 + init(showsBorder: Bool = false) { + self.showsBorder = showsBorder + super.init(frame: .zero) + setupUI() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + backgroundColor = AppColor.cardBackground + layer.cornerRadius = AppRadius.md + clipsToBounds = true + + if showsBorder { + layer.borderWidth = 1 + layer.borderColor = AppColor.cardOutline.cgColor + } + + addSubview(contentView) + contentView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} diff --git a/suixinkan/UI/Common/AppStatusBadge.swift b/suixinkan/UI/Common/AppStatusBadge.swift new file mode 100644 index 0000000..a723246 --- /dev/null +++ b/suixinkan/UI/Common/AppStatusBadge.swift @@ -0,0 +1,82 @@ +// +// AppStatusBadge.swift +// suixinkan +// + +import SnapKit +import UIKit + +/// 统一状态标签,合并订单 / 个人信息等 badge 配色逻辑。 +final class AppStatusBadge: UIView { + + /// 语义色调。 + enum Tone { + case info + case success + case warning + case danger + case neutral + } + + private let label = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + AppFont.captionMedium.apply(to: label) + label.textAlignment = .center + addSubview(label) + label.snp.makeConstraints { make in + make.edges.equalToSuperview().inset( + UIEdgeInsets( + top: AppSpacing.xxs, + left: AppSpacing.xs, + bottom: AppSpacing.xxs, + right: AppSpacing.xs + ) + ) + } + layer.cornerRadius = AppRadius.xs + clipsToBounds = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// 按语义色调与文案刷新展示。 + func apply(tone: Tone, text: String) { + label.text = text + switch tone { + case .info: + label.textColor = AppColor.info + backgroundColor = AppColor.infoBackground + case .success: + label.textColor = AppColor.success + backgroundColor = AppColor.successBackground + case .warning: + label.textColor = AppColor.warning + backgroundColor = AppColor.warningBackground + case .danger: + label.textColor = AppColor.danger + backgroundColor = AppColor.dangerBackground + case .neutral: + label.textColor = AppColor.textTabInactive + backgroundColor = AppColor.inputBackground + } + } + + /// 订单状态映射(对齐 Android OrderView)。 + func applyOrderStatus(_ status: Int, text: String) { + switch status { + case 18, 20: + apply(tone: .info, text: text) + case 30: + apply(tone: .success, text: text) + case 50: + apply(tone: .danger, text: text) + default: + apply(tone: .warning, text: text) + } + } +} diff --git a/suixinkan/UI/Home/HomeViewController.swift b/suixinkan/UI/Home/HomeViewController.swift index 80445bb..7ba29d9 100644 --- a/suixinkan/UI/Home/HomeViewController.swift +++ b/suixinkan/UI/Home/HomeViewController.swift @@ -30,10 +30,10 @@ final class HomeViewController: BaseViewController { } override func setupUI() { - view.backgroundColor = UIColor(hex: 0xF5F5F5) + view.backgroundColor = AppColor.pageBackground contentStack.axis = .vertical - contentStack.spacing = 12 + contentStack.spacing = AppSpacing.sm scrollView.showsVerticalScrollIndicator = false view.addSubview(scenicHeaderView) @@ -51,15 +51,15 @@ final class HomeViewController: BaseViewController { scenicHeaderView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.leading.trailing.equalToSuperview() - make.height.equalTo(52) + make.height.equalTo(AppSpacing.homeHeaderHeight) } scrollView.snp.makeConstraints { make in make.top.equalTo(scenicHeaderView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } contentStack.snp.makeConstraints { make in - make.edges.equalToSuperview().inset(16) - make.width.equalTo(scrollView.snp.width).offset(-32) + make.edges.equalToSuperview().inset(AppSpacing.screenHorizontalInset) + make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.screenHorizontalInset * 2) } } diff --git a/suixinkan/UI/Home/Views/HomeCommonAppsGridView.swift b/suixinkan/UI/Home/Views/HomeCommonAppsGridView.swift index 5630116..c67fc29 100644 --- a/suixinkan/UI/Home/Views/HomeCommonAppsGridView.swift +++ b/suixinkan/UI/Home/Views/HomeCommonAppsGridView.swift @@ -47,8 +47,8 @@ final class HomeCommonAppsGridView: UIView { override init(frame: CGRect) { super.init(frame: frame) titleLabel.text = "常用应用" - titleLabel.font = .systemFont(ofSize: 16, weight: .bold) - titleLabel.textColor = AppColor.text333 + titleLabel.font = .app(.title) + titleLabel.textColor = AppColor.textPrimary collectionView.backgroundColor = .clear collectionView.delegate = self @@ -61,7 +61,7 @@ final class HomeCommonAppsGridView: UIView { make.top.leading.trailing.equalToSuperview() } collectionView.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(12) + make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.sm) make.leading.trailing.bottom.equalToSuperview() make.height.equalTo(88) } @@ -101,8 +101,8 @@ final class HomeMenuCell: UICollectionViewCell { iconView.tintColor = AppColor.primary iconView.contentMode = .scaleAspectFit - titleLabel.font = .systemFont(ofSize: 12) - titleLabel.textColor = AppColor.text333 + titleLabel.font = .app(.caption) + titleLabel.textColor = AppColor.textPrimary titleLabel.textAlignment = .center titleLabel.numberOfLines = 2 @@ -110,13 +110,13 @@ final class HomeMenuCell: UICollectionViewCell { contentView.addSubview(titleLabel) iconView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(8) + make.top.equalToSuperview().offset(AppSpacing.xs) make.centerX.equalToSuperview() make.width.height.equalTo(28) } titleLabel.snp.makeConstraints { make in - make.top.equalTo(iconView.snp.bottom).offset(8) - make.leading.trailing.equalToSuperview().inset(4) + make.top.equalTo(iconView.snp.bottom).offset(AppSpacing.xs) + make.leading.trailing.equalToSuperview().inset(AppSpacing.xxs) } } diff --git a/suixinkan/UI/Home/Views/HomeLocationReportCardView.swift b/suixinkan/UI/Home/Views/HomeLocationReportCardView.swift index 57693f4..1c5389e 100644 --- a/suixinkan/UI/Home/Views/HomeLocationReportCardView.swift +++ b/suixinkan/UI/Home/Views/HomeLocationReportCardView.swift @@ -17,23 +17,23 @@ final class HomeLocationReportCardView: UIView { override init(frame: CGRect) { super.init(frame: frame) - backgroundColor = .white - layer.cornerRadius = 10 + backgroundColor = AppColor.cardBackground + layer.cornerRadius = AppRadius.md titleLabel.text = "位置上报" - titleLabel.font = .systemFont(ofSize: 16, weight: .semibold) - titleLabel.textColor = AppColor.text333 + titleLabel.font = .app(.title) + titleLabel.textColor = AppColor.textPrimary - addressLabel.font = .systemFont(ofSize: 13) - addressLabel.textColor = AppColor.text666 + addressLabel.font = .app(.body) + addressLabel.textColor = AppColor.textSecondary addressLabel.numberOfLines = 2 reportButton.setTitle("立即上报", for: .normal) reportButton.setTitleColor(.white, for: .normal) reportButton.backgroundColor = AppColor.primary - reportButton.layer.cornerRadius = 6 - reportButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium) - reportButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 14, bottom: 8, right: 14) + reportButton.layer.cornerRadius = AppRadius.sm + reportButton.titleLabel?.font = .app(.bodyMedium) + reportButton.contentEdgeInsets = UIEdgeInsets(top: AppSpacing.xs, left: 14, bottom: AppSpacing.xs, right: 14) reportButton.addTarget(self, action: #selector(reportTapped), for: .touchUpInside) addSubview(titleLabel) @@ -41,16 +41,16 @@ final class HomeLocationReportCardView: UIView { addSubview(reportButton) titleLabel.snp.makeConstraints { make in - make.top.leading.equalToSuperview().offset(15) + make.top.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset) } addressLabel.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(8) - make.leading.equalToSuperview().offset(15) - make.bottom.equalToSuperview().offset(-15) - make.trailing.lessThanOrEqualTo(reportButton.snp.leading).offset(-12) + make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.xs) + make.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset) + make.bottom.equalToSuperview().offset(-AppSpacing.screenHorizontalInset) + make.trailing.lessThanOrEqualTo(reportButton.snp.leading).offset(-AppSpacing.sm) } reportButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().offset(-15) + make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset) make.centerY.equalToSuperview() } } diff --git a/suixinkan/UI/Home/Views/HomeQuickActionsView.swift b/suixinkan/UI/Home/Views/HomeQuickActionsView.swift index 8f2858d..ccaee36 100644 --- a/suixinkan/UI/Home/Views/HomeQuickActionsView.swift +++ b/suixinkan/UI/Home/Views/HomeQuickActionsView.swift @@ -19,7 +19,7 @@ final class HomeQuickActionsView: UIView { super.init(frame: frame) stackView.axis = .horizontal stackView.distribution = .fillEqually - stackView.spacing = 12 + stackView.spacing = AppSpacing.sm addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() @@ -45,8 +45,8 @@ final class HomeQuickActionsView: UIView { private func makeActionButton(title: String, icon: String, action: Selector) -> UIView { let container = UIView() - container.backgroundColor = .white - container.layer.cornerRadius = 10 + container.backgroundColor = AppColor.cardBackground + container.layer.cornerRadius = AppRadius.md let button = UIButton(type: .system) button.addTarget(self, action: action, for: .touchUpInside) @@ -61,8 +61,8 @@ final class HomeQuickActionsView: UIView { let label = UILabel() label.text = title - label.font = .systemFont(ofSize: 13, weight: .medium) - label.textColor = AppColor.text333 + label.font = .app(.bodyMedium) + label.textColor = AppColor.textPrimary label.textAlignment = .center let column = UIStackView(arrangedSubviews: [imageView, label]) diff --git a/suixinkan/UI/Home/Views/HomeScenicHeaderView.swift b/suixinkan/UI/Home/Views/HomeScenicHeaderView.swift index 7fab09f..d448830 100644 --- a/suixinkan/UI/Home/Views/HomeScenicHeaderView.swift +++ b/suixinkan/UI/Home/Views/HomeScenicHeaderView.swift @@ -16,22 +16,22 @@ final class HomeScenicHeaderView: UIView { override init(frame: CGRect) { super.init(frame: frame) - backgroundColor = .white - titleLabel.font = .systemFont(ofSize: 16, weight: .medium) - titleLabel.textColor = AppColor.text333 - arrowView.tintColor = AppColor.text666 + backgroundColor = AppColor.cardBackground + titleLabel.font = .app(.subtitle) + titleLabel.textColor = AppColor.textPrimary + arrowView.tintColor = AppColor.textSecondary arrowView.contentMode = .scaleAspectFit addSubview(titleLabel) addSubview(arrowView) titleLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(16) + make.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset) make.centerY.equalToSuperview() - make.trailing.lessThanOrEqualTo(arrowView.snp.leading).offset(-8) + make.trailing.lessThanOrEqualTo(arrowView.snp.leading).offset(-AppSpacing.xs) } arrowView.snp.makeConstraints { make in - make.trailing.equalToSuperview().offset(-16) + make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset) make.centerY.equalToSuperview() make.width.height.equalTo(16) } diff --git a/suixinkan/UI/Home/Views/HomeStoreCardView.swift b/suixinkan/UI/Home/Views/HomeStoreCardView.swift index a756b79..625b0b1 100644 --- a/suixinkan/UI/Home/Views/HomeStoreCardView.swift +++ b/suixinkan/UI/Home/Views/HomeStoreCardView.swift @@ -15,17 +15,17 @@ final class HomeStoreCardView: UIView { override init(frame: CGRect) { super.init(frame: frame) - backgroundColor = .white - layer.cornerRadius = 10 + backgroundColor = AppColor.cardBackground + layer.cornerRadius = AppRadius.md - nameLabel.font = .systemFont(ofSize: 16, weight: .semibold) - nameLabel.textColor = AppColor.text333 + nameLabel.font = .app(.title) + nameLabel.textColor = AppColor.textPrimary - addressLabel.font = .systemFont(ofSize: 13) - addressLabel.textColor = AppColor.text666 + addressLabel.font = .app(.body) + addressLabel.textColor = AppColor.textSecondary addressLabel.numberOfLines = 2 - statusLabel.font = .systemFont(ofSize: 12, weight: .medium) + statusLabel.font = .app(.captionMedium) statusLabel.textColor = AppColor.primary addSubview(nameLabel) @@ -33,17 +33,17 @@ final class HomeStoreCardView: UIView { addSubview(statusLabel) nameLabel.snp.makeConstraints { make in - make.top.leading.equalToSuperview().offset(15) - make.trailing.lessThanOrEqualTo(statusLabel.snp.leading).offset(-8) + make.top.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset) + make.trailing.lessThanOrEqualTo(statusLabel.snp.leading).offset(-AppSpacing.xs) } statusLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(15) - make.trailing.equalToSuperview().offset(-15) + make.top.equalToSuperview().offset(AppSpacing.screenHorizontalInset) + make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset) } addressLabel.snp.makeConstraints { make in - make.top.equalTo(nameLabel.snp.bottom).offset(8) - make.leading.trailing.equalToSuperview().inset(15) - make.bottom.equalToSuperview().offset(-15) + make.top.equalTo(nameLabel.snp.bottom).offset(AppSpacing.xs) + make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset) + make.bottom.equalToSuperview().offset(-AppSpacing.screenHorizontalInset) } } diff --git a/suixinkan/UI/Home/Views/HomeWorkStatusCardView.swift b/suixinkan/UI/Home/Views/HomeWorkStatusCardView.swift index d6bc083..2724be9 100644 --- a/suixinkan/UI/Home/Views/HomeWorkStatusCardView.swift +++ b/suixinkan/UI/Home/Views/HomeWorkStatusCardView.swift @@ -18,18 +18,18 @@ final class HomeWorkStatusCardView: UIView { override init(frame: CGRect) { super.init(frame: frame) - backgroundColor = .white - layer.cornerRadius = 10 + backgroundColor = AppColor.cardBackground + layer.cornerRadius = AppRadius.md - onlineButton.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium) - onlineButton.layer.cornerRadius = 4 + onlineButton.titleLabel?.font = .app(.captionMedium) + onlineButton.layer.cornerRadius = AppRadius.xs onlineButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12) onlineButton.addTarget(self, action: #selector(onlineTapped), for: .touchUpInside) - countdownLabel.font = .systemFont(ofSize: 16, weight: .medium) + countdownLabel.font = .app(.subtitle) countdownLabel.textColor = AppColor.primary - reminderButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + reminderButton.titleLabel?.font = .app(.subtitle) reminderButton.setTitleColor(AppColor.primary, for: .normal) reminderButton.addTarget(self, action: #selector(reminderTapped), for: .touchUpInside) @@ -38,18 +38,18 @@ final class HomeWorkStatusCardView: UIView { addSubview(reminderButton) onlineButton.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(15) + make.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset) make.centerY.equalToSuperview() } countdownLabel.snp.makeConstraints { make in make.center.equalToSuperview() } reminderButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().offset(-15) + make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset) make.centerY.equalToSuperview() } snp.makeConstraints { make in - make.height.equalTo(52) + make.height.equalTo(AppSpacing.formRowHeight) } } @@ -61,11 +61,11 @@ final class HomeWorkStatusCardView: UIView { func apply(isOnline: Bool, countdownText: String, reminderMinutes: Int) { onlineButton.setTitle(isOnline ? "在线" : "离线", for: .normal) if isOnline { - onlineButton.backgroundColor = UIColor(hex: 0x22C55E) - onlineButton.setTitleColor(UIColor(hex: 0xF0FDF4), for: .normal) + onlineButton.backgroundColor = AppColor.online + onlineButton.setTitleColor(AppColor.successBackground, for: .normal) } else { - onlineButton.backgroundColor = UIColor(hex: 0xF4F4F4) - onlineButton.setTitleColor(UIColor(hex: 0x7B8EAA), for: .normal) + onlineButton.backgroundColor = AppColor.inputBackground + onlineButton.setTitleColor(AppColor.textTabInactive, for: .normal) } countdownLabel.text = countdownText let reminderTitle = reminderMinutes == 0 ? "不提醒" : "提前\(reminderMinutes)分钟" diff --git a/suixinkan/UI/MainTab/MainTabBarController.swift b/suixinkan/UI/MainTab/MainTabBarController.swift index 7b184e4..6246304 100644 --- a/suixinkan/UI/MainTab/MainTabBarController.swift +++ b/suixinkan/UI/MainTab/MainTabBarController.swift @@ -67,21 +67,21 @@ final class MainTabBarController: UITabBarController { private func configureTabBarAppearance() { tabBar.tintColor = AppColor.primary - tabBar.backgroundColor = .white + tabBar.backgroundColor = AppColor.cardBackground tabBar.shadowImage = UIImage() let normalAttributes: [NSAttributedString.Key: Any] = [ - .foregroundColor: UIColor(hex: 0x7D8DA3), - .font: UIFont.systemFont(ofSize: 12, weight: .regular), + .foregroundColor: AppColor.textTabInactive, + .font: UIFont.app(.caption), ] let selectedAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: AppColor.primary, - .font: UIFont.systemFont(ofSize: 12, weight: .medium), + .font: UIFont.app(.captionMedium), ] let appearance = UITabBarAppearance() appearance.configureWithOpaqueBackground() - appearance.backgroundColor = .white + appearance.backgroundColor = AppColor.cardBackground appearance.shadowColor = .clear appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes diff --git a/suixinkan/UI/Orders/OrderCardCell.swift b/suixinkan/UI/Orders/OrderCardCell.swift index 740309d..c0429b9 100644 --- a/suixinkan/UI/Orders/OrderCardCell.swift +++ b/suixinkan/UI/Orders/OrderCardCell.swift @@ -107,34 +107,34 @@ final class OrderCardCell: UITableViewCell { } private func setupUI() { - cardView.backgroundColor = .white - cardView.layer.cornerRadius = 12 + cardView.backgroundColor = AppColor.cardBackground + cardView.layer.cornerRadius = AppRadius.lg - orderNumberLabel.font = .systemFont(ofSize: 14) - orderNumberLabel.textColor = .black - createdAtLabel.font = .systemFont(ofSize: 12) - createdAtLabel.textColor = UIColor(hex: 0x6B7280) - typeChip.font = .systemFont(ofSize: 12) + orderNumberLabel.font = .app(.body) + orderNumberLabel.textColor = AppColor.textPrimary + createdAtLabel.font = .app(.caption) + createdAtLabel.textColor = AppColor.textSecondary + typeChip.font = .app(.caption) typeChip.textColor = AppColor.primary - typeChip.backgroundColor = UIColor(hex: 0xEFF6FF) - typeChip.layer.cornerRadius = 4 + typeChip.backgroundColor = AppColor.primaryLight + typeChip.layer.cornerRadius = AppRadius.xs typeChip.clipsToBounds = true typeChip.textAlignment = .center - configureActionButton(refundButton, title: "退款", color: UIColor(hex: 0xEF4444), bg: UIColor(hex: 0xFFE7E7)) - configureActionButton(verifyButton, title: "核销订单", color: .white, bg: UIColor(hex: 0x0073FF)) + configureActionButton(refundButton, title: "退款", color: AppColor.danger, bg: AppColor.dangerBackground) + configureActionButton(verifyButton, title: "核销订单", color: .white, bg: AppColor.primary) refundButton.addTarget(self, action: #selector(refundTapped), for: .touchUpInside) verifyButton.addTarget(self, action: #selector(verifyTapped), for: .touchUpInside) uploadTaskButton.setTitle("上传任务", for: .normal) uploadTaskButton.setTitleColor(AppColor.primary, for: .normal) - uploadTaskButton.titleLabel?.font = .systemFont(ofSize: 16) - uploadTaskButton.backgroundColor = UIColor(hex: 0xEFF6FF) - uploadTaskButton.layer.cornerRadius = 12 + uploadTaskButton.titleLabel?.font = .app(.subtitle) + uploadTaskButton.backgroundColor = AppColor.primaryLight + uploadTaskButton.layer.cornerRadius = AppRadius.lg uploadTaskButton.addTarget(self, action: #selector(uploadTaskTapped), for: .touchUpInside) actionStack.axis = .horizontal - actionStack.spacing = 12 + actionStack.spacing = AppSpacing.sm actionStack.distribution = .fillEqually actionStack.addArrangedSubview(refundButton) actionStack.addArrangedSubview(verifyButton) @@ -143,27 +143,27 @@ final class OrderCardCell: UITableViewCell { infoStack.spacing = 6 cancelButton.setTitle("取消订单", for: .normal) - cancelButton.setTitleColor(UIColor(hex: 0xEF4444), for: .normal) - cancelButton.titleLabel?.font = .systemFont(ofSize: 14) + cancelButton.setTitleColor(AppColor.danger, for: .normal) + cancelButton.titleLabel?.font = .app(.body) cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside) orderSourceButton.setTitleColor(AppColor.primary, for: .normal) - orderSourceButton.titleLabel?.font = .systemFont(ofSize: 14) + orderSourceButton.titleLabel?.font = .app(.body) orderSourceButton.contentHorizontalAlignment = .leading orderSourceButton.addTarget(self, action: #selector(orderSourceTapped), for: .touchUpInside) historicalButton.setTitle("历史拍摄", for: .normal) historicalButton.setTitleColor(AppColor.primary, for: .normal) - historicalButton.titleLabel?.font = .systemFont(ofSize: 14) + historicalButton.titleLabel?.font = .app(.body) historicalButton.addTarget(self, action: #selector(historicalTapped), for: .touchUpInside) refinedStack.axis = .horizontal - refinedStack.spacing = 8 + refinedStack.spacing = AppSpacing.xs refinedNeedButton.setTitle("需要精修", for: .normal) refinedNoButton.setTitle("无需精修", for: .normal) [refinedNeedButton, refinedNoButton].forEach { - $0.titleLabel?.font = .systemFont(ofSize: 13) - $0.layer.cornerRadius = 6 + $0.titleLabel?.font = .app(.body) + $0.layer.cornerRadius = AppRadius.sm $0.layer.borderWidth = 1 } refinedNeedButton.addTarget(self, action: #selector(refinedNeedTapped), for: .touchUpInside) @@ -185,7 +185,7 @@ final class OrderCardCell: UITableViewCell { cardView.addSubview(cancelButton) cardView.snp.makeConstraints { make in - make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 12, right: 16)) + make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: AppSpacing.md, bottom: AppSpacing.sm, right: AppSpacing.md)) } orderNumberLabel.snp.makeConstraints { make in make.top.leading.equalToSuperview().inset(16) @@ -240,9 +240,9 @@ final class OrderCardCell: UITableViewCell { private func configureActionButton(_ button: UIButton, title: String, color: UIColor, bg: UIColor) { button.setTitle(title, for: .normal) button.setTitleColor(color, for: .normal) - button.titleLabel?.font = .systemFont(ofSize: 16) + button.titleLabel?.font = .app(.subtitle) button.backgroundColor = bg - button.layer.cornerRadius = 8 + button.layer.cornerRadius = AppRadius.sm } private func addInfoRow(title: String, value: String) { @@ -251,12 +251,12 @@ final class OrderCardCell: UITableViewCell { row.distribution = .fillEqually let titleLabel = UILabel() titleLabel.text = title - titleLabel.font = .systemFont(ofSize: 13) - titleLabel.textColor = UIColor(hex: 0x6B7280) + titleLabel.font = .app(.body) + titleLabel.textColor = AppColor.textSecondary let valueLabel = UILabel() valueLabel.text = value - valueLabel.font = .systemFont(ofSize: 13) - valueLabel.textColor = AppColor.text333 + valueLabel.font = .app(.body) + valueLabel.textColor = AppColor.textPrimary valueLabel.textAlignment = .right row.addArrangedSubview(titleLabel) row.addArrangedSubview(valueLabel) @@ -269,12 +269,12 @@ final class OrderCardCell: UITableViewCell { row.distribution = .fillEqually let titleLabel = UILabel() titleLabel.text = "手机号" - titleLabel.font = .systemFont(ofSize: 13) - titleLabel.textColor = UIColor(hex: 0x6B7280) + titleLabel.font = .app(.body) + titleLabel.textColor = AppColor.textSecondary let valueButton = UIButton(type: .system) valueButton.setTitle(OrderPhoneMask.mask(phone), for: .normal) valueButton.setTitleColor(AppColor.primary, for: .normal) - valueButton.titleLabel?.font = .systemFont(ofSize: 13) + valueButton.titleLabel?.font = .app(.body) valueButton.contentHorizontalAlignment = .trailing valueButton.isHidden = hidden || phone.isEmpty valueButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside) @@ -285,7 +285,7 @@ final class OrderCardCell: UITableViewCell { private func updateRefinedButtons(isRefined: Int, readOnly: Bool) { let selectedBorder = AppColor.primary.cgColor - let normalBorder = UIColor(hex: 0xE5E7EB).cgColor + let normalBorder = AppColor.border.cgColor refinedNeedButton.layer.borderColor = isRefined == 1 ? selectedBorder : normalBorder refinedNoButton.layer.borderColor = isRefined == 2 ? selectedBorder : normalBorder refinedNeedButton.isEnabled = !readOnly diff --git a/suixinkan/UI/Orders/OrderStatusBadgeView.swift b/suixinkan/UI/Orders/OrderStatusBadgeView.swift index f35b8d1..2c9ab92 100644 --- a/suixinkan/UI/Orders/OrderStatusBadgeView.swift +++ b/suixinkan/UI/Orders/OrderStatusBadgeView.swift @@ -6,20 +6,17 @@ import SnapKit import UIKit -/// 订单状态标签。 +/// 订单状态标签,内部委托 `AppStatusBadge`。 final class OrderStatusBadgeView: UIView { - private let label = UILabel() + + private let badge = AppStatusBadge() override init(frame: CGRect) { super.init(frame: frame) - label.font = .systemFont(ofSize: 14, weight: .medium) - label.textAlignment = .center - addSubview(label) - label.snp.makeConstraints { make in - make.edges.equalToSuperview().inset(UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4)) + addSubview(badge) + badge.snp.makeConstraints { make in + make.edges.equalToSuperview() } - layer.cornerRadius = 4 - clipsToBounds = true } @available(*, unavailable) @@ -28,20 +25,6 @@ final class OrderStatusBadgeView: UIView { } func apply(status: Int, text: String) { - label.text = text - switch status { - case 18, 20: - label.textColor = UIColor(hex: 0x0073FF) - backgroundColor = UIColor(hex: 0xEFF6FF) - case 30: - label.textColor = UIColor(hex: 0x10B981) - backgroundColor = UIColor(hex: 0xF0FDF4) - case 50: - label.textColor = UIColor(hex: 0xEF4444) - backgroundColor = UIColor(hex: 0xFFE7E7) - default: - label.textColor = UIColor(hex: 0xFF7B00) - backgroundColor = UIColor(hex: 0xFFF0E2) - } + badge.applyOrderStatus(status, text: text) } } diff --git a/suixinkan/UI/Orders/OrdersViewController.swift b/suixinkan/UI/Orders/OrdersViewController.swift index dae8439..e67acf2 100644 --- a/suixinkan/UI/Orders/OrdersViewController.swift +++ b/suixinkan/UI/Orders/OrdersViewController.swift @@ -30,7 +30,6 @@ final class OrdersViewController: BaseViewController, UITableViewDataSource, UIT } override func setupUI() { - view.backgroundColor = UIColor(hex: 0xF5F5F5) view.addSubview(filterBar) view.addSubview(listView) listView.tableView.dataSource = self diff --git a/suixinkan/UI/Profile/ProfileStatusBadgeView.swift b/suixinkan/UI/Profile/ProfileStatusBadgeView.swift index 639c7a5..645b519 100644 --- a/suixinkan/UI/Profile/ProfileStatusBadgeView.swift +++ b/suixinkan/UI/Profile/ProfileStatusBadgeView.swift @@ -6,7 +6,7 @@ import SnapKit import UIKit -/// 个人信息页状态标签,用于账号状态、审核状态等展示。 +/// 个人信息页状态标签,内部委托 `AppStatusBadge`。 final class ProfileStatusBadgeView: UIView { enum Style { @@ -20,18 +20,14 @@ final class ProfileStatusBadgeView: UIView { case bankRejected } - private let label = UILabel() + private let badge = AppStatusBadge() override init(frame: CGRect) { super.init(frame: frame) - label.font = .systemFont(ofSize: 12, weight: .medium) - label.textAlignment = .center - addSubview(label) - label.snp.makeConstraints { make in - make.edges.equalToSuperview().inset(UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8)) + addSubview(badge) + badge.snp.makeConstraints { make in + make.edges.equalToSuperview() } - layer.cornerRadius = 4 - clipsToBounds = true } @available(*, unavailable) @@ -40,23 +36,15 @@ final class ProfileStatusBadgeView: UIView { } func apply(style: Style, text: String) { - label.text = text switch style { case .accountActive: - label.textColor = UIColor(hex: 0x22C55E) - backgroundColor = UIColor(hex: 0xF0FDF4) - case .scenic: - label.textColor = AppColor.primary - backgroundColor = UIColor(hex: 0xEFF6FF) + badge.apply(tone: .success, text: text) + case .scenic, .auditApproved, .bankApproved: + badge.apply(tone: .info, text: text) case .auditPending, .bankPending: - label.textColor = UIColor(hex: 0xFF7B00) - backgroundColor = UIColor(hex: 0xFFF0E2) - case .auditApproved, .bankApproved: - label.textColor = AppColor.primary - backgroundColor = UIColor(hex: 0xEFF6FF) + badge.apply(tone: .warning, text: text) case .auditRejected, .bankRejected: - label.textColor = UIColor(hex: 0xEF4444) - backgroundColor = UIColor(hex: 0xFFE7E7) + badge.apply(tone: .danger, text: text) } } } diff --git a/suixinkan/UI/Statistics/StatisticsDailyListView.swift b/suixinkan/UI/Statistics/StatisticsDailyListView.swift index b8894e0..d63ced1 100644 --- a/suixinkan/UI/Statistics/StatisticsDailyListView.swift +++ b/suixinkan/UI/Statistics/StatisticsDailyListView.swift @@ -47,27 +47,27 @@ final class StatisticsDailyListView: UIView { } private func setupUI() { - backgroundColor = .white - layer.cornerRadius = 12 + backgroundColor = AppColor.cardBackground + layer.cornerRadius = AppRadius.lg clipsToBounds = true titleLabel.text = "时间范围" - titleLabel.font = .systemFont(ofSize: 14, weight: .bold) - titleLabel.textColor = .black + titleLabel.font = .app(.bodyMedium) + titleLabel.textColor = AppColor.textPrimary - rangeButton.backgroundColor = UIColor(hex: 0xF4F4F4) - rangeButton.layer.cornerRadius = 4 + rangeButton.backgroundColor = AppColor.inputBackground + rangeButton.layer.cornerRadius = AppRadius.xs rangeButton.addTarget(self, action: #selector(rangeTapped), for: .touchUpInside) - rangeLabel.font = .systemFont(ofSize: 14) - rangeLabel.textColor = UIColor(hex: 0x4B5563) + rangeLabel.font = .app(.body) + rangeLabel.textColor = AppColor.textSecondary rangeLabel.text = "今日" arrowView.image = UIImage(systemName: "chevron.down") - arrowView.tintColor = UIColor(hex: 0x4B5563) + arrowView.tintColor = AppColor.textSecondary arrowView.contentMode = .scaleAspectFit - divider.backgroundColor = UIColor(hex: 0xF4F4F4) + divider.backgroundColor = AppColor.inputBackground tableView.backgroundColor = .clear tableView.separatorStyle = .none diff --git a/suixinkan/UI/Statistics/StatisticsPeriodButton.swift b/suixinkan/UI/Statistics/StatisticsPeriodButton.swift index f58225f..3efa9ae 100644 --- a/suixinkan/UI/Statistics/StatisticsPeriodButton.swift +++ b/suixinkan/UI/Statistics/StatisticsPeriodButton.swift @@ -28,16 +28,16 @@ final class StatisticsPeriodButton: UIButton { } private func setupUI() { - titleLabel?.font = .systemFont(ofSize: 14, weight: .medium) - layer.cornerRadius = 18 + titleLabel?.font = .app(.bodyMedium) + layer.cornerRadius = AppRadius.pill clipsToBounds = true contentEdgeInsets = UIEdgeInsets(top: 6, left: 20, bottom: 6, right: 20) updateAppearance() } private func updateAppearance() { - backgroundColor = isSelected ? UIColor(hex: 0x3B82F6) : UIColor(hex: 0xF3F4F6) - setTitleColor(isSelected ? .white : UIColor(hex: 0x4B5563), for: .normal) + backgroundColor = isSelected ? AppColor.primary : AppColor.inputBackground + setTitleColor(isSelected ? .white : AppColor.textSecondary, for: .normal) titleLabel?.font = .systemFont(ofSize: 14, weight: isSelected ? .semibold : .medium) } } diff --git a/suixinkan/UI/Statistics/StatisticsStatCardView.swift b/suixinkan/UI/Statistics/StatisticsStatCardView.swift index 710e22c..126c0af 100644 --- a/suixinkan/UI/Statistics/StatisticsStatCardView.swift +++ b/suixinkan/UI/Statistics/StatisticsStatCardView.swift @@ -32,13 +32,13 @@ final class StatisticsStatCardView: UIView { } private func setupUI() { - layer.cornerRadius = 12 + layer.cornerRadius = AppRadius.lg clipsToBounds = true - titleLabel.font = .systemFont(ofSize: 14) - titleLabel.textColor = UIColor.black.withAlphaComponent(0.6) + titleLabel.font = .app(.body) + titleLabel.textColor = AppColor.textPrimary.withAlphaComponent(0.6) - valueLabel.font = .systemFont(ofSize: 18, weight: .bold) + valueLabel.font = .app(.metric) valueLabel.numberOfLines = 1 valueLabel.adjustsFontSizeToFitWidth = true valueLabel.minimumScaleFactor = 0.8 @@ -50,16 +50,16 @@ final class StatisticsStatCardView: UIView { addSubview(iconView) titleLabel.snp.makeConstraints { make in - make.top.leading.equalToSuperview().inset(12) - make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-8) + make.top.leading.equalToSuperview().inset(AppSpacing.sm) + make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-AppSpacing.xs) } valueLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(3) - make.leading.bottom.equalToSuperview().inset(12) - make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-8) + make.leading.bottom.equalToSuperview().inset(AppSpacing.sm) + make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-AppSpacing.xs) } iconView.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(8) + make.trailing.equalToSuperview().inset(AppSpacing.xs) make.centerY.equalToSuperview() make.size.equalTo(48) } diff --git a/suixinkan/UI/Statistics/StatisticsSummaryCardView.swift b/suixinkan/UI/Statistics/StatisticsSummaryCardView.swift index ad13826..4259a2e 100644 --- a/suixinkan/UI/Statistics/StatisticsSummaryCardView.swift +++ b/suixinkan/UI/Statistics/StatisticsSummaryCardView.swift @@ -18,8 +18,8 @@ final class StatisticsSummaryCardView: UIView { private let totalAmountCard = StatisticsStatCardView( title: "订单总金额", - valueColor: UIColor(hex: 0x22C55E), - backgroundColor: UIColor(hex: 0xF0FDF4), + valueColor: AppColor.online, + backgroundColor: AppColor.successBackground, iconName: "icon_data_statistics_total_amount" ) private let totalOrderCard = StatisticsStatCardView( @@ -30,20 +30,20 @@ final class StatisticsSummaryCardView: UIView { ) private let receivedCard = StatisticsStatCardView( title: "实收金额", - valueColor: UIColor(hex: 0x22C55E), - backgroundColor: UIColor(hex: 0xFFF0E2), + valueColor: AppColor.online, + backgroundColor: AppColor.warningBackground, iconName: "icon_data_statistics_get_amount" ) private let averageCard = StatisticsStatCardView( title: "客单价", - valueColor: UIColor(hex: 0x0073FF), - backgroundColor: UIColor(hex: 0xEFF6FF), + valueColor: AppColor.primary, + backgroundColor: AppColor.primaryLight, iconName: "icon_data_statistics_average_amount" ) private let refundCard = StatisticsStatCardView( title: "退款金额", - valueColor: UIColor(hex: 0xEF4444), - backgroundColor: UIColor(hex: 0xFFE7E7), + valueColor: AppColor.danger, + backgroundColor: AppColor.dangerBackground, iconName: "icon_data_statistics_refund_amount" ) @@ -69,15 +69,15 @@ final class StatisticsSummaryCardView: UIView { } private func setupUI() { - backgroundColor = .white - layer.cornerRadius = 12 + backgroundColor = AppColor.cardBackground + layer.cornerRadius = AppRadius.lg clipsToBounds = true datePrefixLabel.text = "已选日期:" - datePrefixLabel.font = .systemFont(ofSize: 14) - datePrefixLabel.textColor = AppColor.text333 + datePrefixLabel.font = .app(.body) + datePrefixLabel.textColor = AppColor.textPrimary - dateValueLabel.font = .systemFont(ofSize: 14) + dateValueLabel.font = .app(.body) dateValueLabel.textColor = AppColor.primary dateValueLabel.numberOfLines = 1 @@ -95,17 +95,17 @@ final class StatisticsSummaryCardView: UIView { let row1 = UIStackView(arrangedSubviews: [totalOrderCard, receivedCard]) row1.axis = .horizontal - row1.spacing = 12 + row1.spacing = AppSpacing.sm row1.distribution = .fillEqually let row2 = UIStackView(arrangedSubviews: [averageCard, refundCard]) row2.axis = .horizontal - row2.spacing = 12 + row2.spacing = AppSpacing.sm row2.distribution = .fillEqually let cardStack = UIStackView(arrangedSubviews: [totalAmountCard, row1, row2]) cardStack.axis = .vertical - cardStack.spacing = 12 + cardStack.spacing = AppSpacing.sm let dateRow = UIStackView(arrangedSubviews: [datePrefixLabel, dateValueLabel, UIView()]) dateRow.axis = .horizontal @@ -114,11 +114,11 @@ final class StatisticsSummaryCardView: UIView { let contentStack = UIStackView(arrangedSubviews: [dateRow, periodStack, cardStack]) contentStack.axis = .vertical - contentStack.spacing = 12 + contentStack.spacing = AppSpacing.sm addSubview(contentStack) contentStack.snp.makeConstraints { make in - make.edges.equalToSuperview().inset(16) + make.edges.equalToSuperview().inset(AppSpacing.md) } } diff --git a/suixinkan/UI/Statistics/StatisticsViewController.swift b/suixinkan/UI/Statistics/StatisticsViewController.swift index a58b817..737ec20 100644 --- a/suixinkan/UI/Statistics/StatisticsViewController.swift +++ b/suixinkan/UI/Statistics/StatisticsViewController.swift @@ -23,7 +23,7 @@ final class StatisticsViewController: BaseViewController { } override func setupUI() { - view.backgroundColor = UIColor(hex: 0xF7FAFF) + view.backgroundColor = AppColor.pageBackgroundSoft view.addSubview(summaryCardView) view.addSubview(dailyListView) @@ -31,13 +31,13 @@ final class StatisticsViewController: BaseViewController { override func setupConstraints() { summaryCardView.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide).offset(16) - make.leading.trailing.equalToSuperview().inset(16) + make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md) + make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset) } dailyListView.snp.makeConstraints { make in - make.top.equalTo(summaryCardView.snp.bottom).offset(16) - make.leading.trailing.equalToSuperview().inset(16) - make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16) + make.top.equalTo(summaryCardView.snp.bottom).offset(AppSpacing.md) + make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset) + make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppSpacing.md) } } diff --git a/suixinkanTests/AppThemeTokensTests.swift b/suixinkanTests/AppThemeTokensTests.swift new file mode 100644 index 0000000..6f9cd7d --- /dev/null +++ b/suixinkanTests/AppThemeTokensTests.swift @@ -0,0 +1,80 @@ +// +// AppThemeTokensTests.swift +// suixinkanTests +// + +import UIKit +import XCTest +@testable import suixinkan + +/// Design Token 值与 Android 对齐校验。 +final class AppThemeTokensTests: XCTestCase { + + func testPrimaryColorMatchesAndroid() { + XCTAssertEqual(AppColor.primary.hexRGB, 0x0073FF) + } + + func testPageBackgroundColors() { + XCTAssertEqual(AppColor.pageBackground.hexRGB, 0xF5F5F5) + XCTAssertEqual(AppColor.pageBackgroundSoft.hexRGB, 0xF7FAFF) + } + + func testTextColorsMatchAndroid() { + XCTAssertEqual(AppColor.textPrimary.hexRGB, 0x333333) + XCTAssertEqual(AppColor.textSecondary.hexRGB, 0x666666) + XCTAssertEqual(AppColor.textTertiary.hexRGB, 0x999999) + } + + func testSemanticStatusColors() { + XCTAssertEqual(AppColor.success.hexRGB, 0x10B981) + XCTAssertEqual(AppColor.danger.hexRGB, 0xEF4444) + XCTAssertEqual(AppColor.warning.hexRGB, 0xFF7B00) + XCTAssertEqual(AppColor.info.hexRGB, 0x0073FF) + } + + func testSpacingGrid() { + XCTAssertEqual(AppSpacing.xxs, 4) + XCTAssertEqual(AppSpacing.xs, 8) + XCTAssertEqual(AppSpacing.sm, 12) + XCTAssertEqual(AppSpacing.md, 16) + XCTAssertEqual(AppSpacing.lg, 24) + XCTAssertEqual(AppSpacing.screenHorizontalInset, AppSpacing.md) + XCTAssertEqual(AppSpacing.minTouchTarget, 44) + XCTAssertGreaterThanOrEqual(AppSpacing.buttonHeight, AppSpacing.minTouchTarget) + } + + func testRadiusTokens() { + XCTAssertEqual(AppRadius.xs, 4) + XCTAssertEqual(AppRadius.md, 10) + XCTAssertEqual(AppRadius.lg, 12) + XCTAssertEqual(AppRadius.xl, 16) + } + + func testAppFontSizes() { + XCTAssertEqual(UIFont.app(.body).pointSize, 14) + XCTAssertEqual(UIFont.app(.metric).pointSize, 18) + XCTAssertEqual(UIFont.app(.display).pointSize, 28) + } + + func testStatusBadgeOrderMapping() { + let badge = AppStatusBadge() + badge.applyOrderStatus(30, text: "已完成") + // 布局完成后仍应能读取子 label 文案 + XCTAssertFalse(badge.isHidden) + } +} + +private extension UIColor { + + var hexRGB: UInt { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + let r = Int(round(red * 255)) + let g = Int(round(green * 255)) + let b = Int(round(blue * 255)) + return UInt(r << 16 | g << 8 | b) + } +}