Introduce travel album entry with Sony PTP tethering pipeline, profile space settings page, home routing updates, Launch Screen storyboard, and related tests/docs. Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
7.0 KiB
Swift
159 lines
7.0 KiB
Swift
//
|
||
// CreateTravelAlbumSheet.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/29.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
/// 新建相册任务弹窗,支持先拍再买与买了再拍两种模式。
|
||
struct CreateTravelAlbumSheet: View {
|
||
let orders: [TravelAlbumAvailableOrder]
|
||
let isCreating: Bool
|
||
let onConfirm: (Int, String, String, String, TravelAlbumAvailableOrder?) -> Void
|
||
|
||
@Environment(\.dismiss) private var dismiss
|
||
|
||
@State private var mode = TravelAlbumEntryViewModel.modePreShoot
|
||
@State private var freeCount = ""
|
||
@State private var singlePrice = ""
|
||
@State private var packagePrice = ""
|
||
@State private var selectedOrder: TravelAlbumAvailableOrder?
|
||
|
||
var body: some View {
|
||
NavigationStack {
|
||
ScrollView {
|
||
VStack(spacing: AppMetrics.Spacing.medium) {
|
||
modeCard(
|
||
title: "先拍再买",
|
||
description: "拍完后分享给用户,用户在小程序上选择性购买",
|
||
selected: mode == TravelAlbumEntryViewModel.modePreShoot
|
||
) {
|
||
mode = TravelAlbumEntryViewModel.modePreShoot
|
||
}
|
||
|
||
modeCard(
|
||
title: "买了再拍",
|
||
description: "用户已在小程序下单,绑定订单后用户可直接选片",
|
||
selected: mode == TravelAlbumEntryViewModel.modePreOrder
|
||
) {
|
||
mode = TravelAlbumEntryViewModel.modePreOrder
|
||
}
|
||
|
||
if mode == TravelAlbumEntryViewModel.modePreShoot {
|
||
preShootFields
|
||
} else {
|
||
preOrderFields
|
||
}
|
||
}
|
||
.padding(AppMetrics.Spacing.pageHorizontal)
|
||
.padding(.vertical, AppMetrics.Spacing.medium)
|
||
}
|
||
.background(Color(hex: 0xF5F7FA))
|
||
.navigationTitle("新建相册任务")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbar {
|
||
ToolbarItem(placement: .cancellationAction) {
|
||
Button("取消") { dismiss() }
|
||
}
|
||
ToolbarItem(placement: .confirmationAction) {
|
||
Button(isCreating ? "创建中" : "确认创建") {
|
||
onConfirm(mode, freeCount, singlePrice, packagePrice, selectedOrder)
|
||
}
|
||
.disabled(isCreating)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private var preShootFields: some View {
|
||
VStack(spacing: AppMetrics.Spacing.small) {
|
||
fieldRow(title: "免费张数", placeholder: "请输入免费张数", text: $freeCount, keyboard: .numberPad)
|
||
fieldRow(title: "单张价格(元)", placeholder: "请输入单张照片价格", text: $singlePrice, keyboard: .decimalPad)
|
||
fieldRow(title: "打包价(元)", placeholder: "可选", text: $packagePrice, keyboard: .decimalPad)
|
||
}
|
||
}
|
||
|
||
private var preOrderFields: some View {
|
||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) {
|
||
Text("选择绑定订单")
|
||
.font(.system(size: AppMetrics.FontSize.subheadline, weight: .medium))
|
||
.foregroundStyle(AppDesign.textSecondary)
|
||
|
||
if orders.isEmpty {
|
||
Text("暂无可绑定订单")
|
||
.foregroundStyle(AppDesign.placeholder)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.padding()
|
||
.background(Color.white)
|
||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||
} else {
|
||
ForEach(orders) { order in
|
||
Button {
|
||
selectedOrder = order
|
||
} label: {
|
||
HStack {
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text(order.projectName)
|
||
.font(.system(size: AppMetrics.FontSize.body, weight: .medium))
|
||
.foregroundStyle(AppDesign.textPrimary)
|
||
Text("\(order.orderNumber) · \(order.userPhone)")
|
||
.font(.system(size: AppMetrics.FontSize.caption))
|
||
.foregroundStyle(AppDesign.textSecondary)
|
||
}
|
||
Spacer()
|
||
Image(systemName: selectedOrder?.orderNumber == order.orderNumber ? "checkmark.circle.fill" : "circle")
|
||
.foregroundStyle(selectedOrder?.orderNumber == order.orderNumber ? AppDesign.primary : AppDesign.placeholder)
|
||
}
|
||
.padding(AppMetrics.Spacing.medium)
|
||
.background(Color.white)
|
||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private func modeCard(title: String, description: String, selected: Bool, action: @escaping () -> Void) -> some View {
|
||
Button(action: action) {
|
||
HStack(alignment: .top, spacing: AppMetrics.Spacing.medium) {
|
||
Image(systemName: selected ? "largecircle.fill.circle" : "circle")
|
||
.foregroundStyle(selected ? AppDesign.primary : AppDesign.placeholder)
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text(title)
|
||
.font(.system(size: AppMetrics.FontSize.body, weight: .semibold))
|
||
.foregroundStyle(AppDesign.textPrimary)
|
||
Text(description)
|
||
.font(.system(size: AppMetrics.FontSize.caption))
|
||
.foregroundStyle(AppDesign.textSecondary)
|
||
.multilineTextAlignment(.leading)
|
||
}
|
||
Spacer()
|
||
}
|
||
.padding(AppMetrics.Spacing.large)
|
||
.background(Color.white)
|
||
.overlay(
|
||
RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)
|
||
.stroke(selected ? AppDesign.primary : Color.clear, lineWidth: 1.5)
|
||
)
|
||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
|
||
private func fieldRow(title: String, placeholder: String, text: Binding<String>, keyboard: UIKeyboardType) -> some View {
|
||
VStack(alignment: .leading, spacing: 6) {
|
||
Text(title)
|
||
.font(.system(size: AppMetrics.FontSize.caption))
|
||
.foregroundStyle(AppDesign.textSecondary)
|
||
TextField(placeholder, text: text)
|
||
.keyboardType(keyboard)
|
||
.padding(AppMetrics.Spacing.medium)
|
||
.background(Color.white)
|
||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||
}
|
||
}
|
||
}
|