feat: 重造领域模型并对齐 android(home/checkin/plan/profile/session)

This commit is contained in:
AndyChen1024
2026-09-15 14:35:52 +08:00
parent ac4492a8f1
commit 14eb5ad44d
6 changed files with 313 additions and 40 deletions

View File

@@ -0,0 +1,10 @@
import Foundation
/// 会话(极简,仅 token)。
public struct Session: Hashable, Sendable {
public let token: String
public init(token: String) {
self.token = token
}
}

View File

@@ -0,0 +1,72 @@
import Foundation
/// 打卡状态(对应小程序 STATUSES / android CheckinStatus)。
public enum CheckinStatus: String, CaseIterable, Hashable, Sendable {
case normal = "正常"
case late = "迟到"
case leave = "请假"
case absent = "不参加讨论"
case earlyLeave = "早退"
}
/// 小组成员(打卡视角)。
public struct GroupMember: Identifiable, Hashable, Sendable {
public let id: String
public let name: String
public let checkedIn: Bool
public let checkinTime: String?
public let status: CheckinStatus?
public init(id: String, name: String, checkedIn: Bool, checkinTime: String?, status: CheckinStatus?) {
self.id = id
self.name = name
self.checkedIn = checkedIn
self.checkinTime = checkinTime
self.status = status
}
}
/// 个人打卡历史条目。
public struct PersonalCheckinRecord: Hashable, Sendable {
public let date: String
public let status: CheckinStatus
public let time: String
public init(date: String, status: CheckinStatus, time: String) {
self.date = date
self.status = status
self.time = time
}
}
/// 打卡页聚合数据。
public struct CheckinRecord: Hashable, Sendable {
public let classDate: String
public let weekday: String
public let dayNum: Int
public let checked: Bool
public let status: CheckinStatus?
public let groupName: String
public let groupMembers: [GroupMember]
public let personalHistory: [PersonalCheckinRecord]
public init(
classDate: String,
weekday: String,
dayNum: Int,
checked: Bool,
status: CheckinStatus?,
groupName: String,
groupMembers: [GroupMember],
personalHistory: [PersonalCheckinRecord]
) {
self.classDate = classDate
self.weekday = weekday
self.dayNum = dayNum
self.checked = checked
self.status = status
self.groupName = groupName
self.groupMembers = groupMembers
self.personalHistory = personalHistory
}
}

View File

@@ -1,27 +0,0 @@
import Foundation
/// 一个打卡习惯(对应 Android 端 Habit)。
public struct Habit: Identifiable, Hashable, Sendable {
public let id: String
public let title: String
/// 备注 / 目标描述。
public let detail: String
/// 今日是否已打卡。
public let isCheckedInToday: Bool
/// 连续打卡天数。
public let streakDays: Int
public init(
id: String,
title: String,
detail: String,
isCheckedInToday: Bool,
streakDays: Int
) {
self.id = id
self.title = title
self.detail = detail
self.isCheckedInToday = isCheckedInToday
self.streakDays = streakDays
}
}

View File

@@ -1,19 +1,65 @@
import Foundation
/// Home 页面的聚合数据(对应 Android 端 HomeData)。
public struct HomeData: Identifiable, Hashable, Sendable {
public let id: String
/// 顶部问候语,例如「早上好,Andy」。
public let greeting: String
/// 今日打卡摘要,例如「已完成 2 / 4」。
public let summary: String
/// 今日待打卡的习惯列表。
public let habits: [Habit]
/// 首页模块跳转目标。
public enum ModuleDestination: Hashable, Sendable {
case checkin
case plan
case about
}
public init(id: String, greeting: String, summary: String, habits: [Habit]) {
/// 首页功能模块卡片(打卡介绍 / 计划介绍 / 关于我们)。
public struct HomeModule: Identifiable, Hashable, Sendable {
public let id: String
public let emoji: String
public let title: String
public let description: String
public let destination: ModuleDestination
public init(
id: String,
emoji: String,
title: String,
description: String,
destination: ModuleDestination
) {
self.id = id
self.emoji = emoji
self.title = title
self.description = description
self.destination = destination
}
}
/// 产品介绍卡片。
public struct IntroCard: Hashable, Sendable {
public let title: String
public let body: String
public init(title: String, body: String) {
self.title = title
self.body = body
}
}
/// 首页聚合数据(落地页)。
public struct HomeData: Hashable, Sendable {
public let greeting: String
public let displayName: String
public let encouragement: String
public let modules: [HomeModule]
public let intros: [IntroCard]
public init(
greeting: String,
displayName: String,
encouragement: String,
modules: [HomeModule],
intros: [IntroCard]
) {
self.greeting = greeting
self.summary = summary
self.habits = habits
self.displayName = displayName
self.encouragement = encouragement
self.modules = modules
self.intros = intros
}
}

View File

@@ -0,0 +1,119 @@
import Foundation
/// 计划类型(周/月/年)。
public enum PlanType: String, CaseIterable, Hashable, Sendable {
case weekly
case monthly
case yearly
public var label: String {
switch self {
case .weekly: return "周计划"
case .monthly: return "月计划"
case .yearly: return "年计划"
}
}
}
/// 我提交的计划记录。
public struct MyPlanRecord: Hashable, Sendable {
public let status: String
public let submittedTime: String
public let content: String
public let attachments: [String]
public init(status: String, submittedTime: String, content: String, attachments: [String]) {
self.status = status
self.submittedTime = submittedTime
self.content = content
self.attachments = attachments
}
}
/// 小组成员(计划视角)。
public struct PlanMember: Identifiable, Hashable, Sendable {
public let id: String
public let name: String
public let filled: Bool
public init(id: String, name: String, filled: Bool) {
self.id = id
self.name = name
self.filled = filled
}
}
/// 小组计划统计(含成员)。
public struct PlanGroup: Identifiable, Hashable, Sendable {
public let id: String
public let name: String
public let checkedCount: Int
public let memberCount: Int
public let members: [PlanMember]
public init(id: String, name: String, checkedCount: Int, memberCount: Int, members: [PlanMember]) {
self.id = id
self.name = name
self.checkedCount = checkedCount
self.memberCount = memberCount
self.members = members
}
}
/// 计划页聚合数据。
public struct PlanRecord: Hashable, Sendable {
public let type: PlanType
public let periodKey: String
public let canSubmit: Bool
public let message: String?
public let myChecked: Bool
public let myRecord: MyPlanRecord?
public let groups: [PlanGroup]
public let unassignedCount: Int
public init(
type: PlanType,
periodKey: String,
canSubmit: Bool,
message: String?,
myChecked: Bool,
myRecord: MyPlanRecord?,
groups: [PlanGroup],
unassignedCount: Int
) {
self.type = type
self.periodKey = periodKey
self.canSubmit = canSubmit
self.message = message
self.myChecked = myChecked
self.myRecord = myRecord
self.groups = groups
self.unassignedCount = unassignedCount
}
}
/// 提交计划页数据。
public struct PlanSubmitData: Hashable, Sendable {
public let content: String
public let attachments: [String]
public let checked: Bool
public let canSubmit: Bool
public let message: String?
public let history: [MyPlanRecord]
public init(
content: String,
attachments: [String],
checked: Bool,
canSubmit: Bool,
message: String?,
history: [MyPlanRecord]
) {
self.content = content
self.attachments = attachments
self.checked = checked
self.canSubmit = canSubmit
self.message = message
self.history = history
}
}

View File

@@ -0,0 +1,53 @@
import Foundation
/// 用户所在的小组分组。
public struct ClassGroup: Identifiable, Hashable, Sendable {
public let period: String
public let group: String
public let role: String
public var id: String { period }
public init(period: String, group: String, role: String) {
self.period = period
self.group = group
self.role = role
}
}
/// 个人资料。
public struct UserProfile: Identifiable, Hashable, Sendable {
public let displayName: String
public let name: String
public let studentId: String
public let phone: String?
public let role: String
public let group: String?
public let loginType: String
public let avatarUrl: String?
public let classGroups: [ClassGroup]
public var id: String { studentId }
public init(
displayName: String,
name: String,
studentId: String,
phone: String?,
role: String,
group: String?,
loginType: String,
avatarUrl: String?,
classGroups: [ClassGroup]
) {
self.displayName = displayName
self.name = name
self.studentId = studentId
self.phone = phone
self.role = role
self.group = group
self.loginType = loginType
self.avatarUrl = avatarUrl
self.classGroups = classGroups
}
}