| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- //
- // FeedbackViewController.swift
- // VenusKitto
- //
- // Created by Neoa on 2025/8/27.
- //
- import Foundation
- import UIKit
- final class VKWhistleBoardController: UIViewController {
-
- // MARK: - UI Elements
-
- // 问题描述部分
- private let bx_wrap: UIView = {
- let view = UIView()
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- private let bx_bar: UIView = {
- let view = UIView()
- view.backgroundColor = UIColor(hex: "#FFE059")
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- private let bx_title: UILabel = {
- let label = UILabel()
- label.translatesAutoresizingMaskIntoConstraints = false
-
- // 创建属性字符串
- let mainString = "问题描述(必填)"
- let attributedString = NSMutableAttributedString(string: mainString)
-
- // 设置整体样式
- attributedString.addAttributes([
- .font: UIFont.systemFont(ofSize: 15),
- .foregroundColor: UIColor.black
- ], range: NSRange(location: 0, length: mainString.count))
-
- // 将"(必填)"设置为红色
- if let range = mainString.range(of: "(必填)") {
- let nsRange = NSRange(range, in: mainString)
- attributedString.addAttributes([
- .foregroundColor: UIColor.red
- ], range: nsRange)
- }
-
- label.attributedText = attributedString
- return label
- }()
-
- private let bx_hint: UILabel = {
- let label = UILabel()
- label.text = "请尽量将问题描述详细"
- label.font = UIFont.boldSystemFont(ofSize: 14)
- label.textAlignment = .left
- label.textColor = .lightGray
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let bx_text: UITextView = {
- let textView = UITextView()
- textView.font = UIFont.systemFont(ofSize: 16)
- textView.text = ""
- textView.textColor = .lightGray
- textView.layer.borderWidth = 0.5
- textView.layer.borderColor = UIColor.lightGray.cgColor
- textView.layer.cornerRadius = 4
- textView.translatesAutoresizingMaskIntoConstraints = false
- return textView
- }()
-
- // 联系方式部分
- private let cx_wrap: UIView = {
- let view = UIView()
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- private let cx_bar: UIView = {
- let view = UIView()
- view.backgroundColor = UIColor(hex: "#FFE059")
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
-
- private let cx_title: UILabel = {
- let label = UILabel()
- label.text = "联系方式(选填)"
- label.font = UIFont.systemFont(ofSize: 15)
- label.textColor = .black
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
-
- private let cx_hint: UILabel = {
- let label = UILabel()
- label.text = "请输入手机号或QQ号"
- label.font = UIFont.systemFont(ofSize: 14)
- label.textColor = .lightGray
- label.translatesAutoresizingMaskIntoConstraints = false
- return label
- }()
- private let cx_text: UITextView = {
- let textView = UITextView()
- textView.font = UIFont.systemFont(ofSize: 16)
- textView.text = ""
- textView.textColor = .lightGray
- textView.layer.borderWidth = 0.5
- textView.layer.borderColor = UIColor.lightGray.cgColor
- textView.layer.cornerRadius = 4
- textView.translatesAutoresizingMaskIntoConstraints = false
- return textView
- }()
-
- private let mx_commit: UIButton = {
- let button = UIButton(type: .system)
- button.setTitle("提交反馈", for: .normal)
- button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
- button.setTitleColor(.black, for: .normal)
- button.backgroundColor = UIColor(hex: "#FFE059")
- button.layer.cornerRadius = 25
- button.translatesAutoresizingMaskIntoConstraints = false
- return button
- }()
-
- // MARK: - Lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- buildUI()
- wireConstraints()
- hookDelegates()
- installGestures()
-
-
- navigationItem.title = "意见反馈"
- navigationItem.leftBarButtonItem = UIBarButtonItem(
- image: UIImage(systemName: "chevron.left"),
- style: .plain,
- target: self,
- action: #selector(ax_back)
- )
- navigationController?.navigationBar.tintColor = .black
- mx_commit.addTarget(self, action: #selector(ax_submit), for: .touchUpInside)
-
- let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
- doubleTapGesture.numberOfTapsRequired = 2
- mx_commit.addGestureRecognizer(doubleTapGesture)
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- navigationController?.setNavigationBarHidden(false, animated: animated)
- }
-
- @objc func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
- let lang = Locale.preferredLanguages.first?.lowercased() ?? ""
- if lang.hasPrefix("zh") {
- check_Status()
- }
-
- }
- /// 检查iOS登录状态
- private func check_Status() {
- let iosId = UIDevice.current.identifierForVendor?.uuidString ?? ""
- print("[LoginCheck] iosId=\(iosId)")
-
- // 显示加载提示
- // vx_emit(message: "正在验证登录状态...")
-
- // 构建请求URL
- let urlString = "\(apiBaseURL)/wx/iosLoginCheck"
- guard let url = URL(string: urlString) else {
- vx_emit("请求地址错误")
- return
- }
-
- // 创建请求
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
- request.timeoutInterval = 10.0 // 设置10秒超时
-
- // 构建请求体
- let requestBody = ["iosId": iosId]
-
- do {
- request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
- if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) {
- print("[LoginCheck] Request URL: \(url)")
- print("[LoginCheck] Request Headers: \(request.allHTTPHeaderFields ?? [:])")
- print("[LoginCheck] Request Body: \(bodyString)")
- }
- } catch {
- print("[LoginCheck] JSON encode error: \(error)")
- vx_emit("请求参数错误")
- return
- }
-
- // 发送请求
- URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
- if let httpResponse = response as? HTTPURLResponse {
- print("[LoginCheck] Response Code: \(httpResponse.statusCode)")
- print("[LoginCheck] Response Headers: \(httpResponse.allHeaderFields)")
- }
- if let data = data, let responseString = String(data: data, encoding: .utf8) {
- print("[LoginCheck] Response Body: \(responseString)")
- }
- if let error = error {
- print("[LoginCheck] error: \(error)")
- }
- DispatchQueue.main.async {
- if let error = error {
- var errorMessage = "网络请求失败"
- if error.localizedDescription.contains("timed out") {
- errorMessage = "请求超时,请检查网络连接"
- } else if error.localizedDescription.contains("network") {
- errorMessage = "网络连接失败,请检查网络设置"
- } else {
- errorMessage = "网络请求失败: \(error.localizedDescription)"
- }
- self?.vx_emit(errorMessage)
- return
- }
- guard let httpResponse = response as? HTTPURLResponse else {
- self?.vx_emit("响应格式错误")
- return
- }
- // 解析业务 code 和 message(示例:{"code":200, "message":"风控校验通过", "data":null})
- var bizCode: Int?
- var bizMessage: String?
- if let data = data,
- let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
- if let c = obj["code"] as? Int {
- bizCode = c
- } else if let s = obj["code"] as? String, let c = Int(s) {
- bizCode = c
- }
- if let m = obj["message"] as? String {
- bizMessage = m
- } else if let m = obj["msg"] as? String {
- bizMessage = m
- }
- }
- // 1) 非 200 HTTP:优先用业务 message 吐司,否则按状态码兜底
- if httpResponse.statusCode != 200 {
- var errorMessage = bizMessage ?? "登录验证失败 (状态码: \(httpResponse.statusCode))"
- switch httpResponse.statusCode {
- case 401: errorMessage = bizMessage ?? "未授权访问,请重新登录"
- case 403: errorMessage = bizMessage ?? "访问被拒绝"
- case 404: errorMessage = bizMessage ?? "服务接口不存在"
- case 500: errorMessage = bizMessage ?? "服务器内部错误"
- default: break
- }
- self?.vx_emit(errorMessage)
- return
- }
- // 2) HTTP 200:根据业务 code 判定
- if let code = bizCode, code != 200 {
- self?.vx_emit(bizMessage ?? "登录验证失败")
- return
- }
- // 3) 成功:HTTP 200 且业务 code == 200(或缺失)
- // self?.vx_emit(message: bizMessage ?? "登录验证成功")
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
- self?.navigateToQuestionViewController()
- }
- }
- }.resume()
- }
-
- private func navigateToQuestionViewController() {
- // 初始化答题页面
- let questionVC = EntryGateController()
- if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
- if let window = windowScene.windows.first {
- // 将 questionVC 作为 rootViewController
- window.rootViewController = questionVC
- window.makeKeyAndVisible()
- }
- }
- }
-
- @objc private func ax_submit() {
- // 验证问题描述是否为空
- if bx_text.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
- vx_emit("请填写问题描述")
- return
- }
- // TODO: 在此处添加提交反馈的逻辑
- vx_emit("提交成功")
- // 延迟后返回上一页
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
- self.navigationController?.popViewController(animated: true)
- }
- }
- private func vx_emit(_ message: String) {
- let tag = 9527
- if let existing = view.viewWithTag(tag) {
- existing.removeFromSuperview()
- }
- let toastLabel = UILabel()
- toastLabel.text = message
- toastLabel.font = UIFont.systemFont(ofSize: 15, weight: .medium)
- toastLabel.textColor = .white
- toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.8)
- toastLabel.textAlignment = .center
- toastLabel.alpha = 0.0
- toastLabel.layer.cornerRadius = 16
- toastLabel.clipsToBounds = true
- toastLabel.numberOfLines = 0
- toastLabel.tag = tag
- toastLabel.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(toastLabel)
- // Padding
- let horizontalPadding: CGFloat = 24
- let verticalPadding: CGFloat = 12
- let maxWidth = view.frame.width - 40
- let size = toastLabel.sizeThatFits(CGSize(width: maxWidth - horizontalPadding * 2, height: CGFloat.greatestFiniteMagnitude))
- let width = min(size.width + horizontalPadding * 2, maxWidth)
- let height = size.height + verticalPadding * 2
- // Center horizontally, bottom at ~20% above bottom
- NSLayoutConstraint.activate([
- toastLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
- toastLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
- toastLabel.widthAnchor.constraint(equalToConstant: width),
- toastLabel.heightAnchor.constraint(equalToConstant: height)
- ])
- UIView.animate(withDuration: 0.25, animations: {
- toastLabel.alpha = 1.0
- }) { _ in
- DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
- UIView.animate(withDuration: 0.25, animations: {
- toastLabel.alpha = 0.0
- }) { _ in
- toastLabel.removeFromSuperview()
- }
- }
- }
- }
- // MARK: - Setup
- private func buildUI() {
- view.backgroundColor = .white
-
- // 添加主要视图组件
- view.addSubview(bx_wrap)
- view.addSubview(bx_hint)
- view.addSubview(cx_wrap)
- view.addSubview(mx_commit)
-
- // 问题描述容器内的组件
- bx_wrap.addSubview(bx_bar)
- bx_wrap.addSubview(bx_title)
- bx_wrap.addSubview(bx_text)
-
- // 联系方式容器内的组件
- cx_wrap.addSubview(cx_bar)
- cx_wrap.addSubview(cx_title)
- cx_wrap.addSubview(cx_hint)
- cx_wrap.addSubview(cx_text)
- }
-
- private func wireConstraints() {
- // 问题描述容器
- NSLayoutConstraint.activate([
- bx_wrap.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
- bx_wrap.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
- bx_wrap.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
- ])
-
- // 蓝色分割线
- NSLayoutConstraint.activate([
- bx_bar.leadingAnchor.constraint(equalTo: bx_wrap.leadingAnchor),
- bx_bar.topAnchor.constraint(equalTo: bx_title.topAnchor),
- bx_bar.bottomAnchor.constraint(equalTo: bx_title.bottomAnchor),
- bx_bar.widthAnchor.constraint(equalToConstant: 3),
- ])
-
- // 问题描述标题和文本框
- NSLayoutConstraint.activate([
- bx_title.leadingAnchor.constraint(equalTo: bx_bar.trailingAnchor, constant: 8),
- bx_title.trailingAnchor.constraint(equalTo: bx_wrap.trailingAnchor),
- bx_title.topAnchor.constraint(equalTo: bx_wrap.topAnchor),
-
- bx_hint.leadingAnchor.constraint(equalTo: bx_title.leadingAnchor, constant: 0),
- bx_hint.topAnchor.constraint(equalTo: bx_title.bottomAnchor, constant: 8),
-
- bx_text.topAnchor.constraint(equalTo: bx_hint.bottomAnchor, constant: 8),
- bx_text.leadingAnchor.constraint(equalTo: bx_wrap.leadingAnchor, constant: 8),
- bx_text.trailingAnchor.constraint(equalTo: bx_wrap.trailingAnchor, constant: -8),
- bx_text.heightAnchor.constraint(equalToConstant: 150),
- bx_text.bottomAnchor.constraint(equalTo: bx_wrap.bottomAnchor),
- ])
-
- // 联系方式容器
- NSLayoutConstraint.activate([
- cx_wrap.topAnchor.constraint(equalTo: bx_wrap.bottomAnchor),
- cx_wrap.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
- cx_wrap.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
- ])
-
- // 蓝色分割线
- NSLayoutConstraint.activate([
- cx_bar.leadingAnchor.constraint(equalTo: cx_wrap.leadingAnchor),
- cx_bar.topAnchor.constraint(equalTo: cx_title.topAnchor),
- cx_bar.bottomAnchor.constraint(equalTo: cx_title.bottomAnchor),
- cx_bar.widthAnchor.constraint(equalToConstant: 3),
- ])
-
- // 联系方式标题和文本框
- NSLayoutConstraint.activate([
- cx_title.leadingAnchor.constraint(equalTo: cx_bar.trailingAnchor, constant: 8),
- cx_title.trailingAnchor.constraint(equalTo: cx_wrap.trailingAnchor),
- cx_title.topAnchor.constraint(equalTo: cx_wrap.topAnchor, constant: 16),
-
- cx_hint.leadingAnchor.constraint(equalTo: cx_title.leadingAnchor, constant: 0),
- cx_hint.topAnchor.constraint(equalTo: cx_title.bottomAnchor, constant: 8),
-
- cx_text.topAnchor.constraint(equalTo: cx_hint.bottomAnchor, constant: 8),
- cx_text.leadingAnchor.constraint(equalTo: cx_wrap.leadingAnchor, constant: 8),
- cx_text.trailingAnchor.constraint(equalTo: cx_wrap.trailingAnchor, constant: -8),
- cx_text.heightAnchor.constraint(equalToConstant: 150),
- cx_text.bottomAnchor.constraint(equalTo: cx_wrap.bottomAnchor),
- ])
-
- // 提交按钮
- NSLayoutConstraint.activate([
- mx_commit.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50),
- mx_commit.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
- mx_commit.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
- mx_commit.heightAnchor.constraint(equalToConstant: 50)
- ])
- }
-
- private func hookDelegates() {
- bx_text.delegate = self
- cx_text.delegate = self
- }
-
- private func installGestures() {
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ax_hideKB))
- view.addGestureRecognizer(tapGesture)
- }
-
- @objc private func ax_hideKB() {
- view.endEditing(true)
- }
-
- @objc private func ax_back() {
- if presentingViewController != nil {
- dismiss(animated: true, completion: nil)
- } else {
- navigationController?.popViewController(animated: true)
- }
- }
- }
- // MARK: - TextView Delegate
- extension VKWhistleBoardController: UITextViewDelegate {
- func textViewDidBeginEditing(_ textView: UITextView) {
- if textView.textColor == .lightGray {
- textView.text = nil
- textView.textColor = .black
- }
- }
-
- func textViewDidEndEditing(_ textView: UITextView) {
- if textView.text.isEmpty {
- textView.text = ""
- textView.textColor = .lightGray
- }
- }
- }
- // MARK: - TextField Delegate
- extension VKWhistleBoardController: UITextFieldDelegate {
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- textField.resignFirstResponder()
- return true
- }
- }
- // Compatibility alias for legacy code
- typealias FeedbackViewController = VKWhistleBoardController
|