同步个人空间配置页面

This commit is contained in:
2026-07-08 12:31:03 +08:00
parent 8a65d3c104
commit e88cd4a9a4
6 changed files with 2280 additions and 1 deletions

View File

@ -35,8 +35,10 @@ enum HomeRouteHandler {
selectTab(.orders, from: viewController)
case "photographer_stats":
selectTab(.statistics, from: viewController)
case "system_settings", "space_settings":
case "system_settings":
selectTab(.profile, from: viewController)
case "space_settings":
viewController.navigationController?.pushViewController(ProfileSpaceSettingsViewController(), animated: true)
case "pilot_cert":
let controller = RealNameAuthViewController()
viewController.navigationController?.pushViewController(controller, animated: true)

View File

@ -21,6 +21,50 @@ final class ProfileAPI {
)
}
///
func profileInfo(scenicId: Int) async throws -> PhotographerProfileInfoResponse {
try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/profile/info",
queryItems: [URLQueryItem(name: "scenic_id", value: String(scenicId))]
)
)
}
///
func updateProfileInfo(_ request: UpdatePhotographerProfileInfoRequest) async throws {
let _: EmptyPayload = try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/profile/info-update",
body: request
)
)
}
///
func addSchedule(_ request: AddPhotographerScheduleRequest) async throws {
let _: EmptyPayload = try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/schedule/add",
body: request
)
)
}
///
func deleteSchedule(id: Int) async throws {
let _: EmptyPayload = try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/schedule/delete",
body: PhotographerScheduleDeleteRequest(id: id)
)
)
}
///
func switchableAccounts() async throws -> V9AuthResponse {
try await client.send(

View File

@ -62,6 +62,241 @@ struct UpdateInfoRequest: Encodable {
let avatar: String?
}
/// Android `ProfileInfoResponse`
struct PhotographerProfileInfoResponse: Decodable, Equatable {
let id: Int
let scenicId: Int
let photogUid: Int
let realName: String
let nickname: String
let avatar: String
let scenicCertification: [String]
let description: String
let attrLabel: [String]
let shootLabel: [String]
let cameraDevice: [String]
let businessStartTime: String
let businessEndTime: String
let holidayStartTime: String
let holidayEndTime: String
let businessRange: [Int]
let acceptOrderStatus: Int
let schedule: [String: [PhotographerSchedule]]
let virtualPhone: String
enum CodingKeys: String, CodingKey {
case id
case scenicId = "scenic_id"
case photogUid = "photog_uid"
case realName = "real_name"
case nickname
case avatar
case scenicCertification = "scenic_certification"
case description
case attrLabel = "attr_label"
case shootLabel = "shoot_label"
case cameraDevice = "camera_device"
case businessStartTime = "business_start_time"
case businessEndTime = "business_end_time"
case holidayStartTime = "holiday_start_time"
case holidayEndTime = "holiday_end_time"
case businessRange = "business_range"
case acceptOrderStatus = "accept_order_status"
case schedule
case virtualPhone = "virtual_phone"
}
init(
id: Int = 0,
scenicId: Int = 0,
photogUid: Int = 0,
realName: String = "",
nickname: String = "",
avatar: String = "",
scenicCertification: [String] = [],
description: String = "",
attrLabel: [String] = [],
shootLabel: [String] = [],
cameraDevice: [String] = [],
businessStartTime: String = "",
businessEndTime: String = "",
holidayStartTime: String = "",
holidayEndTime: String = "",
businessRange: [Int] = [],
acceptOrderStatus: Int = 0,
schedule: [String: [PhotographerSchedule]] = [:],
virtualPhone: String = ""
) {
self.id = id
self.scenicId = scenicId
self.photogUid = photogUid
self.realName = realName
self.nickname = nickname
self.avatar = avatar
self.scenicCertification = scenicCertification
self.description = description
self.attrLabel = attrLabel
self.shootLabel = shootLabel
self.cameraDevice = cameraDevice
self.businessStartTime = businessStartTime
self.businessEndTime = businessEndTime
self.holidayStartTime = holidayStartTime
self.holidayEndTime = holidayEndTime
self.businessRange = businessRange
self.acceptOrderStatus = acceptOrderStatus
self.schedule = schedule
self.virtualPhone = virtualPhone
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
realName = try container.decodeLossyString(forKey: .realName)
nickname = try container.decodeLossyString(forKey: .nickname)
avatar = try container.decodeLossyString(forKey: .avatar)
scenicCertification = try container.decodeLossyStringArray(forKey: .scenicCertification)
description = try container.decodeLossyString(forKey: .description)
attrLabel = try container.decodeLossyStringArray(forKey: .attrLabel)
shootLabel = try container.decodeLossyStringArray(forKey: .shootLabel)
cameraDevice = try container.decodeLossyStringArray(forKey: .cameraDevice)
businessStartTime = try container.decodeLossyString(forKey: .businessStartTime)
businessEndTime = try container.decodeLossyString(forKey: .businessEndTime)
holidayStartTime = try container.decodeLossyString(forKey: .holidayStartTime)
holidayEndTime = try container.decodeLossyString(forKey: .holidayEndTime)
businessRange = try container.decodeLossyIntArray(forKey: .businessRange)
acceptOrderStatus = try container.decodeLossyInt(forKey: .acceptOrderStatus) ?? 0
schedule = try container.decodeIfPresent([String: [PhotographerSchedule]].self, forKey: .schedule) ?? [:]
virtualPhone = try container.decodeLossyString(forKey: .virtualPhone)
}
}
/// Android `ScheduleEntity`
struct PhotographerSchedule: Decodable, Equatable, Hashable, Sendable {
let id: Int
let name: String
let remark: String
let startTime: String
let endTime: String
let scheduleDate: String
let orderNumber: String?
let userNickname: String?
let userPhone: String?
let orderStatus: Int?
let orderStatusName: String?
enum CodingKeys: String, CodingKey {
case id
case name
case remark
case startTime = "start_time"
case endTime = "end_time"
case scheduleDate = "schedule_date"
case orderNumber = "order_number"
case userNickname = "user_nickname"
case userPhone = "user_phone"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
}
init(
id: Int = 0,
name: String = "",
remark: String = "",
startTime: String = "",
endTime: String = "",
scheduleDate: String = "",
orderNumber: String? = nil,
userNickname: String? = nil,
userPhone: String? = nil,
orderStatus: Int? = nil,
orderStatusName: String? = nil
) {
self.id = id
self.name = name
self.remark = remark
self.startTime = startTime
self.endTime = endTime
self.scheduleDate = scheduleDate
self.orderNumber = orderNumber
self.userNickname = userNickname
self.userPhone = userPhone
self.orderStatus = orderStatus
self.orderStatusName = orderStatusName
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
name = try container.decodeLossyString(forKey: .name)
remark = try container.decodeLossyString(forKey: .remark)
startTime = try container.decodeLossyString(forKey: .startTime)
endTime = try container.decodeLossyString(forKey: .endTime)
scheduleDate = try container.decodeLossyString(forKey: .scheduleDate)
orderNumber = try container.decodeIfPresent(String.self, forKey: .orderNumber)
userNickname = try container.decodeIfPresent(String.self, forKey: .userNickname)
userPhone = try container.decodeIfPresent(String.self, forKey: .userPhone)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus)
orderStatusName = try container.decodeIfPresent(String.self, forKey: .orderStatusName)
}
}
/// Android `UpdateProfileInfoRequest`
struct UpdatePhotographerProfileInfoRequest: Encodable, Equatable {
let scenicId: String
let description: String
let cameraDevice: [String]
let attrLabel: [String]
let shootLabel: [String]
let businessStartTime: String
let businessEndTime: String
let holidayStartTime: String
let holidayEndTime: String
let businessRange: [Int]
let acceptOrderStatus: Int
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case description
case cameraDevice = "camera_device"
case attrLabel = "attr_label"
case shootLabel = "shoot_label"
case businessStartTime = "business_start_time"
case businessEndTime = "business_end_time"
case holidayStartTime = "holiday_start_time"
case holidayEndTime = "holiday_end_time"
case businessRange = "business_range"
case acceptOrderStatus = "accept_order_status"
}
}
/// Android `AddScheduleRequest`
struct AddPhotographerScheduleRequest: Encodable, Equatable {
var scenicId: String
var name: String
var remark: String
var startTime: String
var endTime: String
var scheduleDate: String
var orderNumber: String?
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case name
case remark
case startTime = "start_time"
case endTime = "end_time"
case scheduleDate = "schedule_date"
case orderNumber = "order_number"
}
}
/// Android `ScheduleDeleteRequest`
struct PhotographerScheduleDeleteRequest: Encodable, Equatable {
let id: Int
}
///
struct RealNameInfoResponse: Decodable, Equatable {
let realNameInfo: RealNameInfo?
@ -350,4 +585,31 @@ private extension KeyedDecodingContainer {
}
return nil
}
func decodeLossyStringArray(forKey key: Key) throws -> [String] {
if let values = try? decodeIfPresent([String].self, forKey: key) {
return values
}
if let values = try? decodeIfPresent([Int].self, forKey: key) {
return values.map(String.init)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? [] : [text]
}
return []
}
func decodeLossyIntArray(forKey key: Key) throws -> [Int] {
if let values = try? decodeIfPresent([Int].self, forKey: key) {
return values
}
if let values = try? decodeIfPresent([String].self, forKey: key) {
return values.compactMap { Int($0.trimmingCharacters(in: .whitespacesAndNewlines)) }
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return [value]
}
return []
}
}

View File

@ -0,0 +1,546 @@
//
// ProfileSpaceSettingsViewModel.swift
// suixinkan
//
import Foundation
/// 便 API
@MainActor
protocol ProfileSpaceSettingsAPI {
///
func profileInfo(scenicId: Int) async throws -> PhotographerProfileInfoResponse
///
func updateProfileInfo(_ request: UpdatePhotographerProfileInfoRequest) async throws
///
func updateUserInfo(nickname: String?, password: String?, avatar: String?) async throws
///
func addSchedule(_ request: AddPhotographerScheduleRequest) async throws
///
func deleteSchedule(id: Int) async throws
}
extension ProfileAPI: ProfileSpaceSettingsAPI {}
///
enum ProfileSpaceSettingsValidationError: LocalizedError, Equatable {
case emptyTime(String)
case invalidTimeRange(String)
case emptyLabel
case duplicateLabel
case labelTooLong
case labelLimitReached
case emptyDevice
case emptyScheduleName
case emptyScheduleTime
case invalidScheduleTime
var errorDescription: String? {
switch self {
case .emptyTime(let title):
"请输入\(title)"
case .invalidTimeRange(let title):
"\(title)开始时间不能大于结束时间"
case .emptyLabel:
"标签不能为空"
case .duplicateLabel:
"标签已存在"
case .labelTooLong:
"标签最多20个字符"
case .labelLimitReached:
"最多8个标签"
case .emptyDevice:
"设备名称不能为空"
case .emptyScheduleName:
"请输入日程名称"
case .emptyScheduleTime:
"请选择日程时间"
case .invalidScheduleTime:
"结束时间必须晚于开始时间"
}
}
}
/// ViewModel Android `ProfileInfoViewModel`
final class ProfileSpaceSettingsViewModel {
private(set) var avatarURL = ""
private(set) var realName = ""
private(set) var nickname = ""
private(set) var editingNickname = ""
private(set) var introduction = ""
private(set) var scenicCertification: [String] = []
private(set) var attrLabels: [String] = []
private(set) var shootLabels: [String] = []
private(set) var businessStartTime: String?
private(set) var businessEndTime: String?
private(set) var holidayStartTime: String?
private(set) var holidayEndTime: String?
private(set) var cameraDevices: [String] = []
private(set) var acceptOrderStatus = 0
private(set) var scheduleMap: [String: [PhotographerSchedule]] = [:]
private(set) var selectedDate: Date
private(set) var isEditingProfile = false
private(set) var isLoading = false
private(set) var hasChanges = false
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
private var originalSnapshot = Snapshot()
private let appStore: AppStore
private let calendar: Calendar
private let nowProvider: () -> Date
init(
appStore: AppStore = .shared,
calendar: Calendar = Calendar(identifier: .gregorian),
nowProvider: @escaping () -> Date = Date.init
) {
self.appStore = appStore
self.calendar = calendar
self.nowProvider = nowProvider
selectedDate = calendar.startOfDay(for: nowProvider())
}
/// ID
var scenicId: Int {
appStore.currentScenicId
}
///
var selectedDateSchedules: [PhotographerSchedule] {
scheduleMap[Self.dateKey(from: selectedDate, calendar: calendar)] ?? []
}
/// 7
var weekDates: [Date] {
let start = calendar.startOfDay(for: nowProvider())
return (0..<7).compactMap { calendar.date(byAdding: .day, value: $0, to: start) }
}
///
func load(api: any ProfileSpaceSettingsAPI) async {
guard scenicId > 0 else {
onShowMessage?("请先选择景区")
return
}
setLoading(true)
defer { setLoading(false) }
do {
let response = try await api.profileInfo(scenicId: scenicId)
apply(response)
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
}
/// 退 Android
func toggleProfileEditing(api: any ProfileSpaceSettingsAPI) async {
if isEditingProfile {
let newNickname = editingNickname.trimmingCharacters(in: .whitespacesAndNewlines)
let currentNickname = nickname.trimmingCharacters(in: .whitespacesAndNewlines)
guard !newNickname.isEmpty, newNickname != currentNickname else {
isEditingProfile = false
notifyStateChange()
return
}
setLoading(true)
defer { setLoading(false) }
do {
try await api.updateUserInfo(nickname: newNickname, password: nil, avatar: nil)
nickname = newNickname
appStore.userName = newNickname
isEditingProfile = false
onShowMessage?("修改成功")
notifyStateChange()
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
return
}
editingNickname = nickname
isEditingProfile = true
notifyStateChange()
}
/// URL
func updateAvatarURL(_ url: String) {
avatarURL = url
appStore.avatar = url
notifyStateChange()
}
///
func updateEditingNickname(_ value: String) {
editingNickname = value
notifyStateChange()
}
///
func updateIntroduction(_ value: String) {
introduction = value
updateHasChanges()
}
///
func addAttrLabel(_ value: String) {
do {
attrLabels = try adding(label: value, to: attrLabels)
updateHasChanges()
} catch {
onShowMessage?(error.localizedDescription)
}
}
///
func removeAttrLabel(_ label: String) {
attrLabels.removeAll { $0 == label }
updateHasChanges()
}
///
func addShootLabel(_ value: String) {
do {
shootLabels = try adding(label: value, to: shootLabels)
updateHasChanges()
} catch {
onShowMessage?(error.localizedDescription)
}
}
///
func removeShootLabel(_ label: String) {
shootLabels.removeAll { $0 == label }
updateHasChanges()
}
///
func updateBusinessStartTime(_ value: String) {
businessStartTime = value
if let end = businessEndTime, !Self.isStart(value, before: end) {
onShowMessage?("开始时间不能大于结束时间")
businessStartTime = nil
}
updateHasChanges()
}
///
func updateBusinessEndTime(_ value: String) {
businessEndTime = value
if let start = businessStartTime, !Self.isStart(start, before: value) {
onShowMessage?("结束时间不能小于开始时间")
businessEndTime = nil
}
updateHasChanges()
}
///
func updateHolidayStartTime(_ value: String) {
holidayStartTime = value
if let end = holidayEndTime, !Self.isStart(value, before: end) {
onShowMessage?("开始时间不能大于结束时间")
holidayStartTime = nil
}
updateHasChanges()
}
///
func updateHolidayEndTime(_ value: String) {
holidayEndTime = value
if let start = holidayStartTime, !Self.isStart(start, before: value) {
onShowMessage?("结束时间不能小于开始时间")
holidayEndTime = nil
}
updateHasChanges()
}
///
func addDevice(_ value: String) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else {
onShowMessage?(ProfileSpaceSettingsValidationError.emptyDevice.localizedDescription)
return
}
cameraDevices.append(text)
updateHasChanges()
}
///
func removeDevice(_ value: String) {
cameraDevices.removeAll { $0 == value }
updateHasChanges()
}
///
func updateAcceptOrderStatus(_ status: Int) {
acceptOrderStatus = status
updateHasChanges()
}
///
func selectDate(_ date: Date) {
selectedDate = calendar.startOfDay(for: date)
notifyStateChange()
}
///
func save(api: any ProfileSpaceSettingsAPI) async {
do {
let request = try makeSaveRequest()
setLoading(true)
defer { setLoading(false) }
try await api.updateProfileInfo(request)
originalSnapshot = currentSnapshot()
hasChanges = false
onShowMessage?("保存成功")
notifyStateChange()
await load(api: api)
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
}
///
func makeSaveRequest() throws -> UpdatePhotographerProfileInfoRequest {
guard let businessStartTime else { throw ProfileSpaceSettingsValidationError.emptyTime("工作日开始时间") }
guard let businessEndTime else { throw ProfileSpaceSettingsValidationError.emptyTime("工作日结束时间") }
guard let holidayStartTime else { throw ProfileSpaceSettingsValidationError.emptyTime("节假日开始时间") }
guard let holidayEndTime else { throw ProfileSpaceSettingsValidationError.emptyTime("节假日结束时间") }
guard Self.isStart(businessStartTime, before: businessEndTime) else {
throw ProfileSpaceSettingsValidationError.invalidTimeRange("工作日")
}
guard Self.isStart(holidayStartTime, before: holidayEndTime) else {
throw ProfileSpaceSettingsValidationError.invalidTimeRange("节假日")
}
return UpdatePhotographerProfileInfoRequest(
scenicId: String(scenicId),
description: introduction,
cameraDevice: cameraDevices,
attrLabel: attrLabels,
shootLabel: shootLabels,
businessStartTime: Self.fullTimeString(businessStartTime),
businessEndTime: Self.fullTimeString(businessEndTime),
holidayStartTime: Self.fullTimeString(holidayStartTime),
holidayEndTime: Self.fullTimeString(holidayEndTime),
businessRange: [],
acceptOrderStatus: acceptOrderStatus
)
}
///
func makeAddScheduleRequest(
name: String,
remark: String,
startTime: String?,
endTime: String?,
scheduleDate: Date,
orderNumber: String?
) throws -> AddPhotographerScheduleRequest {
let scheduleName = name.trimmingCharacters(in: .whitespacesAndNewlines)
guard !scheduleName.isEmpty else { throw ProfileSpaceSettingsValidationError.emptyScheduleName }
guard let startTime, let endTime else { throw ProfileSpaceSettingsValidationError.emptyScheduleTime }
guard Self.isStart(startTime, before: endTime) else { throw ProfileSpaceSettingsValidationError.invalidScheduleTime }
let order = orderNumber?.trimmingCharacters(in: .whitespacesAndNewlines)
return AddPhotographerScheduleRequest(
scenicId: String(scenicId),
name: scheduleName,
remark: remark,
startTime: startTime,
endTime: endTime,
scheduleDate: Self.dateKey(from: scheduleDate, calendar: calendar),
orderNumber: order?.isEmpty == true ? nil : order
)
}
///
func addSchedule(
name: String,
remark: String,
startTime: String?,
endTime: String?,
scheduleDate: Date,
orderNumber: String?,
api: any ProfileSpaceSettingsAPI
) async -> Bool {
do {
let request = try makeAddScheduleRequest(
name: name,
remark: remark,
startTime: startTime,
endTime: endTime,
scheduleDate: scheduleDate,
orderNumber: orderNumber
)
setLoading(true)
defer { setLoading(false) }
try await api.addSchedule(request)
onShowMessage?("添加成功")
await load(api: api)
return true
} catch is CancellationError {
return false
} catch {
onShowMessage?(error.localizedDescription)
return false
}
}
///
func deleteSchedule(id: Int, api: any ProfileSpaceSettingsAPI) async {
setLoading(true)
defer { setLoading(false) }
do {
try await api.deleteSchedule(id: id)
onShowMessage?("删除成功")
await load(api: api)
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
}
private func apply(_ response: PhotographerProfileInfoResponse) {
realName = response.realName
nickname = response.nickname
editingNickname = response.nickname
if !response.avatar.isEmpty {
avatarURL = response.avatar
appStore.avatar = response.avatar
}
scenicCertification = response.scenicCertification
introduction = response.description
attrLabels = response.attrLabel
shootLabels = response.shootLabel
cameraDevices = response.cameraDevice
businessStartTime = Self.shortTimeString(response.businessStartTime)
businessEndTime = Self.shortTimeString(response.businessEndTime)
holidayStartTime = Self.shortTimeString(response.holidayStartTime)
holidayEndTime = Self.shortTimeString(response.holidayEndTime)
acceptOrderStatus = response.acceptOrderStatus
scheduleMap = response.schedule
appStore.userName = response.nickname
appStore.realName = response.realName
originalSnapshot = currentSnapshot()
hasChanges = false
notifyStateChange()
}
private func adding(label rawValue: String, to labels: [String]) throws -> [String] {
let label = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !label.isEmpty else { throw ProfileSpaceSettingsValidationError.emptyLabel }
guard label.count <= 20 else { throw ProfileSpaceSettingsValidationError.labelTooLong }
guard labels.count < 8 else { throw ProfileSpaceSettingsValidationError.labelLimitReached }
guard !labels.contains(label) else { throw ProfileSpaceSettingsValidationError.duplicateLabel }
return labels + [label]
}
private func setLoading(_ loading: Bool) {
isLoading = loading
notifyStateChange()
}
private func updateHasChanges() {
hasChanges = currentSnapshot() != originalSnapshot
notifyStateChange()
}
private func notifyStateChange() {
onStateChange?()
}
private func currentSnapshot() -> Snapshot {
Snapshot(
introduction: introduction,
attrLabels: attrLabels,
shootLabels: shootLabels,
businessStartTime: businessStartTime,
businessEndTime: businessEndTime,
holidayStartTime: holidayStartTime,
holidayEndTime: holidayEndTime,
cameraDevices: cameraDevices,
acceptOrderStatus: acceptOrderStatus
)
}
private static func shortTimeString(_ value: String) -> String? {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty, text != "00:00:00" else { return nil }
if text.count >= 5 {
return String(text.prefix(5))
}
return nil
}
static func fullTimeString(_ value: String) -> String {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
return text.count == 5 ? "\(text):00" : text
}
static func isStart(_ start: String, before end: String) -> Bool {
minutes(from: start) < minutes(from: end)
}
static func minutes(from value: String) -> Int {
let pieces = value.split(separator: ":").compactMap { Int($0) }
guard pieces.count >= 2 else { return 0 }
return pieces[0] * 60 + pieces[1]
}
static func dateKey(from date: Date, calendar: Calendar) -> String {
let formatter = DateFormatter()
formatter.calendar = calendar
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = calendar.timeZone
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: date)
}
/// Android yyyyMd
static func displayDate(_ date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> String {
let components = calendar.dateComponents([.year, .month, .day], from: date)
return "\(components.year ?? 0)\(components.month ?? 0)\(components.day ?? 0)"
}
///
static func displayDay(_ date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> String {
String(calendar.component(.day, from: date))
}
///
static func displayWeekday(_ date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> String {
switch calendar.component(.weekday, from: date) {
case 1: return ""
case 2: return ""
case 3: return ""
case 4: return ""
case 5: return ""
case 6: return ""
case 7: return ""
default: return ""
}
}
}
private struct Snapshot: Equatable {
var introduction = ""
var attrLabels: [String] = []
var shootLabels: [String] = []
var businessStartTime: String?
var businessEndTime: String?
var holidayStartTime: String?
var holidayEndTime: String?
var cameraDevices: [String] = []
var acceptOrderStatus = 0
}

File diff suppressed because it is too large Load Diff