|
@@ -1215,7 +1215,7 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
guard loadingOverlay == nil else { return }
|
|
guard loadingOverlay == nil else { return }
|
|
|
let overlay = UIView()
|
|
let overlay = UIView()
|
|
|
overlay.translatesAutoresizingMaskIntoConstraints = false
|
|
overlay.translatesAutoresizingMaskIntoConstraints = false
|
|
|
- overlay.backgroundColor = UIColor.black.withAlphaComponent(0.35)
|
|
|
|
|
|
|
+ overlay.backgroundColor = UIColor.clear
|
|
|
overlay.isUserInteractionEnabled = true // block touches
|
|
overlay.isUserInteractionEnabled = true // block touches
|
|
|
|
|
|
|
|
let spinner = UIActivityIndicatorView(style: .large)
|
|
let spinner = UIActivityIndicatorView(style: .large)
|
|
@@ -1670,8 +1670,49 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
let selectedId: String?
|
|
let selectedId: String?
|
|
|
let isFirst = (selectedButton == choiceBtnA)
|
|
let isFirst = (selectedButton == choiceBtnA)
|
|
|
if isFirst { selectedId = option1ItemId } else { selectedId = option2ItemId }
|
|
if isFirst { selectedId = option1ItemId } else { selectedId = option2ItemId }
|
|
|
|
|
+
|
|
|
|
|
+ guard let sid = selectedId else {
|
|
|
|
|
+ print("[答题] 选项ID为空")
|
|
|
|
|
+ resetAnswerState()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示加载状态
|
|
|
|
|
+ showSystemLoading()
|
|
|
|
|
+// verdictLabel.text = "提交中..."
|
|
|
|
|
|
|
|
- // Always显示正确答案标记
|
|
|
|
|
|
|
+ // 计算答题耗时
|
|
|
|
|
+ let durationSec: Int = {
|
|
|
|
|
+ if let start = questionStartAt { return max(0, Int(Date().timeIntervalSince(start))) }
|
|
|
|
|
+ return 0
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
|
|
+ // 先请求API,成功后再显示结果
|
|
|
|
|
+ answerQuestion(questionId: q.id, itemId: sid, duration: durationSec, selectedButton: selectedButton) { [weak self] success in
|
|
|
|
|
+ guard let self = self else { return }
|
|
|
|
|
+
|
|
|
|
|
+ DispatchQueue.main.async {
|
|
|
|
|
+ self.hideSystemLoading()
|
|
|
|
|
+
|
|
|
|
|
+ if success {
|
|
|
|
|
+ // API成功,显示答题结果
|
|
|
|
|
+ self.showAnswerResult(question: q, selectedId: sid, selectedButton: selectedButton, isFirst: isFirst)
|
|
|
|
|
+
|
|
|
|
|
+ // 1秒后进入下一题
|
|
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
|
|
|
|
+ self?.goToNextQuestion()
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // API失败,重置状态允许重新答题
|
|
|
|
|
+ self.resetAnswerState()
|
|
|
|
|
+ self.showToast(message: "提交失败,请重试")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示答题结果(正确/错误标记)
|
|
|
|
|
+ private func showAnswerResult(question: QAQuestion, selectedId: String, selectedButton: UIButton, isFirst: Bool) {
|
|
|
let correctImage = UIImage(named: "res_cwqxdp40")
|
|
let correctImage = UIImage(named: "res_cwqxdp40")
|
|
|
let wrongImage = UIImage(named: "res_4h4cvu8h")
|
|
let wrongImage = UIImage(named: "res_4h4cvu8h")
|
|
|
|
|
|
|
@@ -1680,11 +1721,11 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
markB.isHidden = true
|
|
markB.isHidden = true
|
|
|
|
|
|
|
|
// 正确项标绿勾
|
|
// 正确项标绿勾
|
|
|
- if option1ItemId == q.correctItemId { markA.image = correctImage; markA.isHidden = false }
|
|
|
|
|
- if option2ItemId == q.correctItemId { markB.image = correctImage; markB.isHidden = false }
|
|
|
|
|
|
|
+ if option1ItemId == question.correctItemId { markA.image = correctImage; markA.isHidden = false }
|
|
|
|
|
+ if option2ItemId == question.correctItemId { markB.image = correctImage; markB.isHidden = false }
|
|
|
|
|
|
|
|
// 如果选错,则在所选按钮旁显示红叉
|
|
// 如果选错,则在所选按钮旁显示红叉
|
|
|
- let isCorrect = (selectedId == q.correctItemId)
|
|
|
|
|
|
|
+ let isCorrect = (selectedId == question.correctItemId)
|
|
|
if !isCorrect {
|
|
if !isCorrect {
|
|
|
if isFirst { markA.image = wrongImage; markA.isHidden = false }
|
|
if isFirst { markA.image = wrongImage; markA.isHidden = false }
|
|
|
else { markB.image = wrongImage; markB.isHidden = false }
|
|
else { markB.image = wrongImage; markB.isHidden = false }
|
|
@@ -1699,40 +1740,48 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
} else {
|
|
} else {
|
|
|
selectedButton.setBackgroundImage(UIImage(named: "res_zup626a7"), for: .normal)
|
|
selectedButton.setBackgroundImage(UIImage(named: "res_zup626a7"), for: .normal)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 计算答题耗时并上报
|
|
|
|
|
- let durationSec: Int = {
|
|
|
|
|
- if let start = questionStartAt { return max(0, Int(Date().timeIntervalSince(start))) }
|
|
|
|
|
- return 0
|
|
|
|
|
- }()
|
|
|
|
|
- if let sid = selectedId {
|
|
|
|
|
- answerQuestion(questionId: q.id, itemId: sid, duration: durationSec)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 1秒后进入下一题/结束
|
|
|
|
|
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
|
|
|
|
- self?.goToNextQuestion()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置答题状态(用于失败时恢复)
|
|
|
|
|
+ private func resetAnswerState() {
|
|
|
|
|
+ isAnswering = false
|
|
|
|
|
+
|
|
|
|
|
+ // 恢复按钮状态(根据体力判断)
|
|
|
|
|
+ let currentPower = UserDefaults.standard.object(forKey: "power") as? Int ?? 0
|
|
|
|
|
+ let enabled = currentPower > 0
|
|
|
|
|
+ choiceBtnA.isEnabled = enabled
|
|
|
|
|
+ choiceBtnB.isEnabled = enabled
|
|
|
|
|
+
|
|
|
|
|
+ // 恢复按钮背景
|
|
|
|
|
+ choiceBtnA.setBackgroundImage(UIImage(named: "res_o67j6d3t"), for: .normal)
|
|
|
|
|
+ choiceBtnB.setBackgroundImage(UIImage(named: "res_o67j6d3t"), for: .normal)
|
|
|
|
|
+
|
|
|
|
|
+ // 隐藏标记
|
|
|
|
|
+ markA.isHidden = true
|
|
|
|
|
+ markB.isHidden = true
|
|
|
|
|
+
|
|
|
|
|
+ // 恢复提示文字
|
|
|
|
|
+ verdictLabel.text = "请选择正确答案"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Answer Question API
|
|
// MARK: - Answer Question API
|
|
|
- private func answerQuestion(questionId: String, itemId: String, duration: Int) {
|
|
|
|
|
|
|
+ private func answerQuestion(questionId: String, itemId: String, duration: Int, selectedButton: UIButton, completion: @escaping (Bool) -> Void) {
|
|
|
guard let url = URL(string: apiBaseURL + answerQuestionPath) else {
|
|
guard let url = URL(string: apiBaseURL + answerQuestionPath) else {
|
|
|
print("[Answer] Invalid URL")
|
|
print("[Answer] Invalid URL")
|
|
|
|
|
+ completion(false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
let userId = accountInfo?.userId ?? (UserDefaults.standard.string(forKey: "roleID") ?? "")
|
|
let userId = accountInfo?.userId ?? (UserDefaults.standard.string(forKey: "roleID") ?? "")
|
|
|
guard !userId.isEmpty else {
|
|
guard !userId.isEmpty else {
|
|
|
print("[Answer] Missing userId")
|
|
print("[Answer] Missing userId")
|
|
|
- DispatchQueue.main.async { [weak self] in
|
|
|
|
|
- self?.showToast(message: "缺少用户ID,无法上报作答")
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ completion(false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var request = URLRequest(url: url)
|
|
var request = URLRequest(url: url)
|
|
|
request.httpMethod = "POST"
|
|
request.httpMethod = "POST"
|
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
|
|
|
+ request.timeoutInterval = 15 // 设置15秒超时
|
|
|
let body: [String: Any] = [
|
|
let body: [String: Any] = [
|
|
|
"duration": duration,
|
|
"duration": duration,
|
|
|
"itemId": itemId,
|
|
"itemId": itemId,
|
|
@@ -1758,21 +1807,20 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
}
|
|
}
|
|
|
if let error = error {
|
|
if let error = error {
|
|
|
print("[Answer] error: \(error)")
|
|
print("[Answer] error: \(error)")
|
|
|
- DispatchQueue.main.async { [weak self] in
|
|
|
|
|
- self?.showToast(message: "网络异常,作答上报失败")
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ completion(false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
guard let data = data,
|
|
guard let data = data,
|
|
|
let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
|
|
let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
|
|
|
|
|
+ print("[Answer] Invalid response data")
|
|
|
|
|
+ completion(false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
let code = obj["code"] as? Int ?? 0
|
|
let code = obj["code"] as? Int ?? 0
|
|
|
if code != 200 {
|
|
if code != 200 {
|
|
|
let msg = (obj["message"] as? String) ?? (obj["msg"] as? String) ?? "作答上报失败"
|
|
let msg = (obj["message"] as? String) ?? (obj["msg"] as? String) ?? "作答上报失败"
|
|
|
- DispatchQueue.main.async { [weak self] in
|
|
|
|
|
- self?.showToast(message: msg)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ print("[Answer] Response code not 200: \(msg)")
|
|
|
|
|
+ completion(false)
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1791,11 +1839,14 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
DispatchQueue.main.async { [weak self] in
|
|
DispatchQueue.main.async { [weak self] in
|
|
|
- guard let self = self else { return }
|
|
|
|
|
|
|
+ guard let self = self else {
|
|
|
|
|
+ completion(false)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
// 1) 扣体力
|
|
// 1) 扣体力
|
|
|
- let currentPower = UserDefaults.standard.object(forKey: "power") as? Int ?? 0
|
|
|
|
|
- let newPower = max(0, currentPower - 1)
|
|
|
|
|
- self.staminaBadge.setTitle("体力:\(newPower)", for: .normal)
|
|
|
|
|
|
|
+ let currentPower = UserDefaults.standard.object(forKey: "power") as? Int ?? 0
|
|
|
|
|
+ let newPower = max(0, currentPower - 1)
|
|
|
|
|
+ self.staminaBadge.setTitle("体力:\(newPower)", for: .normal)
|
|
|
self.applyPowerToOptionButtons(newPower)
|
|
self.applyPowerToOptionButtons(newPower)
|
|
|
UserDefaults.standard.set(newPower, forKey: "power")
|
|
UserDefaults.standard.set(newPower, forKey: "power")
|
|
|
if newPower == 0 { self.showToast(message: "体力不足,请先领取") }
|
|
if newPower == 0 { self.showToast(message: "体力不足,请先领取") }
|
|
@@ -1842,6 +1893,9 @@ class QuizStageController: UIViewController, ATAdLoadingDelegate, ATBannerDelega
|
|
|
self.accountInfo = info
|
|
self.accountInfo = info
|
|
|
self.activePowerPopup?.configure(with: info)
|
|
self.activePowerPopup?.configure(with: info)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // API请求成功,调用完成回调
|
|
|
|
|
+ completion(true)
|
|
|
}
|
|
}
|
|
|
}.resume()
|
|
}.resume()
|
|
|
}
|
|
}
|