fixed some bugs

This commit is contained in:
2026-09-07 11:42:53 +08:00
parent aca36c78c1
commit 9230db3b69
12 changed files with 883 additions and 14 deletions

View File

@@ -6,6 +6,7 @@ struct RootView: View {
@StateObject private var auth = AuthManager()
@StateObject private var controller = PlayerController()
@StateObject private var catalogVM = CatalogViewModel()
@StateObject private var historyManager = TrainingHistoryManager()
@State private var activeModule: Module = .course
@State private var continuousPlayback = false
@State private var showImporter = false
@@ -14,6 +15,11 @@ struct RootView: View {
@State private var currentCourse: Course?
@State private var courseStatusText = "正在同步"
@State private var showCourseGuardToast = false
@State private var managedResults: [UserResultSummary] = []
@State private var managedDubShares: [UserDubShareSummary] = []
@State private var managementStatus = ""
@State private var managementIsLoading = false
@State private var deletingHistoryID: String?
enum Module {
case course
@@ -49,7 +55,11 @@ struct RootView: View {
onFullscreen: enterFullscreen
)
case .test:
TestView(controller: controller, onFullscreen: enterFullscreen)
TestView(
controller: controller,
historyManager: historyManager,
onFullscreen: enterFullscreen
)
case .mine:
minePage
}
@@ -276,10 +286,195 @@ struct RootView: View {
.font(.system(size: 13))
.foregroundColor(Theme.textMuted)
.padding(.horizontal, 16)
HStack(spacing: 10) {
Button {
Task { await loadManagedHistory() }
} label: {
Text(managementIsLoading ? "刷新中..." : "刷新记录")
.font(.system(size: 13, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 14)
.frame(height: 36)
.background(Theme.buttonBlue)
.clipShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(.plain)
.disabled(managementIsLoading)
if !managementStatus.isEmpty {
Text(managementStatus)
.font(.system(size: 12))
.foregroundColor(Theme.textMuted)
}
}
.padding(.horizontal, 16)
historyGroup(title: "我的测试结果", isEmpty: managedResults.isEmpty) {
ForEach(managedResults) { row in
resultRow(row)
}
}
historyGroup(title: "我的配音分享", isEmpty: managedDubShares.isEmpty) {
ForEach(managedDubShares) { row in
dubShareRow(row)
}
}
}
.padding(.bottom, 24)
}
.task(id: currentCourse?.videoHash) {
await loadManagedHistory()
}
}
private func historyGroup<Content: View>(
title: String,
isEmpty: Bool,
@ViewBuilder content: () -> Content
) -> some View {
VStack(alignment: .leading, spacing: 10) {
Text(title)
.font(.system(size: 18, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 16)
if isEmpty {
Text("该课程暂无记录")
.font(.system(size: 13))
.foregroundColor(Theme.textMuted)
.padding(14)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Theme.surface)
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding(.horizontal, 16)
} else {
LazyVStack(spacing: 8, content: content)
.padding(.horizontal, 16)
}
}
}
private func resultRow(_ row: UserResultSummary) -> some View {
historyRow(
title: "第 \(row.sentenceIndex + 1) 句 · \(String(format: "%.0f", row.overallScore)) 分",
subtitle: "\(row.passed ? "通过" : "未通过") · \(displayDate(row.createdAt))\n\(row.sentenceText)",
isDeleting: deletingHistoryID == row.id,
deleteLabel: "删除"
) {
await deleteResult(row)
}
}
private func dubShareRow(_ row: UserDubShareSummary) -> some View {
historyRow(
title: row.title,
subtitle: "\(row.segmentCount) 句 · 平均 \(String(format: "%.0f", row.averageScore)) 分 · \(displayDate(row.createdAt))",
isDeleting: deletingHistoryID == row.id,
deleteLabel: "删除"
) {
await deleteDubShare(row)
}
}
private func historyRow(
title: String,
subtitle: String,
isDeleting: Bool,
deleteLabel: String,
action: @escaping () async -> Void
) -> some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.system(size: 15, weight: .bold))
.foregroundColor(Theme.textDark)
Text(subtitle)
.font(.system(size: 12))
.foregroundColor(Theme.textSubtle)
.fixedSize(horizontal: false, vertical: true)
HStack {
Spacer()
Button {
Task { await action() }
} label: {
Text(isDeleting ? "删除中..." : deleteLabel)
.font(.system(size: 12, weight: .bold))
.foregroundColor(.white)
.padding(.horizontal, 12)
.frame(height: 28)
.background(isDeleting ? Theme.border : Theme.danger)
.clipShape(RoundedRectangle(cornerRadius: 7))
}
.buttonStyle(.plain)
.disabled(isDeleting)
}
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Theme.surface)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Theme.lightBorder, lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
private func loadManagedHistory() async {
guard let course = currentCourse else {
managementStatus = "请先选择课程"
managedResults = []
managedDubShares = []
return
}
managementIsLoading = true
managementStatus = "正在加载..."
defer { managementIsLoading = false }
do {
async let resultsTask = UserHistoryAPI.results(token: auth.token)
async let sharesTask = UserHistoryAPI.dubShares(token: auth.token)
let (allResults, allShares) = try await (resultsTask, sharesTask)
managedResults = allResults.filter { $0.videoHash.caseInsensitiveCompare(course.videoHash) == .orderedSame }
managedDubShares = allShares.filter { $0.videoHash.caseInsensitiveCompare(course.videoHash) == .orderedSame }
managementStatus = "共 \(managedResults.count) 条测试、\(managedDubShares.count) 条配音"
} catch {
managementStatus = "记录加载失败:\(error.localizedDescription)"
}
}
private func deleteResult(_ row: UserResultSummary) async {
guard let course = currentCourse, deletingHistoryID == nil else { return }
deletingHistoryID = row.id
defer { deletingHistoryID = nil }
do {
try await UserHistoryAPI.deleteResult(token: auth.token, attemptId: row.attemptId)
managedResults.removeAll { $0.id == row.id }
historyManager.store.removeScore(videoHash: course.videoHash, sentenceIndex: row.sentenceIndex)
historyManager.notifyHistoryChanged()
managementStatus = "测试记录已删除"
} catch {
managementStatus = "删除失败:\(error.localizedDescription)"
}
}
private func deleteDubShare(_ row: UserDubShareSummary) async {
guard let course = currentCourse, deletingHistoryID == nil else { return }
deletingHistoryID = row.id
defer { deletingHistoryID = nil }
do {
try await UserHistoryAPI.deleteDubShare(token: auth.token, shareId: row.shareId)
managedDubShares.removeAll { $0.id == row.id }
historyManager.store.removeAllAudio(videoHash: course.videoHash)
historyManager.notifyHistoryChanged()
managementStatus = "配音分享已删除"
} catch {
managementStatus = "删除失败:\(error.localizedDescription)"
}
}
private func displayDate(_ value: String) -> String {
value.replacingOccurrences(of: "T", with: " ").prefix(16).description
}
// MARK: - Load Courses
private func loadCourses() async {