LNOrderRecordCell.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. //
  2. // LNOrderRecordCell.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/24.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNOrderRecordCell: UITableViewCell {
  11. private let dateLabel = UILabel()
  12. private let stateLabel = UILabel()
  13. private let gameIc = UIImageView()
  14. private let gameNameLabel = UILabel()
  15. private let orderIdLabel = UILabel()
  16. private let priceLabel = UILabel()
  17. private let countLabel = UILabel()
  18. private let avatar = UIImageView()
  19. private let nameLabel = UILabel()
  20. private let genderView = UIImageView()
  21. private let requestLine = UIView()
  22. private let requestView = UIView()
  23. private let requestLabel = UILabel()
  24. private let menuView = UIView()
  25. private let rejectButton = UIButton()
  26. private let acceptButton = UIButton()
  27. private let startButton = UIButton()
  28. private let completeButton = UIButton()
  29. private let commitButton = UIButton()
  30. private var curItem: LNOrderRecordItemVO?
  31. override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
  32. super.init(style: style, reuseIdentifier: reuseIdentifier)
  33. setupViews()
  34. }
  35. func update(_ item: LNOrderRecordItemVO) {
  36. gameIc.sd_setImage(with: URL(string: item.categoryIcon))
  37. dateLabel.text = TimeInterval(item.createTime / 1_000).formattedFullDateWithTime()
  38. gameNameLabel.text = item.bizCategoryName
  39. orderIdLabel.text = item.orderId
  40. priceLabel.text = item.price.toDisplay
  41. countLabel.text = "\(item.unit)x\(item.purchaseQty)"
  42. avatar.sd_setImage(with: URL(string: item.avatar))
  43. nameLabel.text = item.nickname
  44. genderView.image = switch item.gender {
  45. case .unknow: nil
  46. case .male: .icGenderMale
  47. case .female: .icGenderFemale
  48. }
  49. requestLabel.text = item.customerRemark
  50. requestLine.isHidden = item.customerRemark.isEmpty
  51. requestView.isHidden = item.customerRemark.isEmpty
  52. curItem = item
  53. updateByStateChanged()
  54. }
  55. required init?(coder: NSCoder) {
  56. fatalError("init(coder:) has not been implemented")
  57. }
  58. }
  59. extension LNOrderRecordCell {
  60. private func updateByStateChanged() {
  61. guard let curItem else { return }
  62. menuView.isHidden = true
  63. rejectButton.isHidden = true
  64. acceptButton.isHidden = true
  65. startButton.isHidden = true
  66. completeButton.isHidden = true
  67. commitButton.isHidden = true
  68. if curItem.refundApply {
  69. stateLabel.text = .init(key: "A00052")
  70. stateLabel.textColor = .init(hex: "#F23051")
  71. menuView.isHidden = false
  72. commitButton.isHidden = false
  73. return
  74. }
  75. switch curItem.status {
  76. case .created, .waitingForAccept:
  77. stateLabel.text = .init(key: "A00055")
  78. stateLabel.textColor = .init(hex: "#FFB836")
  79. menuView.isHidden = false
  80. rejectButton.isHidden = false
  81. acceptButton.isHidden = false
  82. case .completed:
  83. stateLabel.text = .init(key: "A00137")
  84. stateLabel.textColor = .text_6
  85. case .refunded:
  86. stateLabel.text = .init(key: "A00138")
  87. stateLabel.textColor = .text_3
  88. case .accepted:
  89. stateLabel.text = .init(key: "A00165")
  90. stateLabel.textColor = .text_6
  91. menuView.isHidden = false
  92. startButton.isHidden = false
  93. case .rejected:
  94. stateLabel.text = .init(key: "A00140")
  95. stateLabel.textColor = .text_3
  96. case .servicing:
  97. stateLabel.text = .init(key: "A00080")
  98. stateLabel.textColor = .init(hex: "#FFB836")
  99. menuView.isHidden = false
  100. completeButton.isHidden = false
  101. case .serviceDone:
  102. stateLabel.text = .init(key: "A00137")
  103. stateLabel.textColor = .text_6
  104. case .cancelled:
  105. stateLabel.text = .init(key: "A00152")
  106. stateLabel.textColor = .text_3
  107. }
  108. }
  109. private func setupViews() {
  110. backgroundColor = .clear
  111. let container = UIView()
  112. container.backgroundColor = .fill
  113. container.layer.cornerRadius = 12
  114. contentView.addSubview(container)
  115. container.snp.makeConstraints { make in
  116. make.horizontalEdges.equalToSuperview().inset(16)
  117. make.top.equalToSuperview().offset(10)
  118. make.bottom.equalToSuperview()
  119. }
  120. let stackView = UIStackView()
  121. stackView.axis = .vertical
  122. stackView.spacing = 0
  123. container.addSubview(stackView)
  124. stackView.snp.makeConstraints { make in
  125. make.horizontalEdges.equalToSuperview().inset(12)
  126. make.top.equalToSuperview().offset(2)
  127. make.bottom.equalToSuperview().offset(-8)
  128. }
  129. let top = buildTopView()
  130. stackView.addArrangedSubview(top)
  131. let line = UIView()
  132. line.backgroundColor = .fill_2
  133. line.snp.makeConstraints { make in
  134. make.height.equalTo(0.5).priority(.medium)
  135. }
  136. stackView.addArrangedSubview(line)
  137. let orderInfo = buildOrderInfo()
  138. stackView.addArrangedSubview(orderInfo)
  139. let userInfo = buildUserInfoView()
  140. stackView.addArrangedSubview(userInfo)
  141. let menu = buildBottomView()
  142. stackView.addArrangedSubview(menu)
  143. }
  144. private func buildTopView() -> UIView {
  145. let container = UIView()
  146. container.snp.makeConstraints { make in
  147. make.height.equalTo(30)
  148. }
  149. stateLabel.font = .heading_h5
  150. container.addSubview(stateLabel)
  151. stateLabel.snp.makeConstraints { make in
  152. make.centerY.equalToSuperview()
  153. make.trailing.equalToSuperview()
  154. make.width.height.equalTo(0).priority(.low)
  155. }
  156. dateLabel.font = .body_xs
  157. dateLabel.textColor = .text_3
  158. container.addSubview(dateLabel)
  159. dateLabel.snp.makeConstraints { make in
  160. make.leading.equalToSuperview()
  161. make.centerY.equalToSuperview()
  162. make.trailing.lessThanOrEqualTo(stateLabel.snp.leading).offset(-5)
  163. }
  164. return container
  165. }
  166. private func buildOrderInfo() -> UIView {
  167. let container = UIView()
  168. container.snp.makeConstraints { make in
  169. make.height.equalTo(54)
  170. }
  171. let price = buildPriceView()
  172. container.addSubview(price)
  173. price.snp.makeConstraints { make in
  174. make.centerY.equalToSuperview()
  175. make.trailing.equalToSuperview()
  176. }
  177. let infoView = buildGameInfoView()
  178. container.addSubview(infoView)
  179. infoView.snp.makeConstraints { make in
  180. make.leading.equalToSuperview()
  181. make.centerY.equalToSuperview().inset(4)
  182. make.trailing.lessThanOrEqualTo(price.snp.leading).offset(-12)
  183. }
  184. return container
  185. }
  186. private func buildUserInfoView() -> UIView {
  187. let container = UIView()
  188. container.backgroundColor = .fill_1
  189. container.layer.cornerRadius = 12
  190. let stackView = UIStackView()
  191. stackView.axis = .vertical
  192. stackView.spacing = 4
  193. container.addSubview(stackView)
  194. stackView.snp.makeConstraints { make in
  195. make.edges.equalToSuperview().inset(8)
  196. }
  197. let userView = UIView()
  198. userView.snp.makeConstraints { make in
  199. make.height.equalTo(23).priority(.medium)
  200. }
  201. stackView.addArrangedSubview(userView)
  202. avatar.layer.cornerRadius = 11.5
  203. avatar.clipsToBounds = true
  204. userView.addSubview(avatar)
  205. avatar.snp.makeConstraints { make in
  206. make.leading.equalToSuperview()
  207. make.centerY.equalToSuperview()
  208. make.width.height.equalTo(23)
  209. }
  210. nameLabel.font = .body_s
  211. nameLabel.textColor = .text_5
  212. userView.addSubview(nameLabel)
  213. nameLabel.snp.makeConstraints { make in
  214. make.centerY.equalTo(avatar)
  215. make.leading.equalTo(avatar.snp.trailing).offset(8)
  216. }
  217. userView.addSubview(genderView)
  218. genderView.snp.makeConstraints { make in
  219. make.centerY.equalTo(avatar)
  220. make.leading.equalTo(nameLabel.snp.trailing).offset(3)
  221. make.width.height.equalTo(14)
  222. }
  223. requestLine.backgroundColor = .fill_3
  224. requestLine.snp.makeConstraints { make in
  225. make.height.equalTo(0.5)
  226. }
  227. stackView.addArrangedSubview(requestLine)
  228. stackView.addArrangedSubview(requestView)
  229. let titleLabel = UILabel()
  230. titleLabel.font = .heading_h5
  231. titleLabel.textColor = .text_4
  232. titleLabel.text = .init(key: "A00167")
  233. requestView.addSubview(titleLabel)
  234. titleLabel.snp.makeConstraints { make in
  235. make.leading.equalToSuperview()
  236. make.top.equalToSuperview()
  237. }
  238. requestLabel.font = .body_xs
  239. requestLabel.textColor = .text_4
  240. requestLabel.numberOfLines = 0
  241. requestLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
  242. requestLabel.setContentCompressionResistancePriority(.defaultHigh , for: .vertical)
  243. requestView.addSubview(requestLabel)
  244. requestLabel.snp.makeConstraints { make in
  245. make.horizontalEdges.equalToSuperview()
  246. make.top.equalTo(titleLabel.snp.bottom)
  247. make.bottom.equalToSuperview()
  248. }
  249. return container
  250. }
  251. private func buildBottomView() -> UIView {
  252. menuView.snp.makeConstraints { make in
  253. make.height.equalTo(38)
  254. }
  255. let stackView = UIStackView()
  256. stackView.axis = .horizontal
  257. stackView.spacing = 6
  258. menuView.addSubview(stackView)
  259. stackView.snp.makeConstraints { make in
  260. make.trailing.equalToSuperview()
  261. make.bottom.equalToSuperview()
  262. }
  263. rejectButton.setTitle(.init(key: "A00168"), for: .normal)
  264. rejectButton.setTitleColor(.text_3, for: .normal)
  265. rejectButton.titleLabel?.font = .heading_h5
  266. rejectButton.layer.cornerRadius = 15
  267. rejectButton.layer.borderWidth = 1
  268. rejectButton.layer.borderColor = UIColor.fill_3.cgColor
  269. rejectButton.contentEdgeInsets = .init(top: 8, left: 18, bottom: 8, right: 18)
  270. rejectButton.addAction(UIAction(handler: { [weak self] _ in
  271. guard let self else { return }
  272. guard let curItem else { return }
  273. LNOrderManager.shared.rejectOrder(orderId: curItem.orderId) { [weak self] success in
  274. guard let self else { return }
  275. guard success else { return }
  276. curItem.status = .rejected
  277. updateByStateChanged()
  278. }
  279. }), for: .touchUpInside)
  280. rejectButton.snp.makeConstraints { make in
  281. make.height.equalTo(30)
  282. }
  283. stackView.addArrangedSubview(rejectButton)
  284. acceptButton.setTitle(.init(key: "A00169"), for: .normal)
  285. acceptButton.setTitleColor(.text_1, for: .normal)
  286. acceptButton.titleLabel?.font = .heading_h5
  287. acceptButton.layer.cornerRadius = 15
  288. acceptButton.setBackgroundImage(.primary_8, for: .normal)
  289. acceptButton.contentEdgeInsets = .init(top: 8, left: 10, bottom: 8, right: 10)
  290. acceptButton.clipsToBounds = true
  291. acceptButton.addAction(UIAction(handler: { [weak self] _ in
  292. guard let self else { return }
  293. guard let curItem else { return }
  294. LNOrderManager.shared.acceptOrder(orderId: curItem.orderId) { [weak self] success in
  295. guard let self else { return }
  296. guard success else { return }
  297. curItem.status = .accepted
  298. updateByStateChanged()
  299. }
  300. }), for: .touchUpInside)
  301. acceptButton.snp.makeConstraints { make in
  302. make.height.equalTo(30)
  303. }
  304. stackView.addArrangedSubview(acceptButton)
  305. startButton.setTitle(.init(key: "A00064"), for: .normal)
  306. startButton.setTitleColor(.text_1, for: .normal)
  307. startButton.titleLabel?.font = .heading_h5
  308. startButton.layer.cornerRadius = 15
  309. startButton.setBackgroundImage(.primary_8, for: .normal)
  310. startButton.contentEdgeInsets = .init(top: 8, left: 10, bottom: 8, right: 10)
  311. startButton.clipsToBounds = true
  312. startButton.addAction(UIAction(handler: { [weak self] _ in
  313. guard let self else { return }
  314. guard let curItem else { return }
  315. LNOrderManager.shared.startOrderService(orderId: curItem.orderId) { [weak self] success in
  316. guard let self else { return }
  317. guard success else { return }
  318. curItem.status = .servicing
  319. updateByStateChanged()
  320. }
  321. }), for: .touchUpInside)
  322. startButton.snp.makeConstraints { make in
  323. make.height.equalTo(30)
  324. }
  325. stackView.addArrangedSubview(startButton)
  326. completeButton.setTitle(.init(key: "A00151"), for: .normal)
  327. completeButton.setTitleColor(.text_1, for: .normal)
  328. completeButton.titleLabel?.font = .heading_h5
  329. completeButton.layer.cornerRadius = 15
  330. completeButton.setBackgroundImage(.primary_8, for: .normal)
  331. completeButton.contentEdgeInsets = .init(top: 8, left: 10, bottom: 8, right: 10)
  332. completeButton.clipsToBounds = true
  333. completeButton.addAction(UIAction(handler: { [weak self] _ in
  334. guard let self else { return }
  335. guard let curItem else { return }
  336. LNOrderManager.shared.finishOrderService(orderId: curItem.orderId) { [weak self] success in
  337. guard let self else { return }
  338. guard success else { return }
  339. curItem.status = .serviceDone
  340. updateByStateChanged()
  341. }
  342. }), for: .touchUpInside)
  343. completeButton.snp.makeConstraints { make in
  344. make.height.equalTo(30)
  345. }
  346. stackView.addArrangedSubview(completeButton)
  347. commitButton.setTitle(.init(key: "A00170"), for: .normal)
  348. commitButton.setTitleColor(.text_6, for: .normal)
  349. commitButton.titleLabel?.font = .heading_h5
  350. commitButton.layer.cornerRadius = 15
  351. commitButton.setBackgroundImage(.primary_7, for: .normal)
  352. commitButton.contentEdgeInsets = .init(top: 8, left: 10, bottom: 8, right: 10)
  353. commitButton.clipsToBounds = true
  354. commitButton.addAction(UIAction(handler: { [weak self] _ in
  355. guard let self else { return }
  356. guard let curItem else { return }
  357. pushToProtest(curItem.orderId) { [weak self] in
  358. guard let self else { return }
  359. curItem.status = .serviceDone
  360. updateByStateChanged()
  361. }
  362. }), for: .touchUpInside)
  363. commitButton.snp.makeConstraints { make in
  364. make.height.equalTo(30)
  365. }
  366. stackView.addArrangedSubview(commitButton)
  367. let cover = UIView()
  368. cover.backgroundColor = .fill
  369. cover.layer.cornerRadius = 14
  370. cover.isUserInteractionEnabled = false
  371. commitButton.insertSubview(cover, at: 0)
  372. cover.snp.makeConstraints { make in
  373. make.edges.equalToSuperview().inset(1)
  374. }
  375. return menuView
  376. }
  377. private func buildGameInfoView() -> UIView {
  378. let container = UIView()
  379. gameIc.layer.cornerRadius = 20
  380. gameIc.clipsToBounds = true
  381. container.addSubview(gameIc)
  382. gameIc.snp.makeConstraints { make in
  383. make.leading.equalToSuperview()
  384. make.verticalEdges.equalToSuperview()
  385. make.width.height.equalTo(50)
  386. }
  387. let textInfo = UIView()
  388. container.addSubview(textInfo)
  389. textInfo.snp.makeConstraints { make in
  390. make.centerY.equalToSuperview()
  391. make.leading.equalTo(gameIc.snp.trailing).offset(8)
  392. make.trailing.equalToSuperview()
  393. }
  394. gameNameLabel.textColor = .text_5
  395. gameNameLabel.font = .heading_h4
  396. textInfo.addSubview(gameNameLabel)
  397. gameNameLabel.snp.makeConstraints { make in
  398. make.horizontalEdges.equalToSuperview()
  399. make.top.equalToSuperview()
  400. }
  401. orderIdLabel.font = .body_xs
  402. orderIdLabel.textColor = .text_5
  403. textInfo.addSubview(orderIdLabel)
  404. orderIdLabel.snp.makeConstraints { make in
  405. make.horizontalEdges.equalToSuperview()
  406. make.bottom.equalToSuperview()
  407. make.top.equalTo(gameNameLabel.snp.bottom).offset(2)
  408. }
  409. return container
  410. }
  411. private func buildPriceView() -> UIView {
  412. let container = UIView()
  413. priceLabel.font = .heading_h3
  414. priceLabel.textColor = .text_5
  415. container.addSubview(priceLabel)
  416. priceLabel.snp.makeConstraints { make in
  417. make.top.trailing.equalToSuperview()
  418. }
  419. let coin = UIImageView.coinImageView()
  420. container.addSubview(coin)
  421. coin.snp.makeConstraints { make in
  422. make.centerY.equalTo(priceLabel)
  423. make.leading.equalToSuperview()
  424. make.trailing.equalTo(priceLabel.snp.leading)
  425. make.width.height.equalTo(16)
  426. }
  427. countLabel.font = .body_m
  428. countLabel.textColor = .text_5
  429. countLabel.textAlignment = .right
  430. countLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  431. countLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
  432. container.addSubview(countLabel)
  433. countLabel.snp.makeConstraints { make in
  434. make.trailing.bottom.equalToSuperview()
  435. make.top.equalTo(priceLabel.snp.bottom)
  436. }
  437. return container
  438. }
  439. }
  440. #if DEBUG
  441. import SwiftUI
  442. struct LNOrderRecordCellPreview: UIViewRepresentable {
  443. func makeUIView(context: Context) -> some UIView {
  444. let container = UIView()
  445. container.backgroundColor = .lightGray
  446. let view = LNOrderRecordCell()
  447. container.addSubview(view)
  448. view.snp.makeConstraints { make in
  449. make.horizontalEdges.equalToSuperview()
  450. make.centerY.equalToSuperview()
  451. make.height.equalTo(133)
  452. }
  453. return container
  454. }
  455. func updateUIView(_ uiView: UIViewType, context: Context) { }
  456. }
  457. #Preview(body: {
  458. LNOrderRecordCellPreview()
  459. })
  460. #endif // DEBUG