新增门店订单与核销管理列表,对齐 Android 订单能力并完善账号级缓存。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 14:46:10 +08:00
parent c4de3d17d0
commit 08492df6ce
51 changed files with 3668 additions and 102 deletions

View File

@ -0,0 +1,276 @@
//
// OrderListComponents.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import SwiftUI
/// UI
enum OrderListUI {
///
static func statusColors(for status: Int) -> (text: Color, background: Color) {
switch status {
case 18:
return (AppDesign.orderStatusPaidText, AppDesign.orderStatusPaidBackground)
case 30:
return (AppDesign.orderStatusCompletedText, AppDesign.orderStatusCompletedBackground)
case 50:
return (AppDesign.orderStatusRefundedText, AppDesign.orderStatusRefundedBackground)
default:
return (AppDesign.orderStatusOtherText, AppDesign.orderStatusOtherBackground)
}
}
///
static func maskPhone(_ phone: String) -> String {
let trimmed = phone.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.count >= 7 else { return trimmed.isEmpty ? "--" : trimmed }
return "\(trimmed.prefix(3))****\(trimmed.suffix(4))"
}
/// depositPayTime
static func displayPayTime(payTime: String, depositPayTime: String) -> String {
if payTime.isEmpty {
return depositPayTime.isEmpty ? "--" : depositPayTime
}
if payTime.hasPrefix("1970") || payTime == "0" {
return depositPayTime.isEmpty ? payTime : depositPayTime
}
return payTime
}
}
/// Chip Android OrderView
struct OrderStatusChip: View {
let text: String
let status: Int
var body: some View {
let colors = OrderListUI.statusColors(for: status)
Text(text.isEmpty ? "--" : text)
.font(.system(size: 14))
.foregroundStyle(colors.text)
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(colors.background, in: RoundedRectangle(cornerRadius: 4))
}
}
///
struct OrderInfoRow: View {
let title: String
let value: String
var valueColor: Color = .black
var trailing: AnyView?
init(title: String, value: String, valueColor: Color = .black, @ViewBuilder trailing: () -> some View = { EmptyView() }) {
self.title = title
self.value = value
self.valueColor = valueColor
let built = trailing()
self.trailing = built is EmptyView ? nil : AnyView(built)
}
var body: some View {
HStack(alignment: .center, spacing: 8) {
Text(title)
.font(.system(size: 14))
.foregroundStyle(AppDesign.orderLabelColor)
Spacer(minLength: 8)
if let trailing {
trailing
} else {
Text(value)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(valueColor)
.multilineTextAlignment(.trailing)
.lineLimit(2)
.minimumScaleFactor(0.75)
}
}
}
}
/// Android AppButton
struct OrderActionButton: View {
let title: String
var textColor: Color = .white
var backgroundColor: Color = AppDesign.primary
var maxWidth: CGFloat? = .infinity
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.system(size: 16))
.foregroundStyle(textColor)
.frame(maxWidth: maxWidth)
.frame(height: 48)
.background(backgroundColor, in: RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
}
}
///
struct OrderPhoneCallButton: View {
let action: () -> Void
var body: some View {
Button(action: action) {
Image(systemName: "phone.fill")
.font(.system(size: 12))
.foregroundStyle(AppDesign.primary)
.frame(width: 24, height: 24)
.background(AppDesign.primarySoft, in: Circle())
}
.buttonStyle(.plain)
}
}
/// Android ExclusiveChoiceButtons
struct OrderRefinedChoiceSection: View {
let currentSelection: Int
let readOnly: Bool
let onSelectionChange: (Int) -> Void
var body: some View {
HStack {
Text("需要剪辑修图")
.font(.system(size: 14))
.foregroundStyle(AppDesign.orderLabelColor)
Spacer()
if readOnly {
Text(refinedDisplayText)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(.black)
} else {
HStack(spacing: 16) {
refinedChoiceButton(title: "", value: 1)
refinedChoiceButton(title: "", value: 2)
}
}
}
}
private var refinedDisplayText: String {
switch currentSelection {
case 1: return ""
case 2: return ""
default: return "--"
}
}
private func refinedChoiceButton(title: String, value: Int) -> some View {
let selected = currentSelection == value
return Button {
onSelectionChange(value)
} label: {
HStack(spacing: 4) {
ZStack {
Circle()
.stroke(AppDesign.primary, lineWidth: selected ? 0 : 1.5)
.background(selected ? Circle().fill(AppDesign.primary) : nil)
.frame(width: 20, height: 20)
if selected {
Image(systemName: "checkmark")
.font(.system(size: 10, weight: .bold))
.foregroundStyle(.white)
}
}
Text(title)
.font(.system(size: 14))
.foregroundStyle(.black)
}
}
.buttonStyle(.plain)
}
}
/// /
struct OrderGiftStepperRow: View {
let title: String
let valueText: String
let editable: Bool
let onTap: () -> Void
var body: some View {
HStack {
Text(title)
.font(.system(size: 14))
.foregroundStyle(AppDesign.orderLabelColor)
Spacer()
if editable {
HStack(spacing: 8) {
giftStepButton(symbol: "-", action: onTap)
Text(valueText)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(.black)
giftStepButton(symbol: "+", action: onTap)
}
} else {
Text(valueText)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(.black)
}
}
}
private func giftStepButton(symbol: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
Text(symbol)
.font(.system(size: 16, weight: .bold))
.foregroundStyle(Color(hex: 0xE5E7EB))
.frame(width: 24, height: 24)
.overlay(Circle().stroke(Color(hex: 0xE5E7EB), lineWidth: 1))
}
.buttonStyle(.plain)
}
}
/// pill Tab
struct WriteOffStatusTabBar: View {
let filters: [OrderStatusFilter]
let selectedStatus: Int
let onSelect: (Int) -> Void
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(filters) { filter in
let selected = selectedStatus == filter.id
Button {
onSelect(filter.id)
} label: {
Text(filter.title)
.font(.system(size: 14))
.foregroundStyle(selected ? .white : AppDesign.orderLabelColor)
.padding(.horizontal, 12)
.frame(height: 32)
.background(
selected ? AppDesign.primary : Color(hex: 0xF3F4F6),
in: RoundedRectangle(cornerRadius: 16)
)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal, 16)
}
}
}
/// `.searchable`
enum OrderListSearchBehavior {
///
static func normalized(_ text: String) -> String {
text.trimmingCharacters(in: .whitespacesAndNewlines)
}
///
static func shouldReloadAfterClear(newText: String, appliedQuery: String) -> Bool {
normalized(newText).isEmpty && !appliedQuery.isEmpty
}
}