FeedbackViewController.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. //
  2. // FeedbackViewController.swift
  3. // VenusKitto
  4. //
  5. // Created by Neoa on 2025/8/27.
  6. //
  7. import Foundation
  8. import UIKit
  9. final class VKWhistleBoardController: UIViewController {
  10. // MARK: - UI Elements
  11. // 问题描述部分
  12. private let bx_wrap: UIView = {
  13. let view = UIView()
  14. view.translatesAutoresizingMaskIntoConstraints = false
  15. return view
  16. }()
  17. private let bx_bar: UIView = {
  18. let view = UIView()
  19. view.backgroundColor = UIColor(hex: "#FFE059")
  20. view.translatesAutoresizingMaskIntoConstraints = false
  21. return view
  22. }()
  23. private let bx_title: UILabel = {
  24. let label = UILabel()
  25. label.translatesAutoresizingMaskIntoConstraints = false
  26. // 创建属性字符串
  27. let mainString = "问题描述(必填)"
  28. let attributedString = NSMutableAttributedString(string: mainString)
  29. // 设置整体样式
  30. attributedString.addAttributes([
  31. .font: UIFont.systemFont(ofSize: 15),
  32. .foregroundColor: UIColor.black
  33. ], range: NSRange(location: 0, length: mainString.count))
  34. // 将"(必填)"设置为红色
  35. if let range = mainString.range(of: "(必填)") {
  36. let nsRange = NSRange(range, in: mainString)
  37. attributedString.addAttributes([
  38. .foregroundColor: UIColor.red
  39. ], range: nsRange)
  40. }
  41. label.attributedText = attributedString
  42. return label
  43. }()
  44. private let bx_hint: UILabel = {
  45. let label = UILabel()
  46. label.text = "请尽量将问题描述详细"
  47. label.font = UIFont.boldSystemFont(ofSize: 14)
  48. label.textAlignment = .left
  49. label.textColor = .lightGray
  50. label.translatesAutoresizingMaskIntoConstraints = false
  51. return label
  52. }()
  53. private let bx_text: UITextView = {
  54. let textView = UITextView()
  55. textView.font = UIFont.systemFont(ofSize: 16)
  56. textView.text = ""
  57. textView.textColor = .lightGray
  58. textView.layer.borderWidth = 0.5
  59. textView.layer.borderColor = UIColor.lightGray.cgColor
  60. textView.layer.cornerRadius = 4
  61. textView.translatesAutoresizingMaskIntoConstraints = false
  62. return textView
  63. }()
  64. // 联系方式部分
  65. private let cx_wrap: UIView = {
  66. let view = UIView()
  67. view.translatesAutoresizingMaskIntoConstraints = false
  68. return view
  69. }()
  70. private let cx_bar: UIView = {
  71. let view = UIView()
  72. view.backgroundColor = UIColor(hex: "#FFE059")
  73. view.translatesAutoresizingMaskIntoConstraints = false
  74. return view
  75. }()
  76. private let cx_title: UILabel = {
  77. let label = UILabel()
  78. label.text = "联系方式(选填)"
  79. label.font = UIFont.systemFont(ofSize: 15)
  80. label.textColor = .black
  81. label.translatesAutoresizingMaskIntoConstraints = false
  82. return label
  83. }()
  84. private let cx_hint: UILabel = {
  85. let label = UILabel()
  86. label.text = "请输入手机号或QQ号"
  87. label.font = UIFont.systemFont(ofSize: 14)
  88. label.textColor = .lightGray
  89. label.translatesAutoresizingMaskIntoConstraints = false
  90. return label
  91. }()
  92. private let cx_text: UITextView = {
  93. let textView = UITextView()
  94. textView.font = UIFont.systemFont(ofSize: 16)
  95. textView.text = ""
  96. textView.textColor = .lightGray
  97. textView.layer.borderWidth = 0.5
  98. textView.layer.borderColor = UIColor.lightGray.cgColor
  99. textView.layer.cornerRadius = 4
  100. textView.translatesAutoresizingMaskIntoConstraints = false
  101. return textView
  102. }()
  103. private let mx_commit: UIButton = {
  104. let button = UIButton(type: .system)
  105. button.setTitle("提交反馈", for: .normal)
  106. button.titleLabel?.font = UIFont.systemFont(ofSize: 16)
  107. button.setTitleColor(.black, for: .normal)
  108. button.backgroundColor = UIColor(hex: "#FFE059")
  109. button.layer.cornerRadius = 25
  110. button.translatesAutoresizingMaskIntoConstraints = false
  111. return button
  112. }()
  113. // MARK: - Lifecycle
  114. override func viewDidLoad() {
  115. super.viewDidLoad()
  116. buildUI()
  117. wireConstraints()
  118. hookDelegates()
  119. installGestures()
  120. navigationItem.title = "意见反馈"
  121. navigationItem.leftBarButtonItem = UIBarButtonItem(
  122. image: UIImage(systemName: "chevron.left"),
  123. style: .plain,
  124. target: self,
  125. action: #selector(ax_back)
  126. )
  127. navigationController?.navigationBar.tintColor = .black
  128. mx_commit.addTarget(self, action: #selector(ax_submit), for: .touchUpInside)
  129. }
  130. override func viewWillAppear(_ animated: Bool) {
  131. super.viewWillAppear(animated)
  132. navigationController?.setNavigationBarHidden(false, animated: animated)
  133. }
  134. @objc private func ax_submit() {
  135. // 验证问题描述是否为空
  136. if bx_text.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  137. vx_emit("请填写问题描述")
  138. return
  139. }
  140. // TODO: 在此处添加提交反馈的逻辑
  141. vx_emit("提交成功")
  142. // 延迟后返回上一页
  143. DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
  144. self.navigationController?.popViewController(animated: true)
  145. }
  146. }
  147. private func vx_emit(_ message: String) {
  148. let toastLabel = UILabel()
  149. toastLabel.text = message
  150. toastLabel.font = UIFont.systemFont(ofSize: 14)
  151. toastLabel.textColor = .white
  152. toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.7)
  153. toastLabel.textAlignment = .center
  154. toastLabel.alpha = 0.0
  155. toastLabel.layer.cornerRadius = 8
  156. toastLabel.clipsToBounds = true
  157. toastLabel.translatesAutoresizingMaskIntoConstraints = false
  158. view.addSubview(toastLabel)
  159. NSLayoutConstraint.activate([
  160. toastLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  161. toastLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -100),
  162. toastLabel.widthAnchor.constraint(equalToConstant: 160),
  163. toastLabel.heightAnchor.constraint(equalToConstant: 40)
  164. ])
  165. UIView.animate(withDuration: 0.3) {
  166. toastLabel.alpha = 1.0
  167. } completion: { _ in
  168. DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
  169. UIView.animate(withDuration: 0.3) {
  170. toastLabel.alpha = 0.0
  171. } completion: { _ in
  172. toastLabel.removeFromSuperview()
  173. }
  174. }
  175. }
  176. }
  177. // MARK: - Setup
  178. private func buildUI() {
  179. view.backgroundColor = .white
  180. // 添加主要视图组件
  181. view.addSubview(bx_wrap)
  182. view.addSubview(bx_hint)
  183. view.addSubview(cx_wrap)
  184. view.addSubview(mx_commit)
  185. // 问题描述容器内的组件
  186. bx_wrap.addSubview(bx_bar)
  187. bx_wrap.addSubview(bx_title)
  188. bx_wrap.addSubview(bx_text)
  189. // 联系方式容器内的组件
  190. cx_wrap.addSubview(cx_bar)
  191. cx_wrap.addSubview(cx_title)
  192. cx_wrap.addSubview(cx_hint)
  193. cx_wrap.addSubview(cx_text)
  194. }
  195. private func wireConstraints() {
  196. // 问题描述容器
  197. NSLayoutConstraint.activate([
  198. bx_wrap.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
  199. bx_wrap.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
  200. bx_wrap.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
  201. ])
  202. // 蓝色分割线
  203. NSLayoutConstraint.activate([
  204. bx_bar.leadingAnchor.constraint(equalTo: bx_wrap.leadingAnchor),
  205. bx_bar.topAnchor.constraint(equalTo: bx_title.topAnchor),
  206. bx_bar.bottomAnchor.constraint(equalTo: bx_title.bottomAnchor),
  207. bx_bar.widthAnchor.constraint(equalToConstant: 3),
  208. ])
  209. // 问题描述标题和文本框
  210. NSLayoutConstraint.activate([
  211. bx_title.leadingAnchor.constraint(equalTo: bx_bar.trailingAnchor, constant: 8),
  212. bx_title.trailingAnchor.constraint(equalTo: bx_wrap.trailingAnchor),
  213. bx_title.topAnchor.constraint(equalTo: bx_wrap.topAnchor),
  214. bx_hint.leadingAnchor.constraint(equalTo: bx_title.leadingAnchor, constant: 0),
  215. bx_hint.topAnchor.constraint(equalTo: bx_title.bottomAnchor, constant: 8),
  216. bx_text.topAnchor.constraint(equalTo: bx_hint.bottomAnchor, constant: 8),
  217. bx_text.leadingAnchor.constraint(equalTo: bx_wrap.leadingAnchor, constant: 8),
  218. bx_text.trailingAnchor.constraint(equalTo: bx_wrap.trailingAnchor, constant: -8),
  219. bx_text.heightAnchor.constraint(equalToConstant: 150),
  220. bx_text.bottomAnchor.constraint(equalTo: bx_wrap.bottomAnchor),
  221. ])
  222. // 联系方式容器
  223. NSLayoutConstraint.activate([
  224. cx_wrap.topAnchor.constraint(equalTo: bx_wrap.bottomAnchor),
  225. cx_wrap.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
  226. cx_wrap.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
  227. ])
  228. // 蓝色分割线
  229. NSLayoutConstraint.activate([
  230. cx_bar.leadingAnchor.constraint(equalTo: cx_wrap.leadingAnchor),
  231. cx_bar.topAnchor.constraint(equalTo: cx_title.topAnchor),
  232. cx_bar.bottomAnchor.constraint(equalTo: cx_title.bottomAnchor),
  233. cx_bar.widthAnchor.constraint(equalToConstant: 3),
  234. ])
  235. // 联系方式标题和文本框
  236. NSLayoutConstraint.activate([
  237. cx_title.leadingAnchor.constraint(equalTo: cx_bar.trailingAnchor, constant: 8),
  238. cx_title.trailingAnchor.constraint(equalTo: cx_wrap.trailingAnchor),
  239. cx_title.topAnchor.constraint(equalTo: cx_wrap.topAnchor, constant: 16),
  240. cx_hint.leadingAnchor.constraint(equalTo: cx_title.leadingAnchor, constant: 0),
  241. cx_hint.topAnchor.constraint(equalTo: cx_title.bottomAnchor, constant: 8),
  242. cx_text.topAnchor.constraint(equalTo: cx_hint.bottomAnchor, constant: 8),
  243. cx_text.leadingAnchor.constraint(equalTo: cx_wrap.leadingAnchor, constant: 8),
  244. cx_text.trailingAnchor.constraint(equalTo: cx_wrap.trailingAnchor, constant: -8),
  245. cx_text.heightAnchor.constraint(equalToConstant: 150),
  246. cx_text.bottomAnchor.constraint(equalTo: cx_wrap.bottomAnchor),
  247. ])
  248. // 提交按钮
  249. NSLayoutConstraint.activate([
  250. mx_commit.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50),
  251. mx_commit.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
  252. mx_commit.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
  253. mx_commit.heightAnchor.constraint(equalToConstant: 50)
  254. ])
  255. }
  256. private func hookDelegates() {
  257. bx_text.delegate = self
  258. cx_text.delegate = self
  259. }
  260. private func installGestures() {
  261. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ax_hideKB))
  262. view.addGestureRecognizer(tapGesture)
  263. }
  264. @objc private func ax_hideKB() {
  265. view.endEditing(true)
  266. }
  267. @objc private func ax_back() {
  268. if presentingViewController != nil {
  269. dismiss(animated: true, completion: nil)
  270. } else {
  271. navigationController?.popViewController(animated: true)
  272. }
  273. }
  274. }
  275. // MARK: - TextView Delegate
  276. extension VKWhistleBoardController: UITextViewDelegate {
  277. func textViewDidBeginEditing(_ textView: UITextView) {
  278. if textView.textColor == .lightGray {
  279. textView.text = nil
  280. textView.textColor = .black
  281. }
  282. }
  283. func textViewDidEndEditing(_ textView: UITextView) {
  284. if textView.text.isEmpty {
  285. textView.text = ""
  286. textView.textColor = .lightGray
  287. }
  288. }
  289. }
  290. // MARK: - TextField Delegate
  291. extension VKWhistleBoardController: UITextFieldDelegate {
  292. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  293. textField.resignFirstResponder()
  294. return true
  295. }
  296. }
  297. // Compatibility alias for legacy code
  298. typealias FeedbackViewController = VKWhistleBoardController