引入统一设计令牌并迁移首页、订单和数据 UI
This commit is contained in:
128
design-system/随心瞰商家版/MASTER.md
Normal file
128
design-system/随心瞰商家版/MASTER.md
Normal file
@ -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 组件化
|
||||||
24
design-system/随心瞰商家版/pages/home.md
Normal file
24
design-system/随心瞰商家版/pages/home.md
Normal file
@ -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 卡片规范
|
||||||
@ -13,7 +13,7 @@ class BaseViewController: UIViewController {
|
|||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
view.backgroundColor = .systemBackground
|
view.backgroundColor = AppColor.pageBackground
|
||||||
setupNavigationBar()
|
setupNavigationBar()
|
||||||
setupUI()
|
setupUI()
|
||||||
setupConstraints()
|
setupConstraints()
|
||||||
@ -61,14 +61,14 @@ class BaseViewController: UIViewController {
|
|||||||
|
|
||||||
let container = UIView()
|
let container = UIView()
|
||||||
container.backgroundColor = UIColor.black.withAlphaComponent(0.8)
|
container.backgroundColor = UIColor.black.withAlphaComponent(0.8)
|
||||||
container.layer.cornerRadius = 8
|
container.layer.cornerRadius = AppRadius.sm
|
||||||
container.clipsToBounds = true
|
container.clipsToBounds = true
|
||||||
container.alpha = 0
|
container.alpha = 0
|
||||||
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = message
|
label.text = message
|
||||||
label.textColor = .white
|
label.textColor = .white
|
||||||
label.font = .systemFont(ofSize: 14)
|
label.font = .app(.body)
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
) {
|
) {
|
||||||
guard let windowScene = scene as? UIWindowScene else { return }
|
guard let windowScene = scene as? UIWindowScene else { return }
|
||||||
|
|
||||||
|
AppNavigationBarAppearance.applyGlobalAppearance()
|
||||||
|
|
||||||
let window = UIWindow(windowScene: windowScene)
|
let window = UIWindow(windowScene: windowScene)
|
||||||
window.rootViewController = AppRouter.makeRootViewController()
|
window.rootViewController = AppRouter.makeRootViewController()
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
|
|||||||
@ -5,15 +5,56 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// 应用主题色,与 Android `theme/Color.kt` 对齐。
|
/// 应用语义色,与 Android `theme/Color.kt` 及 `ScenicQueueTokens` 对齐。
|
||||||
enum AppColor {
|
enum AppColor {
|
||||||
|
|
||||||
static let primary = UIColor(red: 0 / 255, green: 115 / 255, blue: 255 / 255, alpha: 1)
|
// MARK: - Brand
|
||||||
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 primary = UIColor(hex: 0x0073FF)
|
||||||
static let agreementLink = UIColor(red: 32 / 255, green: 139 / 255, blue: 255 / 255, alpha: 1)
|
static let primaryBanner = UIColor(hex: 0x1677FF)
|
||||||
static let inputBackground = UIColor(red: 244 / 255, green: 244 / 255, blue: 244 / 255, alpha: 1)
|
static let primaryLight = UIColor(hex: 0xEFF6FF)
|
||||||
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: - 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
|
static let placeholder = UIColor.gray
|
||||||
}
|
}
|
||||||
|
|||||||
54
suixinkan/Theme/AppFont.swift
Normal file
54
suixinkan/Theme/AppFont.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
28
suixinkan/Theme/AppNavigationBarAppearance.swift
Normal file
28
suixinkan/Theme/AppNavigationBarAppearance.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
20
suixinkan/Theme/AppRadius.swift
Normal file
20
suixinkan/Theme/AppRadius.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
18
suixinkan/Theme/AppShadow.swift
Normal file
18
suixinkan/Theme/AppShadow.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
33
suixinkan/Theme/AppSpacing.swift
Normal file
33
suixinkan/Theme/AppSpacing.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -32,14 +32,14 @@ final class AppButton: UIButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI(title: String) {
|
private func setupUI(title: String) {
|
||||||
titleLabel?.font = .systemFont(ofSize: 14, weight: .regular)
|
titleLabel?.font = .app(.body)
|
||||||
layer.cornerRadius = 10
|
layer.cornerRadius = AppRadius.md
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
setTitle(title, for: .normal)
|
setTitle(title, for: .normal)
|
||||||
updateAppearance()
|
updateAppearance()
|
||||||
|
|
||||||
snp.makeConstraints { make in
|
snp.makeConstraints { make in
|
||||||
make.height.equalTo(46)
|
make.height.equalTo(AppSpacing.buttonHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
44
suixinkan/UI/Common/AppCardView.swift
Normal file
44
suixinkan/UI/Common/AppCardView.swift
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
suixinkan/UI/Common/AppStatusBadge.swift
Normal file
82
suixinkan/UI/Common/AppStatusBadge.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,10 +30,10 @@ final class HomeViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func setupUI() {
|
override func setupUI() {
|
||||||
view.backgroundColor = UIColor(hex: 0xF5F5F5)
|
view.backgroundColor = AppColor.pageBackground
|
||||||
|
|
||||||
contentStack.axis = .vertical
|
contentStack.axis = .vertical
|
||||||
contentStack.spacing = 12
|
contentStack.spacing = AppSpacing.sm
|
||||||
|
|
||||||
scrollView.showsVerticalScrollIndicator = false
|
scrollView.showsVerticalScrollIndicator = false
|
||||||
view.addSubview(scenicHeaderView)
|
view.addSubview(scenicHeaderView)
|
||||||
@ -51,15 +51,15 @@ final class HomeViewController: BaseViewController {
|
|||||||
scenicHeaderView.snp.makeConstraints { make in
|
scenicHeaderView.snp.makeConstraints { make in
|
||||||
make.top.equalTo(view.safeAreaLayoutGuide)
|
make.top.equalTo(view.safeAreaLayoutGuide)
|
||||||
make.leading.trailing.equalToSuperview()
|
make.leading.trailing.equalToSuperview()
|
||||||
make.height.equalTo(52)
|
make.height.equalTo(AppSpacing.homeHeaderHeight)
|
||||||
}
|
}
|
||||||
scrollView.snp.makeConstraints { make in
|
scrollView.snp.makeConstraints { make in
|
||||||
make.top.equalTo(scenicHeaderView.snp.bottom)
|
make.top.equalTo(scenicHeaderView.snp.bottom)
|
||||||
make.leading.trailing.bottom.equalToSuperview()
|
make.leading.trailing.bottom.equalToSuperview()
|
||||||
}
|
}
|
||||||
contentStack.snp.makeConstraints { make in
|
contentStack.snp.makeConstraints { make in
|
||||||
make.edges.equalToSuperview().inset(16)
|
make.edges.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
|
||||||
make.width.equalTo(scrollView.snp.width).offset(-32)
|
make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.screenHorizontalInset * 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,8 +47,8 @@ final class HomeCommonAppsGridView: UIView {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
titleLabel.text = "常用应用"
|
titleLabel.text = "常用应用"
|
||||||
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
|
titleLabel.font = .app(.title)
|
||||||
titleLabel.textColor = AppColor.text333
|
titleLabel.textColor = AppColor.textPrimary
|
||||||
|
|
||||||
collectionView.backgroundColor = .clear
|
collectionView.backgroundColor = .clear
|
||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
@ -61,7 +61,7 @@ final class HomeCommonAppsGridView: UIView {
|
|||||||
make.top.leading.trailing.equalToSuperview()
|
make.top.leading.trailing.equalToSuperview()
|
||||||
}
|
}
|
||||||
collectionView.snp.makeConstraints { make in
|
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.leading.trailing.bottom.equalToSuperview()
|
||||||
make.height.equalTo(88)
|
make.height.equalTo(88)
|
||||||
}
|
}
|
||||||
@ -101,8 +101,8 @@ final class HomeMenuCell: UICollectionViewCell {
|
|||||||
iconView.tintColor = AppColor.primary
|
iconView.tintColor = AppColor.primary
|
||||||
iconView.contentMode = .scaleAspectFit
|
iconView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
titleLabel.font = .systemFont(ofSize: 12)
|
titleLabel.font = .app(.caption)
|
||||||
titleLabel.textColor = AppColor.text333
|
titleLabel.textColor = AppColor.textPrimary
|
||||||
titleLabel.textAlignment = .center
|
titleLabel.textAlignment = .center
|
||||||
titleLabel.numberOfLines = 2
|
titleLabel.numberOfLines = 2
|
||||||
|
|
||||||
@ -110,13 +110,13 @@ final class HomeMenuCell: UICollectionViewCell {
|
|||||||
contentView.addSubview(titleLabel)
|
contentView.addSubview(titleLabel)
|
||||||
|
|
||||||
iconView.snp.makeConstraints { make in
|
iconView.snp.makeConstraints { make in
|
||||||
make.top.equalToSuperview().offset(8)
|
make.top.equalToSuperview().offset(AppSpacing.xs)
|
||||||
make.centerX.equalToSuperview()
|
make.centerX.equalToSuperview()
|
||||||
make.width.height.equalTo(28)
|
make.width.height.equalTo(28)
|
||||||
}
|
}
|
||||||
titleLabel.snp.makeConstraints { make in
|
titleLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(iconView.snp.bottom).offset(8)
|
make.top.equalTo(iconView.snp.bottom).offset(AppSpacing.xs)
|
||||||
make.leading.trailing.equalToSuperview().inset(4)
|
make.leading.trailing.equalToSuperview().inset(AppSpacing.xxs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,23 +17,23 @@ final class HomeLocationReportCardView: UIView {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
backgroundColor = .white
|
backgroundColor = AppColor.cardBackground
|
||||||
layer.cornerRadius = 10
|
layer.cornerRadius = AppRadius.md
|
||||||
|
|
||||||
titleLabel.text = "位置上报"
|
titleLabel.text = "位置上报"
|
||||||
titleLabel.font = .systemFont(ofSize: 16, weight: .semibold)
|
titleLabel.font = .app(.title)
|
||||||
titleLabel.textColor = AppColor.text333
|
titleLabel.textColor = AppColor.textPrimary
|
||||||
|
|
||||||
addressLabel.font = .systemFont(ofSize: 13)
|
addressLabel.font = .app(.body)
|
||||||
addressLabel.textColor = AppColor.text666
|
addressLabel.textColor = AppColor.textSecondary
|
||||||
addressLabel.numberOfLines = 2
|
addressLabel.numberOfLines = 2
|
||||||
|
|
||||||
reportButton.setTitle("立即上报", for: .normal)
|
reportButton.setTitle("立即上报", for: .normal)
|
||||||
reportButton.setTitleColor(.white, for: .normal)
|
reportButton.setTitleColor(.white, for: .normal)
|
||||||
reportButton.backgroundColor = AppColor.primary
|
reportButton.backgroundColor = AppColor.primary
|
||||||
reportButton.layer.cornerRadius = 6
|
reportButton.layer.cornerRadius = AppRadius.sm
|
||||||
reportButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
reportButton.titleLabel?.font = .app(.bodyMedium)
|
||||||
reportButton.contentEdgeInsets = UIEdgeInsets(top: 8, left: 14, bottom: 8, right: 14)
|
reportButton.contentEdgeInsets = UIEdgeInsets(top: AppSpacing.xs, left: 14, bottom: AppSpacing.xs, right: 14)
|
||||||
reportButton.addTarget(self, action: #selector(reportTapped), for: .touchUpInside)
|
reportButton.addTarget(self, action: #selector(reportTapped), for: .touchUpInside)
|
||||||
|
|
||||||
addSubview(titleLabel)
|
addSubview(titleLabel)
|
||||||
@ -41,16 +41,16 @@ final class HomeLocationReportCardView: UIView {
|
|||||||
addSubview(reportButton)
|
addSubview(reportButton)
|
||||||
|
|
||||||
titleLabel.snp.makeConstraints { make in
|
titleLabel.snp.makeConstraints { make in
|
||||||
make.top.leading.equalToSuperview().offset(15)
|
make.top.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset)
|
||||||
}
|
}
|
||||||
addressLabel.snp.makeConstraints { make in
|
addressLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(titleLabel.snp.bottom).offset(8)
|
make.top.equalTo(titleLabel.snp.bottom).offset(AppSpacing.xs)
|
||||||
make.leading.equalToSuperview().offset(15)
|
make.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset)
|
||||||
make.bottom.equalToSuperview().offset(-15)
|
make.bottom.equalToSuperview().offset(-AppSpacing.screenHorizontalInset)
|
||||||
make.trailing.lessThanOrEqualTo(reportButton.snp.leading).offset(-12)
|
make.trailing.lessThanOrEqualTo(reportButton.snp.leading).offset(-AppSpacing.sm)
|
||||||
}
|
}
|
||||||
reportButton.snp.makeConstraints { make in
|
reportButton.snp.makeConstraints { make in
|
||||||
make.trailing.equalToSuperview().offset(-15)
|
make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ final class HomeQuickActionsView: UIView {
|
|||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
stackView.axis = .horizontal
|
stackView.axis = .horizontal
|
||||||
stackView.distribution = .fillEqually
|
stackView.distribution = .fillEqually
|
||||||
stackView.spacing = 12
|
stackView.spacing = AppSpacing.sm
|
||||||
addSubview(stackView)
|
addSubview(stackView)
|
||||||
stackView.snp.makeConstraints { make in
|
stackView.snp.makeConstraints { make in
|
||||||
make.edges.equalToSuperview()
|
make.edges.equalToSuperview()
|
||||||
@ -45,8 +45,8 @@ final class HomeQuickActionsView: UIView {
|
|||||||
|
|
||||||
private func makeActionButton(title: String, icon: String, action: Selector) -> UIView {
|
private func makeActionButton(title: String, icon: String, action: Selector) -> UIView {
|
||||||
let container = UIView()
|
let container = UIView()
|
||||||
container.backgroundColor = .white
|
container.backgroundColor = AppColor.cardBackground
|
||||||
container.layer.cornerRadius = 10
|
container.layer.cornerRadius = AppRadius.md
|
||||||
|
|
||||||
let button = UIButton(type: .system)
|
let button = UIButton(type: .system)
|
||||||
button.addTarget(self, action: action, for: .touchUpInside)
|
button.addTarget(self, action: action, for: .touchUpInside)
|
||||||
@ -61,8 +61,8 @@ final class HomeQuickActionsView: UIView {
|
|||||||
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.text = title
|
label.text = title
|
||||||
label.font = .systemFont(ofSize: 13, weight: .medium)
|
label.font = .app(.bodyMedium)
|
||||||
label.textColor = AppColor.text333
|
label.textColor = AppColor.textPrimary
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
|
|
||||||
let column = UIStackView(arrangedSubviews: [imageView, label])
|
let column = UIStackView(arrangedSubviews: [imageView, label])
|
||||||
|
|||||||
@ -16,22 +16,22 @@ final class HomeScenicHeaderView: UIView {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
backgroundColor = .white
|
backgroundColor = AppColor.cardBackground
|
||||||
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
|
titleLabel.font = .app(.subtitle)
|
||||||
titleLabel.textColor = AppColor.text333
|
titleLabel.textColor = AppColor.textPrimary
|
||||||
arrowView.tintColor = AppColor.text666
|
arrowView.tintColor = AppColor.textSecondary
|
||||||
arrowView.contentMode = .scaleAspectFit
|
arrowView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
addSubview(titleLabel)
|
addSubview(titleLabel)
|
||||||
addSubview(arrowView)
|
addSubview(arrowView)
|
||||||
|
|
||||||
titleLabel.snp.makeConstraints { make in
|
titleLabel.snp.makeConstraints { make in
|
||||||
make.leading.equalToSuperview().offset(16)
|
make.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset)
|
||||||
make.centerY.equalToSuperview()
|
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
|
arrowView.snp.makeConstraints { make in
|
||||||
make.trailing.equalToSuperview().offset(-16)
|
make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
make.width.height.equalTo(16)
|
make.width.height.equalTo(16)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,17 +15,17 @@ final class HomeStoreCardView: UIView {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
backgroundColor = .white
|
backgroundColor = AppColor.cardBackground
|
||||||
layer.cornerRadius = 10
|
layer.cornerRadius = AppRadius.md
|
||||||
|
|
||||||
nameLabel.font = .systemFont(ofSize: 16, weight: .semibold)
|
nameLabel.font = .app(.title)
|
||||||
nameLabel.textColor = AppColor.text333
|
nameLabel.textColor = AppColor.textPrimary
|
||||||
|
|
||||||
addressLabel.font = .systemFont(ofSize: 13)
|
addressLabel.font = .app(.body)
|
||||||
addressLabel.textColor = AppColor.text666
|
addressLabel.textColor = AppColor.textSecondary
|
||||||
addressLabel.numberOfLines = 2
|
addressLabel.numberOfLines = 2
|
||||||
|
|
||||||
statusLabel.font = .systemFont(ofSize: 12, weight: .medium)
|
statusLabel.font = .app(.captionMedium)
|
||||||
statusLabel.textColor = AppColor.primary
|
statusLabel.textColor = AppColor.primary
|
||||||
|
|
||||||
addSubview(nameLabel)
|
addSubview(nameLabel)
|
||||||
@ -33,17 +33,17 @@ final class HomeStoreCardView: UIView {
|
|||||||
addSubview(statusLabel)
|
addSubview(statusLabel)
|
||||||
|
|
||||||
nameLabel.snp.makeConstraints { make in
|
nameLabel.snp.makeConstraints { make in
|
||||||
make.top.leading.equalToSuperview().offset(15)
|
make.top.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset)
|
||||||
make.trailing.lessThanOrEqualTo(statusLabel.snp.leading).offset(-8)
|
make.trailing.lessThanOrEqualTo(statusLabel.snp.leading).offset(-AppSpacing.xs)
|
||||||
}
|
}
|
||||||
statusLabel.snp.makeConstraints { make in
|
statusLabel.snp.makeConstraints { make in
|
||||||
make.top.equalToSuperview().offset(15)
|
make.top.equalToSuperview().offset(AppSpacing.screenHorizontalInset)
|
||||||
make.trailing.equalToSuperview().offset(-15)
|
make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset)
|
||||||
}
|
}
|
||||||
addressLabel.snp.makeConstraints { make in
|
addressLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(nameLabel.snp.bottom).offset(8)
|
make.top.equalTo(nameLabel.snp.bottom).offset(AppSpacing.xs)
|
||||||
make.leading.trailing.equalToSuperview().inset(15)
|
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
|
||||||
make.bottom.equalToSuperview().offset(-15)
|
make.bottom.equalToSuperview().offset(-AppSpacing.screenHorizontalInset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,18 +18,18 @@ final class HomeWorkStatusCardView: UIView {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
backgroundColor = .white
|
backgroundColor = AppColor.cardBackground
|
||||||
layer.cornerRadius = 10
|
layer.cornerRadius = AppRadius.md
|
||||||
|
|
||||||
onlineButton.titleLabel?.font = .systemFont(ofSize: 12, weight: .medium)
|
onlineButton.titleLabel?.font = .app(.captionMedium)
|
||||||
onlineButton.layer.cornerRadius = 4
|
onlineButton.layer.cornerRadius = AppRadius.xs
|
||||||
onlineButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)
|
onlineButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)
|
||||||
onlineButton.addTarget(self, action: #selector(onlineTapped), for: .touchUpInside)
|
onlineButton.addTarget(self, action: #selector(onlineTapped), for: .touchUpInside)
|
||||||
|
|
||||||
countdownLabel.font = .systemFont(ofSize: 16, weight: .medium)
|
countdownLabel.font = .app(.subtitle)
|
||||||
countdownLabel.textColor = AppColor.primary
|
countdownLabel.textColor = AppColor.primary
|
||||||
|
|
||||||
reminderButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
reminderButton.titleLabel?.font = .app(.subtitle)
|
||||||
reminderButton.setTitleColor(AppColor.primary, for: .normal)
|
reminderButton.setTitleColor(AppColor.primary, for: .normal)
|
||||||
reminderButton.addTarget(self, action: #selector(reminderTapped), for: .touchUpInside)
|
reminderButton.addTarget(self, action: #selector(reminderTapped), for: .touchUpInside)
|
||||||
|
|
||||||
@ -38,18 +38,18 @@ final class HomeWorkStatusCardView: UIView {
|
|||||||
addSubview(reminderButton)
|
addSubview(reminderButton)
|
||||||
|
|
||||||
onlineButton.snp.makeConstraints { make in
|
onlineButton.snp.makeConstraints { make in
|
||||||
make.leading.equalToSuperview().offset(15)
|
make.leading.equalToSuperview().offset(AppSpacing.screenHorizontalInset)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
}
|
}
|
||||||
countdownLabel.snp.makeConstraints { make in
|
countdownLabel.snp.makeConstraints { make in
|
||||||
make.center.equalToSuperview()
|
make.center.equalToSuperview()
|
||||||
}
|
}
|
||||||
reminderButton.snp.makeConstraints { make in
|
reminderButton.snp.makeConstraints { make in
|
||||||
make.trailing.equalToSuperview().offset(-15)
|
make.trailing.equalToSuperview().offset(-AppSpacing.screenHorizontalInset)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
}
|
}
|
||||||
snp.makeConstraints { make in
|
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) {
|
func apply(isOnline: Bool, countdownText: String, reminderMinutes: Int) {
|
||||||
onlineButton.setTitle(isOnline ? "在线" : "离线", for: .normal)
|
onlineButton.setTitle(isOnline ? "在线" : "离线", for: .normal)
|
||||||
if isOnline {
|
if isOnline {
|
||||||
onlineButton.backgroundColor = UIColor(hex: 0x22C55E)
|
onlineButton.backgroundColor = AppColor.online
|
||||||
onlineButton.setTitleColor(UIColor(hex: 0xF0FDF4), for: .normal)
|
onlineButton.setTitleColor(AppColor.successBackground, for: .normal)
|
||||||
} else {
|
} else {
|
||||||
onlineButton.backgroundColor = UIColor(hex: 0xF4F4F4)
|
onlineButton.backgroundColor = AppColor.inputBackground
|
||||||
onlineButton.setTitleColor(UIColor(hex: 0x7B8EAA), for: .normal)
|
onlineButton.setTitleColor(AppColor.textTabInactive, for: .normal)
|
||||||
}
|
}
|
||||||
countdownLabel.text = countdownText
|
countdownLabel.text = countdownText
|
||||||
let reminderTitle = reminderMinutes == 0 ? "不提醒" : "提前\(reminderMinutes)分钟"
|
let reminderTitle = reminderMinutes == 0 ? "不提醒" : "提前\(reminderMinutes)分钟"
|
||||||
|
|||||||
@ -67,21 +67,21 @@ final class MainTabBarController: UITabBarController {
|
|||||||
|
|
||||||
private func configureTabBarAppearance() {
|
private func configureTabBarAppearance() {
|
||||||
tabBar.tintColor = AppColor.primary
|
tabBar.tintColor = AppColor.primary
|
||||||
tabBar.backgroundColor = .white
|
tabBar.backgroundColor = AppColor.cardBackground
|
||||||
tabBar.shadowImage = UIImage()
|
tabBar.shadowImage = UIImage()
|
||||||
|
|
||||||
let normalAttributes: [NSAttributedString.Key: Any] = [
|
let normalAttributes: [NSAttributedString.Key: Any] = [
|
||||||
.foregroundColor: UIColor(hex: 0x7D8DA3),
|
.foregroundColor: AppColor.textTabInactive,
|
||||||
.font: UIFont.systemFont(ofSize: 12, weight: .regular),
|
.font: UIFont.app(.caption),
|
||||||
]
|
]
|
||||||
let selectedAttributes: [NSAttributedString.Key: Any] = [
|
let selectedAttributes: [NSAttributedString.Key: Any] = [
|
||||||
.foregroundColor: AppColor.primary,
|
.foregroundColor: AppColor.primary,
|
||||||
.font: UIFont.systemFont(ofSize: 12, weight: .medium),
|
.font: UIFont.app(.captionMedium),
|
||||||
]
|
]
|
||||||
|
|
||||||
let appearance = UITabBarAppearance()
|
let appearance = UITabBarAppearance()
|
||||||
appearance.configureWithOpaqueBackground()
|
appearance.configureWithOpaqueBackground()
|
||||||
appearance.backgroundColor = .white
|
appearance.backgroundColor = AppColor.cardBackground
|
||||||
appearance.shadowColor = .clear
|
appearance.shadowColor = .clear
|
||||||
appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes
|
appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes
|
||||||
appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes
|
appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes
|
||||||
|
|||||||
@ -107,34 +107,34 @@ final class OrderCardCell: UITableViewCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
cardView.backgroundColor = .white
|
cardView.backgroundColor = AppColor.cardBackground
|
||||||
cardView.layer.cornerRadius = 12
|
cardView.layer.cornerRadius = AppRadius.lg
|
||||||
|
|
||||||
orderNumberLabel.font = .systemFont(ofSize: 14)
|
orderNumberLabel.font = .app(.body)
|
||||||
orderNumberLabel.textColor = .black
|
orderNumberLabel.textColor = AppColor.textPrimary
|
||||||
createdAtLabel.font = .systemFont(ofSize: 12)
|
createdAtLabel.font = .app(.caption)
|
||||||
createdAtLabel.textColor = UIColor(hex: 0x6B7280)
|
createdAtLabel.textColor = AppColor.textSecondary
|
||||||
typeChip.font = .systemFont(ofSize: 12)
|
typeChip.font = .app(.caption)
|
||||||
typeChip.textColor = AppColor.primary
|
typeChip.textColor = AppColor.primary
|
||||||
typeChip.backgroundColor = UIColor(hex: 0xEFF6FF)
|
typeChip.backgroundColor = AppColor.primaryLight
|
||||||
typeChip.layer.cornerRadius = 4
|
typeChip.layer.cornerRadius = AppRadius.xs
|
||||||
typeChip.clipsToBounds = true
|
typeChip.clipsToBounds = true
|
||||||
typeChip.textAlignment = .center
|
typeChip.textAlignment = .center
|
||||||
|
|
||||||
configureActionButton(refundButton, title: "退款", color: UIColor(hex: 0xEF4444), bg: UIColor(hex: 0xFFE7E7))
|
configureActionButton(refundButton, title: "退款", color: AppColor.danger, bg: AppColor.dangerBackground)
|
||||||
configureActionButton(verifyButton, title: "核销订单", color: .white, bg: UIColor(hex: 0x0073FF))
|
configureActionButton(verifyButton, title: "核销订单", color: .white, bg: AppColor.primary)
|
||||||
refundButton.addTarget(self, action: #selector(refundTapped), for: .touchUpInside)
|
refundButton.addTarget(self, action: #selector(refundTapped), for: .touchUpInside)
|
||||||
verifyButton.addTarget(self, action: #selector(verifyTapped), for: .touchUpInside)
|
verifyButton.addTarget(self, action: #selector(verifyTapped), for: .touchUpInside)
|
||||||
|
|
||||||
uploadTaskButton.setTitle("上传任务", for: .normal)
|
uploadTaskButton.setTitle("上传任务", for: .normal)
|
||||||
uploadTaskButton.setTitleColor(AppColor.primary, for: .normal)
|
uploadTaskButton.setTitleColor(AppColor.primary, for: .normal)
|
||||||
uploadTaskButton.titleLabel?.font = .systemFont(ofSize: 16)
|
uploadTaskButton.titleLabel?.font = .app(.subtitle)
|
||||||
uploadTaskButton.backgroundColor = UIColor(hex: 0xEFF6FF)
|
uploadTaskButton.backgroundColor = AppColor.primaryLight
|
||||||
uploadTaskButton.layer.cornerRadius = 12
|
uploadTaskButton.layer.cornerRadius = AppRadius.lg
|
||||||
uploadTaskButton.addTarget(self, action: #selector(uploadTaskTapped), for: .touchUpInside)
|
uploadTaskButton.addTarget(self, action: #selector(uploadTaskTapped), for: .touchUpInside)
|
||||||
|
|
||||||
actionStack.axis = .horizontal
|
actionStack.axis = .horizontal
|
||||||
actionStack.spacing = 12
|
actionStack.spacing = AppSpacing.sm
|
||||||
actionStack.distribution = .fillEqually
|
actionStack.distribution = .fillEqually
|
||||||
actionStack.addArrangedSubview(refundButton)
|
actionStack.addArrangedSubview(refundButton)
|
||||||
actionStack.addArrangedSubview(verifyButton)
|
actionStack.addArrangedSubview(verifyButton)
|
||||||
@ -143,27 +143,27 @@ final class OrderCardCell: UITableViewCell {
|
|||||||
infoStack.spacing = 6
|
infoStack.spacing = 6
|
||||||
|
|
||||||
cancelButton.setTitle("取消订单", for: .normal)
|
cancelButton.setTitle("取消订单", for: .normal)
|
||||||
cancelButton.setTitleColor(UIColor(hex: 0xEF4444), for: .normal)
|
cancelButton.setTitleColor(AppColor.danger, for: .normal)
|
||||||
cancelButton.titleLabel?.font = .systemFont(ofSize: 14)
|
cancelButton.titleLabel?.font = .app(.body)
|
||||||
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
||||||
|
|
||||||
orderSourceButton.setTitleColor(AppColor.primary, for: .normal)
|
orderSourceButton.setTitleColor(AppColor.primary, for: .normal)
|
||||||
orderSourceButton.titleLabel?.font = .systemFont(ofSize: 14)
|
orderSourceButton.titleLabel?.font = .app(.body)
|
||||||
orderSourceButton.contentHorizontalAlignment = .leading
|
orderSourceButton.contentHorizontalAlignment = .leading
|
||||||
orderSourceButton.addTarget(self, action: #selector(orderSourceTapped), for: .touchUpInside)
|
orderSourceButton.addTarget(self, action: #selector(orderSourceTapped), for: .touchUpInside)
|
||||||
|
|
||||||
historicalButton.setTitle("历史拍摄", for: .normal)
|
historicalButton.setTitle("历史拍摄", for: .normal)
|
||||||
historicalButton.setTitleColor(AppColor.primary, 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)
|
historicalButton.addTarget(self, action: #selector(historicalTapped), for: .touchUpInside)
|
||||||
|
|
||||||
refinedStack.axis = .horizontal
|
refinedStack.axis = .horizontal
|
||||||
refinedStack.spacing = 8
|
refinedStack.spacing = AppSpacing.xs
|
||||||
refinedNeedButton.setTitle("需要精修", for: .normal)
|
refinedNeedButton.setTitle("需要精修", for: .normal)
|
||||||
refinedNoButton.setTitle("无需精修", for: .normal)
|
refinedNoButton.setTitle("无需精修", for: .normal)
|
||||||
[refinedNeedButton, refinedNoButton].forEach {
|
[refinedNeedButton, refinedNoButton].forEach {
|
||||||
$0.titleLabel?.font = .systemFont(ofSize: 13)
|
$0.titleLabel?.font = .app(.body)
|
||||||
$0.layer.cornerRadius = 6
|
$0.layer.cornerRadius = AppRadius.sm
|
||||||
$0.layer.borderWidth = 1
|
$0.layer.borderWidth = 1
|
||||||
}
|
}
|
||||||
refinedNeedButton.addTarget(self, action: #selector(refinedNeedTapped), for: .touchUpInside)
|
refinedNeedButton.addTarget(self, action: #selector(refinedNeedTapped), for: .touchUpInside)
|
||||||
@ -185,7 +185,7 @@ final class OrderCardCell: UITableViewCell {
|
|||||||
cardView.addSubview(cancelButton)
|
cardView.addSubview(cancelButton)
|
||||||
|
|
||||||
cardView.snp.makeConstraints { make in
|
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
|
orderNumberLabel.snp.makeConstraints { make in
|
||||||
make.top.leading.equalToSuperview().inset(16)
|
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) {
|
private func configureActionButton(_ button: UIButton, title: String, color: UIColor, bg: UIColor) {
|
||||||
button.setTitle(title, for: .normal)
|
button.setTitle(title, for: .normal)
|
||||||
button.setTitleColor(color, for: .normal)
|
button.setTitleColor(color, for: .normal)
|
||||||
button.titleLabel?.font = .systemFont(ofSize: 16)
|
button.titleLabel?.font = .app(.subtitle)
|
||||||
button.backgroundColor = bg
|
button.backgroundColor = bg
|
||||||
button.layer.cornerRadius = 8
|
button.layer.cornerRadius = AppRadius.sm
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addInfoRow(title: String, value: String) {
|
private func addInfoRow(title: String, value: String) {
|
||||||
@ -251,12 +251,12 @@ final class OrderCardCell: UITableViewCell {
|
|||||||
row.distribution = .fillEqually
|
row.distribution = .fillEqually
|
||||||
let titleLabel = UILabel()
|
let titleLabel = UILabel()
|
||||||
titleLabel.text = title
|
titleLabel.text = title
|
||||||
titleLabel.font = .systemFont(ofSize: 13)
|
titleLabel.font = .app(.body)
|
||||||
titleLabel.textColor = UIColor(hex: 0x6B7280)
|
titleLabel.textColor = AppColor.textSecondary
|
||||||
let valueLabel = UILabel()
|
let valueLabel = UILabel()
|
||||||
valueLabel.text = value
|
valueLabel.text = value
|
||||||
valueLabel.font = .systemFont(ofSize: 13)
|
valueLabel.font = .app(.body)
|
||||||
valueLabel.textColor = AppColor.text333
|
valueLabel.textColor = AppColor.textPrimary
|
||||||
valueLabel.textAlignment = .right
|
valueLabel.textAlignment = .right
|
||||||
row.addArrangedSubview(titleLabel)
|
row.addArrangedSubview(titleLabel)
|
||||||
row.addArrangedSubview(valueLabel)
|
row.addArrangedSubview(valueLabel)
|
||||||
@ -269,12 +269,12 @@ final class OrderCardCell: UITableViewCell {
|
|||||||
row.distribution = .fillEqually
|
row.distribution = .fillEqually
|
||||||
let titleLabel = UILabel()
|
let titleLabel = UILabel()
|
||||||
titleLabel.text = "手机号"
|
titleLabel.text = "手机号"
|
||||||
titleLabel.font = .systemFont(ofSize: 13)
|
titleLabel.font = .app(.body)
|
||||||
titleLabel.textColor = UIColor(hex: 0x6B7280)
|
titleLabel.textColor = AppColor.textSecondary
|
||||||
let valueButton = UIButton(type: .system)
|
let valueButton = UIButton(type: .system)
|
||||||
valueButton.setTitle(OrderPhoneMask.mask(phone), for: .normal)
|
valueButton.setTitle(OrderPhoneMask.mask(phone), for: .normal)
|
||||||
valueButton.setTitleColor(AppColor.primary, for: .normal)
|
valueButton.setTitleColor(AppColor.primary, for: .normal)
|
||||||
valueButton.titleLabel?.font = .systemFont(ofSize: 13)
|
valueButton.titleLabel?.font = .app(.body)
|
||||||
valueButton.contentHorizontalAlignment = .trailing
|
valueButton.contentHorizontalAlignment = .trailing
|
||||||
valueButton.isHidden = hidden || phone.isEmpty
|
valueButton.isHidden = hidden || phone.isEmpty
|
||||||
valueButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)
|
valueButton.addTarget(self, action: #selector(callTapped), for: .touchUpInside)
|
||||||
@ -285,7 +285,7 @@ final class OrderCardCell: UITableViewCell {
|
|||||||
|
|
||||||
private func updateRefinedButtons(isRefined: Int, readOnly: Bool) {
|
private func updateRefinedButtons(isRefined: Int, readOnly: Bool) {
|
||||||
let selectedBorder = AppColor.primary.cgColor
|
let selectedBorder = AppColor.primary.cgColor
|
||||||
let normalBorder = UIColor(hex: 0xE5E7EB).cgColor
|
let normalBorder = AppColor.border.cgColor
|
||||||
refinedNeedButton.layer.borderColor = isRefined == 1 ? selectedBorder : normalBorder
|
refinedNeedButton.layer.borderColor = isRefined == 1 ? selectedBorder : normalBorder
|
||||||
refinedNoButton.layer.borderColor = isRefined == 2 ? selectedBorder : normalBorder
|
refinedNoButton.layer.borderColor = isRefined == 2 ? selectedBorder : normalBorder
|
||||||
refinedNeedButton.isEnabled = !readOnly
|
refinedNeedButton.isEnabled = !readOnly
|
||||||
|
|||||||
@ -6,20 +6,17 @@
|
|||||||
import SnapKit
|
import SnapKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// 订单状态标签。
|
/// 订单状态标签,内部委托 `AppStatusBadge`。
|
||||||
final class OrderStatusBadgeView: UIView {
|
final class OrderStatusBadgeView: UIView {
|
||||||
private let label = UILabel()
|
|
||||||
|
private let badge = AppStatusBadge()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
addSubview(badge)
|
||||||
label.textAlignment = .center
|
badge.snp.makeConstraints { make in
|
||||||
addSubview(label)
|
make.edges.equalToSuperview()
|
||||||
label.snp.makeConstraints { make in
|
|
||||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4))
|
|
||||||
}
|
}
|
||||||
layer.cornerRadius = 4
|
|
||||||
clipsToBounds = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
@ -28,20 +25,6 @@ final class OrderStatusBadgeView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apply(status: Int, text: String) {
|
func apply(status: Int, text: String) {
|
||||||
label.text = text
|
badge.applyOrderStatus(status, 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,6 @@ final class OrdersViewController: BaseViewController, UITableViewDataSource, UIT
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func setupUI() {
|
override func setupUI() {
|
||||||
view.backgroundColor = UIColor(hex: 0xF5F5F5)
|
|
||||||
view.addSubview(filterBar)
|
view.addSubview(filterBar)
|
||||||
view.addSubview(listView)
|
view.addSubview(listView)
|
||||||
listView.tableView.dataSource = self
|
listView.tableView.dataSource = self
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import SnapKit
|
import SnapKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
/// 个人信息页状态标签,用于账号状态、审核状态等展示。
|
/// 个人信息页状态标签,内部委托 `AppStatusBadge`。
|
||||||
final class ProfileStatusBadgeView: UIView {
|
final class ProfileStatusBadgeView: UIView {
|
||||||
|
|
||||||
enum Style {
|
enum Style {
|
||||||
@ -20,18 +20,14 @@ final class ProfileStatusBadgeView: UIView {
|
|||||||
case bankRejected
|
case bankRejected
|
||||||
}
|
}
|
||||||
|
|
||||||
private let label = UILabel()
|
private let badge = AppStatusBadge()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
label.font = .systemFont(ofSize: 12, weight: .medium)
|
addSubview(badge)
|
||||||
label.textAlignment = .center
|
badge.snp.makeConstraints { make in
|
||||||
addSubview(label)
|
make.edges.equalToSuperview()
|
||||||
label.snp.makeConstraints { make in
|
|
||||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8))
|
|
||||||
}
|
}
|
||||||
layer.cornerRadius = 4
|
|
||||||
clipsToBounds = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
@ -40,23 +36,15 @@ final class ProfileStatusBadgeView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apply(style: Style, text: String) {
|
func apply(style: Style, text: String) {
|
||||||
label.text = text
|
|
||||||
switch style {
|
switch style {
|
||||||
case .accountActive:
|
case .accountActive:
|
||||||
label.textColor = UIColor(hex: 0x22C55E)
|
badge.apply(tone: .success, text: text)
|
||||||
backgroundColor = UIColor(hex: 0xF0FDF4)
|
case .scenic, .auditApproved, .bankApproved:
|
||||||
case .scenic:
|
badge.apply(tone: .info, text: text)
|
||||||
label.textColor = AppColor.primary
|
|
||||||
backgroundColor = UIColor(hex: 0xEFF6FF)
|
|
||||||
case .auditPending, .bankPending:
|
case .auditPending, .bankPending:
|
||||||
label.textColor = UIColor(hex: 0xFF7B00)
|
badge.apply(tone: .warning, text: text)
|
||||||
backgroundColor = UIColor(hex: 0xFFF0E2)
|
|
||||||
case .auditApproved, .bankApproved:
|
|
||||||
label.textColor = AppColor.primary
|
|
||||||
backgroundColor = UIColor(hex: 0xEFF6FF)
|
|
||||||
case .auditRejected, .bankRejected:
|
case .auditRejected, .bankRejected:
|
||||||
label.textColor = UIColor(hex: 0xEF4444)
|
badge.apply(tone: .danger, text: text)
|
||||||
backgroundColor = UIColor(hex: 0xFFE7E7)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,27 +47,27 @@ final class StatisticsDailyListView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
backgroundColor = .white
|
backgroundColor = AppColor.cardBackground
|
||||||
layer.cornerRadius = 12
|
layer.cornerRadius = AppRadius.lg
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
|
|
||||||
titleLabel.text = "时间范围"
|
titleLabel.text = "时间范围"
|
||||||
titleLabel.font = .systemFont(ofSize: 14, weight: .bold)
|
titleLabel.font = .app(.bodyMedium)
|
||||||
titleLabel.textColor = .black
|
titleLabel.textColor = AppColor.textPrimary
|
||||||
|
|
||||||
rangeButton.backgroundColor = UIColor(hex: 0xF4F4F4)
|
rangeButton.backgroundColor = AppColor.inputBackground
|
||||||
rangeButton.layer.cornerRadius = 4
|
rangeButton.layer.cornerRadius = AppRadius.xs
|
||||||
rangeButton.addTarget(self, action: #selector(rangeTapped), for: .touchUpInside)
|
rangeButton.addTarget(self, action: #selector(rangeTapped), for: .touchUpInside)
|
||||||
|
|
||||||
rangeLabel.font = .systemFont(ofSize: 14)
|
rangeLabel.font = .app(.body)
|
||||||
rangeLabel.textColor = UIColor(hex: 0x4B5563)
|
rangeLabel.textColor = AppColor.textSecondary
|
||||||
rangeLabel.text = "今日"
|
rangeLabel.text = "今日"
|
||||||
|
|
||||||
arrowView.image = UIImage(systemName: "chevron.down")
|
arrowView.image = UIImage(systemName: "chevron.down")
|
||||||
arrowView.tintColor = UIColor(hex: 0x4B5563)
|
arrowView.tintColor = AppColor.textSecondary
|
||||||
arrowView.contentMode = .scaleAspectFit
|
arrowView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
divider.backgroundColor = UIColor(hex: 0xF4F4F4)
|
divider.backgroundColor = AppColor.inputBackground
|
||||||
|
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
|
|||||||
@ -28,16 +28,16 @@ final class StatisticsPeriodButton: UIButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
titleLabel?.font = .app(.bodyMedium)
|
||||||
layer.cornerRadius = 18
|
layer.cornerRadius = AppRadius.pill
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
contentEdgeInsets = UIEdgeInsets(top: 6, left: 20, bottom: 6, right: 20)
|
contentEdgeInsets = UIEdgeInsets(top: 6, left: 20, bottom: 6, right: 20)
|
||||||
updateAppearance()
|
updateAppearance()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateAppearance() {
|
private func updateAppearance() {
|
||||||
backgroundColor = isSelected ? UIColor(hex: 0x3B82F6) : UIColor(hex: 0xF3F4F6)
|
backgroundColor = isSelected ? AppColor.primary : AppColor.inputBackground
|
||||||
setTitleColor(isSelected ? .white : UIColor(hex: 0x4B5563), for: .normal)
|
setTitleColor(isSelected ? .white : AppColor.textSecondary, for: .normal)
|
||||||
titleLabel?.font = .systemFont(ofSize: 14, weight: isSelected ? .semibold : .medium)
|
titleLabel?.font = .systemFont(ofSize: 14, weight: isSelected ? .semibold : .medium)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,13 +32,13 @@ final class StatisticsStatCardView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
layer.cornerRadius = 12
|
layer.cornerRadius = AppRadius.lg
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
|
|
||||||
titleLabel.font = .systemFont(ofSize: 14)
|
titleLabel.font = .app(.body)
|
||||||
titleLabel.textColor = UIColor.black.withAlphaComponent(0.6)
|
titleLabel.textColor = AppColor.textPrimary.withAlphaComponent(0.6)
|
||||||
|
|
||||||
valueLabel.font = .systemFont(ofSize: 18, weight: .bold)
|
valueLabel.font = .app(.metric)
|
||||||
valueLabel.numberOfLines = 1
|
valueLabel.numberOfLines = 1
|
||||||
valueLabel.adjustsFontSizeToFitWidth = true
|
valueLabel.adjustsFontSizeToFitWidth = true
|
||||||
valueLabel.minimumScaleFactor = 0.8
|
valueLabel.minimumScaleFactor = 0.8
|
||||||
@ -50,16 +50,16 @@ final class StatisticsStatCardView: UIView {
|
|||||||
addSubview(iconView)
|
addSubview(iconView)
|
||||||
|
|
||||||
titleLabel.snp.makeConstraints { make in
|
titleLabel.snp.makeConstraints { make in
|
||||||
make.top.leading.equalToSuperview().inset(12)
|
make.top.leading.equalToSuperview().inset(AppSpacing.sm)
|
||||||
make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-8)
|
make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-AppSpacing.xs)
|
||||||
}
|
}
|
||||||
valueLabel.snp.makeConstraints { make in
|
valueLabel.snp.makeConstraints { make in
|
||||||
make.top.equalTo(titleLabel.snp.bottom).offset(3)
|
make.top.equalTo(titleLabel.snp.bottom).offset(3)
|
||||||
make.leading.bottom.equalToSuperview().inset(12)
|
make.leading.bottom.equalToSuperview().inset(AppSpacing.sm)
|
||||||
make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-8)
|
make.trailing.lessThanOrEqualTo(iconView.snp.leading).offset(-AppSpacing.xs)
|
||||||
}
|
}
|
||||||
iconView.snp.makeConstraints { make in
|
iconView.snp.makeConstraints { make in
|
||||||
make.trailing.equalToSuperview().inset(8)
|
make.trailing.equalToSuperview().inset(AppSpacing.xs)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
make.size.equalTo(48)
|
make.size.equalTo(48)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,8 +18,8 @@ final class StatisticsSummaryCardView: UIView {
|
|||||||
|
|
||||||
private let totalAmountCard = StatisticsStatCardView(
|
private let totalAmountCard = StatisticsStatCardView(
|
||||||
title: "订单总金额",
|
title: "订单总金额",
|
||||||
valueColor: UIColor(hex: 0x22C55E),
|
valueColor: AppColor.online,
|
||||||
backgroundColor: UIColor(hex: 0xF0FDF4),
|
backgroundColor: AppColor.successBackground,
|
||||||
iconName: "icon_data_statistics_total_amount"
|
iconName: "icon_data_statistics_total_amount"
|
||||||
)
|
)
|
||||||
private let totalOrderCard = StatisticsStatCardView(
|
private let totalOrderCard = StatisticsStatCardView(
|
||||||
@ -30,20 +30,20 @@ final class StatisticsSummaryCardView: UIView {
|
|||||||
)
|
)
|
||||||
private let receivedCard = StatisticsStatCardView(
|
private let receivedCard = StatisticsStatCardView(
|
||||||
title: "实收金额",
|
title: "实收金额",
|
||||||
valueColor: UIColor(hex: 0x22C55E),
|
valueColor: AppColor.online,
|
||||||
backgroundColor: UIColor(hex: 0xFFF0E2),
|
backgroundColor: AppColor.warningBackground,
|
||||||
iconName: "icon_data_statistics_get_amount"
|
iconName: "icon_data_statistics_get_amount"
|
||||||
)
|
)
|
||||||
private let averageCard = StatisticsStatCardView(
|
private let averageCard = StatisticsStatCardView(
|
||||||
title: "客单价",
|
title: "客单价",
|
||||||
valueColor: UIColor(hex: 0x0073FF),
|
valueColor: AppColor.primary,
|
||||||
backgroundColor: UIColor(hex: 0xEFF6FF),
|
backgroundColor: AppColor.primaryLight,
|
||||||
iconName: "icon_data_statistics_average_amount"
|
iconName: "icon_data_statistics_average_amount"
|
||||||
)
|
)
|
||||||
private let refundCard = StatisticsStatCardView(
|
private let refundCard = StatisticsStatCardView(
|
||||||
title: "退款金额",
|
title: "退款金额",
|
||||||
valueColor: UIColor(hex: 0xEF4444),
|
valueColor: AppColor.danger,
|
||||||
backgroundColor: UIColor(hex: 0xFFE7E7),
|
backgroundColor: AppColor.dangerBackground,
|
||||||
iconName: "icon_data_statistics_refund_amount"
|
iconName: "icon_data_statistics_refund_amount"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,15 +69,15 @@ final class StatisticsSummaryCardView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
backgroundColor = .white
|
backgroundColor = AppColor.cardBackground
|
||||||
layer.cornerRadius = 12
|
layer.cornerRadius = AppRadius.lg
|
||||||
clipsToBounds = true
|
clipsToBounds = true
|
||||||
|
|
||||||
datePrefixLabel.text = "已选日期:"
|
datePrefixLabel.text = "已选日期:"
|
||||||
datePrefixLabel.font = .systemFont(ofSize: 14)
|
datePrefixLabel.font = .app(.body)
|
||||||
datePrefixLabel.textColor = AppColor.text333
|
datePrefixLabel.textColor = AppColor.textPrimary
|
||||||
|
|
||||||
dateValueLabel.font = .systemFont(ofSize: 14)
|
dateValueLabel.font = .app(.body)
|
||||||
dateValueLabel.textColor = AppColor.primary
|
dateValueLabel.textColor = AppColor.primary
|
||||||
dateValueLabel.numberOfLines = 1
|
dateValueLabel.numberOfLines = 1
|
||||||
|
|
||||||
@ -95,17 +95,17 @@ final class StatisticsSummaryCardView: UIView {
|
|||||||
|
|
||||||
let row1 = UIStackView(arrangedSubviews: [totalOrderCard, receivedCard])
|
let row1 = UIStackView(arrangedSubviews: [totalOrderCard, receivedCard])
|
||||||
row1.axis = .horizontal
|
row1.axis = .horizontal
|
||||||
row1.spacing = 12
|
row1.spacing = AppSpacing.sm
|
||||||
row1.distribution = .fillEqually
|
row1.distribution = .fillEqually
|
||||||
|
|
||||||
let row2 = UIStackView(arrangedSubviews: [averageCard, refundCard])
|
let row2 = UIStackView(arrangedSubviews: [averageCard, refundCard])
|
||||||
row2.axis = .horizontal
|
row2.axis = .horizontal
|
||||||
row2.spacing = 12
|
row2.spacing = AppSpacing.sm
|
||||||
row2.distribution = .fillEqually
|
row2.distribution = .fillEqually
|
||||||
|
|
||||||
let cardStack = UIStackView(arrangedSubviews: [totalAmountCard, row1, row2])
|
let cardStack = UIStackView(arrangedSubviews: [totalAmountCard, row1, row2])
|
||||||
cardStack.axis = .vertical
|
cardStack.axis = .vertical
|
||||||
cardStack.spacing = 12
|
cardStack.spacing = AppSpacing.sm
|
||||||
|
|
||||||
let dateRow = UIStackView(arrangedSubviews: [datePrefixLabel, dateValueLabel, UIView()])
|
let dateRow = UIStackView(arrangedSubviews: [datePrefixLabel, dateValueLabel, UIView()])
|
||||||
dateRow.axis = .horizontal
|
dateRow.axis = .horizontal
|
||||||
@ -114,11 +114,11 @@ final class StatisticsSummaryCardView: UIView {
|
|||||||
|
|
||||||
let contentStack = UIStackView(arrangedSubviews: [dateRow, periodStack, cardStack])
|
let contentStack = UIStackView(arrangedSubviews: [dateRow, periodStack, cardStack])
|
||||||
contentStack.axis = .vertical
|
contentStack.axis = .vertical
|
||||||
contentStack.spacing = 12
|
contentStack.spacing = AppSpacing.sm
|
||||||
|
|
||||||
addSubview(contentStack)
|
addSubview(contentStack)
|
||||||
contentStack.snp.makeConstraints { make in
|
contentStack.snp.makeConstraints { make in
|
||||||
make.edges.equalToSuperview().inset(16)
|
make.edges.equalToSuperview().inset(AppSpacing.md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ final class StatisticsViewController: BaseViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func setupUI() {
|
override func setupUI() {
|
||||||
view.backgroundColor = UIColor(hex: 0xF7FAFF)
|
view.backgroundColor = AppColor.pageBackgroundSoft
|
||||||
|
|
||||||
view.addSubview(summaryCardView)
|
view.addSubview(summaryCardView)
|
||||||
view.addSubview(dailyListView)
|
view.addSubview(dailyListView)
|
||||||
@ -31,13 +31,13 @@ final class StatisticsViewController: BaseViewController {
|
|||||||
|
|
||||||
override func setupConstraints() {
|
override func setupConstraints() {
|
||||||
summaryCardView.snp.makeConstraints { make in
|
summaryCardView.snp.makeConstraints { make in
|
||||||
make.top.equalTo(view.safeAreaLayoutGuide).offset(16)
|
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md)
|
||||||
make.leading.trailing.equalToSuperview().inset(16)
|
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
|
||||||
}
|
}
|
||||||
dailyListView.snp.makeConstraints { make in
|
dailyListView.snp.makeConstraints { make in
|
||||||
make.top.equalTo(summaryCardView.snp.bottom).offset(16)
|
make.top.equalTo(summaryCardView.snp.bottom).offset(AppSpacing.md)
|
||||||
make.leading.trailing.equalToSuperview().inset(16)
|
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
|
||||||
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
|
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppSpacing.md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
80
suixinkanTests/AppThemeTokensTests.swift
Normal file
80
suixinkanTests/AppThemeTokensTests.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user