LNOrderRecordCell.swift 20 KB

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